summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRoman Birg <roman@cyngn.com>2014-04-24 22:22:45 -0700
committerAndy Mast <andy@cyngn.com>2015-02-13 17:16:40 -0800
commit041ca05791c2967665d35433f72285830a4e1280 (patch)
tree883423292f681cd02b77f469c392b0fa9090a78f
parentf5cbc865b5c6505026f214d053878291ea1363b8 (diff)
downloadandroid_frameworks_base-staging/caf/themes/cm-12.0.tar.gz
android_frameworks_base-staging/caf/themes/cm-12.0.tar.bz2
android_frameworks_base-staging/caf/themes/cm-12.0.zip
Themes: Squashed for caf [1/6]staging/caf/themes/cm-12.0
frameworks: add keyguard wallpaper functionality to WallpaperManager Adapted for cm-12. Original Id: I0eb1b5578b705c2ade4c0772fa86e09478b9f73e Signed-off-by: Roman Birg <roman@cyngn.com> Conflicts: core/res/res/values/cm_strings.xml Id: I1625f0569a66ad77e18b8377c519a07b7de6afdc Themes: Port to CM12 [1/6] Themes: Aapt port Id: I106d447daf7935bada65e78911d8973ce0ca27ae Themes: Port our additions to idmap Id: I2e47cc23de4e7c0b884cccbd87c7d77079ac6824 Themes: remove legacy theme support from idmap Id: I17dfe35f9985d8cef790b26a8bcda738ea65917c Themes: Forward port changes to Installer.java Id: If64e856773e50f5ed74f2358e0c590abad724689 Themes: Add clean spec for aapt Id: I68f5f63f7e83b99230860dd2d8646e96da484b62 androidfw: Port CM overlay contributions Id: Id7b7f5f35011922c668fcea3a8aec5b42bd28653 androidfw: Port addOverlayPath and removeOverlayPath Id: I279db083af28fd8941f3227f2a7512ff094742c1 androidfw: Port addCommonOverlayPath Id: I12d2fe05a04f6a7e553c330505a475346374b507 androidfw: Port addIconPath Id: Ide3db28cde0c7f93edd9e7ad626ebace8d4105cc androidfw: Allow package ID to be overriden at runtime Id: Ieca3a0ae070a6c0ad0cf2b73b5944d83397d08b9 Aapt: Zip Parsing Test Cases w/ Refactoring Id: I28f9115700e186136432138d228111ebcfbd0480 Themes: Update tests for idmap Id: I3dae5bd376d122eab397863378599ae0ac7c6734 androidfw: Add test case for overriding package id Id: I2668c529e24a55cd6bc8437406fc284b853a75e7 androidfw: Add tests for bags The overlayOverridesStyleAttribute will currently fail since our changes to allow theming styles is not currently implemented. Id: Idfacc4382baf4152c839799a22b6cbe015ef2197 androidfw: Don't consider package ids 0x60 and 0x61 as dynamic Package IDs that are not 0x01 (system) or 0x7f (app) are treated as dynamic references. Overlays are assigned specific package ids that fall within the region of shared libraries. This patch treats them the same as system and app resource packages. Id: Ieecaa889bed50490796351302405a38f77c84f4e Theme Parsing & Info Id: I3583d7e8ca704402e3d8c6e1c7cea1645b91c06f Port ThemeUtils and its dependencies This is pretty much copy/paste from cm-11.0 Id: I406860a259136ccca107b981aca0369851df445e Themes: AndroidManifest and Intent port Id: Ib97e8539301d20d120fd8b49891fdaae8205fe42 ComposedIconInfo Port (w/ stubbed IconPackHelper) IconPackHelper is stubbed out so that PMService can reference it, we'll need to port the Resource stack before porting the implementation. Id: I59b680511de525e1d375a4f3be04347686b5e81b Port PackageManagerService and other dependencies Id: I11629d1e5eee21e01c060bc6c0393aae96034b69 Themes: Add in ThemeService See also: external/selinux. The policy must be added in order for the service to start without a security exception. Id: Ic6f64796b264e430e9706a17a3fd2a35085fd1ca TODO: ThemeService / Keyguard interaction TODO: SystemServer - AppsLaunchFailure Add AppsLaunchFailure Id: I09a3826f89c62cb898866408e807f269616f48fc androidfw: Update bags tests The overlayOverridesStyleAttribute was updated such that it does not check the attribute of the theme before adding the overlay. The bag is cached on the first call and the next call, after the overlay is attached, returns that pre-computed bag so the test would always fail. We now simply check that the attribute matches the one in the overlay, and if so then the test passes. This patch also adds a new test to check that an overlay can reference and access resources that are unique to the overlay. Currently this test fails. Id: I3892df3f0d9443a73eaa11b3d5e97cfe86620a73 androidfw: Add test for referencing overlay styles as parents Id: I4fa3bd447c888e96176955924ebe7ee5c784ab55 androidfw: Allow referencing and retrieving overlay resources This patch allows a theme to reference it's own resources. Overlays get their own group which contains those resources that are not idmapped. Idmapped resources end up in the target's group. Id: Ibc119ddcdb35d44a8afec3c6152bcab2909cda18 androidfw: Fill in missing attributes from overlay style Id: I74051b379b73c728c6a2aa4bc62f3cd268a40b53 Protect windowNoTitle and windowActionBar attributes This patch creates a new method to define "protected" attributes. These are attributes like windowActionBar which should not be modified by a theme. Some apps (eg gmail) use the appcompat library which has its own Actionbar classes. When an app uses its own Actionbar it must not include the default actionbar which is achieved through the windowActionBar attribute. Some themes may try to change these attributes, which can will cause the app to crash. Id: Ie3bb7285eed09f3f13facf9d142ea9eb83796eec Themes: Use SYSTEM_DEFAULT in ThemeService Id: I52794dd98ca2f64aa50046ecdd7f79f27c21dd98 androidfw: Test missing parent attributes are merged in This test checks that an overlaid style contains any attributes that were in the original style but omitted in the overlaid style. Id: I6b496ef2eb0a7ef27b4fafdfda5bdf7ccffad989 androidfw: Add test case for protected attributes For this test to pass a protected attribute, such as windowNoTitle, must be equal to the original and not the value specified in the overlay. Id: Ic03f11214a1fc4139e3c48d7e72694a80f819023 Themes: Attach theme and icon resources from java Id: I9ffa0ce96a4af603b78b32d6b190f9698d3e4b4f Themes: Icons, icons, icons! Let there be icons. Legacy icons and composed icons are included in this patch. Id: I9fedafa270f1c4dc30c9c8ffd4cf619895e688e6 Themes: Retrieve explicitly themed context and resources Id: I4e41c251aee47361b183b60089bf5666540f653e Themes: Add themeChange config change to manifest Id: Ia84c0089a79637906e4f75fa38a56e8ff3b21a2b Themes: Register THEME_SERVICE in ContextImpl Id: I608a0b65c7e2ff0d69bae7bf343916f2b985f4a0 Themes: Remove legacy theme support Id: I25887843d31f705425aa40f9a23482fd2cafaef8 androidfw: correctly index paths in idmap Since we added the mtime values for the target and overlay, the indices are increased by two. Id: Ie0f5474d425945d58a12021cd2739240d2e98c0a androidfw: Fix opening assets from theme resources Id: Iedb51163a62b046cdf7fda1ad1b55cc1ee409047 Themes: Consider overlaid resource as "best fit" Id: Ife8342a49eb9502be52f085f88161b113332e9e6 Themes: Save and restore theme config Id: I3fcd445fb458aa6ed09397c05df6eb66d9be7235 Themes: Let ThemeService process additional themes Id: I45837f26948367d5cc6c520e8c53f9da60bd1fda AAPT: Don't applyVersionForCompatibility on android When compiling themes with aapt, we do not want aapt to call applyVersionForCompatibility as this causes the entries in the resource table to have an incorrect path. Id: Ie2c69533b3659c7b7458d6e4b7bdc84946d1be8e androidfw: Don't consider package id 0x5f as dynamic Package id 0x5f is reserved for common overlay resources and needs to be reserved so that it is not considered a dynamic. Id: Id27b8e0e2231ee8541365274d512e347afcfd05b AAPT: Include resources.arsc in apk Common resources needs the resources.arsc in the resources.apk so that it can be included when processing other overlays. Without it, common resources cannot be referenced. Id: I4aee29f660e4a0aa1909240dc0ca5680f0a2d135 Themes: Add keyguard wallpaper support to theme service Id: Ib8f8acd55ab4d2b6ef06ee0a630dc50c4f870beb Themes: Don't pre-process non .9.png images When creating a resources.apk we do not need to pre-process the normal .png images as those can be referenced directly from the theme's apk. Id: Iaf846a03ead9ecb1e68c040eac6e0ecbfc6e5875 Themes: Adjust offsets for idmap hashes Idmap now has a header so the indices to the hashes need to be incremented by one. Id: If1fb183cc116ef9e3ad6cb4e17b6e44763e9e72a Themes: Use single ThemeInfo instead of an array We only ever used the first index so there is no need to use an array of ThemeInfo(s) Id: I9e2af076bc17396a0c978be3c0d31c41277db3df New converter for Kitkat -> L fonts.xml L introduced a new fonts xml format. Its great, but our themes will keep using hte old format. This provides a converter and test cases. The parser was taken from the chooser and remains mostly unchanged with the exception of a getName() helper method. Id: Ia1d42c9e50eb7b52d2d98fe6dbeee530bef3adc2 Themes: Port theme bootanimation support to CM12 Id: Ie016884b0e3b77e08732308923ac44e0975e0116 Resources: Clear drawable cache Id: I04b5b78cce703194a2baeff9c51d2e4733b8ccc9 Font switching Id: Ia43060a7db624102cdcd9b0d9dc7148441401584 Zygote changes Id: Ie3681cf0d2b9929661cf1214e899cef9a5f37471 Recreate String Blocks Id: I4747ebd1a0908b76ae7214b0584948353d426fc5 add a getter for the x and y offsets of the wallpaper window Id: I35294bcac664e85cc5d344b50b5c4335a60d3f37 Themes: Don't spam logcat with CREATING STRING CACHE When processing resources with AAPT on the device, it spams the logcat with warning messages about CREATING STRING CACHE. Change ALOGW to ALOGV so it will only show when verbose logging is enabled. Id: I5b591c3336e176dd71cebe672d60721c29651b00 SystemUI: Audio Volume Panel Id: I78c471864af401b274597339b8451e65931fdb32 AmService uiContext port Id: Ida251d7f80797b0ec78b3d20cf60a795d6c4c1f0 Cleanly detach theme assets Types from an overlay are added to the target group's TypeList and need to be removed when the overlay assets are detached. Failing to remove these types results in resources not being retieved due to the erroneous types. Id: I4a9c624e30309e61fce905ced45c55acd3ac4845 Themes: GlobalAction Port Id: Ifd87e04f94a284e77f1c48bec9fd75d69c45c47e Themes: Do not store forward locked themes in ASEC containers If a theme is in a asec container and is applied, when the device is rebooted the device will get stuck in a nasty boot loop since the theme resources must be read and the asec container is not ready yet. Id: I1d93d8175d5c40b34c222974960c43352012a5ad Use systemui's applied theme for notifications. Notifications contain RemoteViews which are inflated using the application's context for which this notification belongs to. This can look out of place if SystemUI is using a different theme than the rest of the system. This patch will use SystemUIs theme when inflating the RemoteViews, giving us a more consistent look in the notification drawer. Id: I9514ce7fcc4858bad3d3c4190f55c1f5a1441d7c SysUI: Add theme support This ports over the changes needed to facilitate a theme change in SystemUI. Id: I673fb79db90994371a9c0627746a97414132f0ba Themes: Allow composing of VectorDrawable Base icons can be vector drawables. This patch allows them to be composed. Currently, VectorDrawables cannot have filters applied since they do not have a method to get the Paint object like BitmapDrawable and PaintDrawable. Id: I762c8e1f4d1c945b8ebc164bbd7944120324bd42 Themes: Add target api to ThemesContract This will allow the ThemesProvider to track the api a theme was built for. We may want to let the user know when a theme may not be designed for the version of CM installed on their device. Id: Idf0e6cef0ce9ac5e221ce5ff7e0b155ae0258d5f Access Themed ResTables from compiled theme apk [1/2] Before this patch the ResTable for a theme/app was created and accessed seperate from the compiled APK. Since the compiled APK has its own copy of the resources.arsc, we can just reuse the table in the APK instead. Id: I106a2434e74784bc04014831098f49fe128bc7e2 Themes: Port AppsLaunchFailureReceiver to CM12 Id: I5c3265e64aef1536ba5fceed0ec89082e786b686 Themes: Bump idmap hash version to 3 Due to changes in idmap, we need to force the recreation of resource cache when upgrading from CM11 to CM12. Id: I25c1e2c598bca889818e2d685651e3214c30ab3c Remove debug logs Id: Ia5cfa83ddf6da195e20526a94ba154864b8d0ecb Send target sdk version to aapt [1/2] If vector drawables are used in a theme we must have a minSdkVersion of 21 passed to aapt or else aapt will Segfault. Id: I687ee146f9f80543bbcdd06d93891cb3b23001c4 Add missing imports to ActivityThread Id: I09fe07807ed824ccb938e0e174b06653c613c403 Themes: Dynamically add/remove content from StatusBarWindow StatusBarWindowView has logic for resizing and fading content which doesn't always behave correctly if this view is not the root. Rather than create a container, this patch uses the existing StatusBarWindowView as the container and the inflated status bar is then added to this view. Id: Ia93d25a589419145f95d75b1b56eb3c2f300f935 Themes: don't use preloaded drawables when themed If we have themed assets we should try and load those rather than pulling from the preloaded drawables. This allows us to continue and preload drawables in ZygoteInit while maintaining the ability to theme those preloaded assets. Id: I68cfc099d328ece0791b6d0e5cf11d07097fd1fd CM11 -> CM12 Upgrade [1/3] - Introduce a new secure setting "THEME_PREV_BOOT_API_LEVEL". This field will always be set to the previous api level for themes. So if we upgrade from CM11 to CM12 this value will differ from the current API causing an upgrade to trigger - When moving from CM11 -> CM12, unapply incompatible overlays - Rename "holo" to "system" in secure settings themeConfig - Provide a testing downgrade script to put the secure settings db into a state similiar to CM11 (at least for themes) Id: I71be2c0ad83e60ffe8c574f913e5eaecb9700045 Themes: Add constant for system target API Id: I0a6caf65c9e8b0feeef1ae848ba4683235304e8c Conflicts: core/java/android/provider/Settings.java core/res/AndroidManifest.xml core/res/res/values/cm_strings.xml core/res/res/values/symbols.xml packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java policy/src/com/android/internal/policy/impl/GlobalActions.java services/core/java/com/android/server/pm/PackageManagerService.java services/java/com/android/server/SystemServer.java Id: I6196d5c395c400f90cddaeb4113e094fc6a9a7e0 Themes: Hide LegacyFontParser and THEME_PREV_BOOT_API_LEVEL Id: Ic43367a80dd6583b91fa6e0548af0866a832ac2a Clean host static libaapt Id: I4b00f171577b85ad02e7e7e398425a46bdfc5466 Themes: Fix GlobalAction Background Id: Ic9451cbf31ca8671b16ed4eaef53c694a478e052 Themes: Call startKeyguard() when recreating status bar mStatusBarKeyguardViewManager holds on to a reference of ScrimController and we need to update this otherwise the scrim will remain on the screen and consume all touch events. This is easily reproduced by changing a theme and then turning the screen off and on to display the keyguard. Once the keyguard is dismissed the scrim remains on the screen. Id: Ie050a951117ed4aebef4ea3343366b9b9361b8a6 Themes: Restructure resource cache [1/2] The new structure is as follows: /data/resource-cache/ ├─ theme1_pkg_name | ├─ target1_pkg_name | | ├─ idmap | | └─ resources.apk | ├─ target2_pkg_name | · | · | · | ├─ targetN_pkg_name | └─ icons | ├─ hash | └─ resources.apk ├─ theme2_pkg_name · · · └─ themeN_pkg_name Id: Id39688c88929733b42368c1f20ef0e25848a3390 Themes: Ensure themed fonts always have fallbacks We need to ensure that if fonts are being used from a theme that we include any families from the system that the theme did not include. We also need to add the Roboto fonts as fallback. The system sans-serif fonts (Roboto) are added as fallback fonts. This ensures we have a fallback for glyphs that are not in the themed fonts. Id: Iebb634ef4d01fedd132858410a5d392e74800acf Themes: Don't add common assets if files cannot be accessed If resApkPath does not exist, asset manaager will load the resources from the theme's apk which end up overriding resources in the 0x7f package id. This causes apps, such as SystemUI, to crash endlessly. The correct behavior is to not add common overlay assets if the files that are being passed in as paths cannot be accessed. The same behavior is already used in AssetManager::addOverlayPath() Id: If5e90baeda5f07973f268060dcb6d73dae657603 Themes: Update LoadedApk resources. Ported from cm-11.0 with lazy loading of resources Id: I324eb5ebc106a531afc4b44ea5d32f272250f6a8 Themes: System theme should clear wallpaper CM12's default lockscreen wallpaper is clear. So applying the system default should clear the wallpaper. Id: I9a269985009f96c18948aa8db9cef64c245fe431 Themes: Watch all app crashes for problematic themes Only watching for exceptions that occur during an apps launch may not be sufficient to catch possible bad themes causing issues. This patch monitors all app crashes and broadcasts that there was a crash if either Resources.NotFoundException or InflateException are thrown. The remaining logic is the same as when an app launch failure occurred. We also display a notification to the user so that they know why their theme was reset to the system theme. Id: I0761d02587b5b81deee4a31a89f515dbc7cc5fe6 Conflicts: core/res/res/values/cm_strings.xml core/res/res/values/cm_symbols.xml Themes: Allow setting a default theme This is a forward port from cm-11.0 Id: I045e1e3a0e561331f0964fe33ec25d2d2a82794a Conflicts: packages/SettingsProvider/res/values/defaults.xml packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java Themes: Check common resource paths at java layer This solves two problems: 1. It won't try to attach common resources that do not exist 2. Avoids call down to the native layer if they don't exist Id: I7b58b3f8a2540b6f62bd54feb21a3e33320c8e47 Themes: Let garbage collector free up native instances This patch introduces typefaces which mirror the public ones but are not final so they can be garbage collected and the native instance freed without causing native crashes when we try to unreference them manually during a font change. Id: I488fb8f05e9b2bcaac07fccbea267f93c93f6dd6 Themes: Handle case when keyguard is showing during theme change After we recreate the status bar we need to check if the keyguard was showing and if so make sure it is showing after the theme change. We also need to make sure the phone status bar is not visible if the keyguard was showing, otherwise you see two status bars. Id: Iba09e3e5c8cf52d49f292423a50034a3cf84d9c1 Themes: Use mRealPackage, when available, for idmapping Some packages replace existing apps and take on their package name, i.e. com.cyanogenmod.trebuchet -> com.android.launcher3. When this happens pkg.packageName becomes the name of the package it is overriding and mRealPackage will be the original package name. Because it is possible for the resources in the new package to not match up with the package it replaced, we need to perform the idmap using the original package name. Id: Ic22c7005274ae5c4ea29d9a4b29f9b9f40719e68 Themes: Clear bag cache when removing overlay assets The bag cache needs to be cleared from the group that was being overlaid, otherwise it will be referencing styles and attributes that may no longer exist. Id: Ieef3ff2c55a643e12e9c4b8b4f6d5fd54da663d7 Themes: Allow notification shadows to be rounded If a theme has a very large rounded rectangle it is obvious the notification's shadow is a rectangle. So this patch set the shadow to be drawn as a rounded rect like the notification itself. Id: I560fc5c4a4ce3d052d98b14af978a28c13b6caca Conflicts: packages/SystemUI/res/values/cm_dimens.xml Themes: Get boot theme config from ThemeUtils mBootThemeConfig is always null right now because we ever assign it using ThemeUtils.getBootThemeDirty(). This patch makes sure we take care of that now. Id: I3bb8a3361f0bea8f31d1a03762378bc7ff316127 Change-Id: I68d96635fb71753a1feee1179d52f79423bf4075
-rwxr-xr-xAndroid.mk3
-rw-r--r--CleanSpec.mk9
-rw-r--r--cmds/bootanimation/BootAnimation.cpp13
-rw-r--r--cmds/bootanimation/BootAnimation.h2
-rw-r--r--cmds/idmap/create.cpp37
-rw-r--r--cmds/idmap/idmap.cpp72
-rw-r--r--cmds/idmap/idmap.h5
-rw-r--r--cmds/idmap/inspect.cpp12
-rw-r--r--cmds/idmap/scan.cpp2
-rw-r--r--core/java/android/app/ActivityManager.java25
-rw-r--r--core/java/android/app/ActivityThread.java28
-rw-r--r--core/java/android/app/ApplicationPackageManager.java72
-rw-r--r--core/java/android/app/ComposedIconInfo.aidl19
-rw-r--r--core/java/android/app/ComposedIconInfo.java96
-rw-r--r--core/java/android/app/ContextImpl.java55
-rw-r--r--core/java/android/app/IWallpaperManager.aidl19
-rw-r--r--core/java/android/app/IWallpaperManagerCallback.aidl5
-rw-r--r--core/java/android/app/IconPackHelper.java848
-rw-r--r--core/java/android/app/LoadedApk.java3
-rw-r--r--core/java/android/app/ResourcesManager.java409
-rw-r--r--core/java/android/app/WallpaperManager.java183
-rw-r--r--core/java/android/content/Context.java30
-rw-r--r--core/java/android/content/ContextWrapper.java15
-rw-r--r--core/java/android/content/Intent.java66
-rw-r--r--core/java/android/content/pm/ActivityInfo.java7
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java10
-rw-r--r--core/java/android/content/pm/BaseThemeInfo.java111
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl6
-rw-r--r--core/java/android/content/pm/PackageInfo.java48
-rw-r--r--core/java/android/content/pm/PackageInfoLite.java3
-rw-r--r--core/java/android/content/pm/PackageItemInfo.java14
-rw-r--r--core/java/android/content/pm/PackageManager.java62
-rw-r--r--core/java/android/content/pm/PackageParser.java190
-rw-r--r--core/java/android/content/pm/ThemeInfo.aidl3
-rw-r--r--core/java/android/content/pm/ThemeInfo.java65
-rw-r--r--core/java/android/content/pm/ThemeUtils.java718
-rw-r--r--core/java/android/content/res/AssetManager.java225
-rw-r--r--core/java/android/content/res/CompatibilityInfo.java16
-rw-r--r--core/java/android/content/res/Configuration.java84
-rw-r--r--core/java/android/content/res/IThemeChangeListener.aidl22
-rw-r--r--core/java/android/content/res/IThemeProcessingListener.aidl21
-rw-r--r--core/java/android/content/res/IThemeService.aidl40
-rw-r--r--core/java/android/content/res/Resources.java175
-rw-r--r--core/java/android/content/res/ResourcesKey.java7
-rw-r--r--core/java/android/content/res/ThemeConfig.java553
-rw-r--r--core/java/android/content/res/ThemeManager.java298
-rw-r--r--core/java/android/os/Process.java7
-rwxr-xr-xcore/java/android/provider/Settings.java29
-rw-r--r--core/java/android/provider/ThemesContract.java565
-rw-r--r--core/java/android/view/IWindowManager.aidl12
-rw-r--r--core/java/android/view/IWindowSession.aidl10
-rw-r--r--core/java/android/view/WindowManagerPolicy.java3
-rw-r--r--core/java/android/widget/RemoteViews.java10
-rw-r--r--core/java/com/android/internal/os/ZygoteConnection.java10
-rw-r--r--core/java/com/android/internal/util/cm/ImageUtils.java297
-rw-r--r--core/jni/android_util_AssetManager.cpp186
-rw-r--r--core/res/AndroidManifest.xml46
-rw-r--r--core/res/res/anim/lock_screen_wallpaper_exit_noop.xml32
-rw-r--r--core/res/res/values/attrs_manifest.xml2
-rwxr-xr-xcore/res/res/values/cm_strings.xml27
-rw-r--r--core/res/res/values/cm_symbols.xml30
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--graphics/java/android/graphics/FontListConverter.java220
-rw-r--r--graphics/java/android/graphics/FontListParser.java53
-rw-r--r--graphics/java/android/graphics/LegacyFontListParser.java187
-rw-r--r--graphics/java/android/graphics/Typeface.java173
-rw-r--r--graphics/tests/localtests/Android.mk18
-rw-r--r--graphics/tests/localtests/src/android/graphics/FontListConverterTest.java280
-rw-r--r--graphics/tests/localtests/src/android/graphics/TypefaceTestSuite.java27
-rw-r--r--include/androidfw/AssetManager.h33
-rw-r--r--include/androidfw/ResourceTypes.h24
-rw-r--r--libs/androidfw/AssetManager.cpp359
-rw-r--r--libs/androidfw/ResourceTypes.cpp500
-rw-r--r--libs/androidfw/tests/Android.mk4
-rw-r--r--libs/androidfw/tests/Idmap_test.cpp2
-rw-r--r--libs/androidfw/tests/PackageIdOverride_test.cpp60
-rw-r--r--libs/androidfw/tests/ThemesBags_test.cpp169
-rw-r--r--libs/androidfw/tests/ThemesIdmap_test.cpp133
-rw-r--r--libs/androidfw/tests/data/app/R.h9
-rw-r--r--libs/androidfw/tests/data/app/app_arsc.h101
-rw-r--r--libs/androidfw/tests/data/app/res/values/values.xml15
-rw-r--r--libs/androidfw/tests/data/bags/AndroidManifest.xml21
-rw-r--r--libs/androidfw/tests/data/bags/R.h26
-rw-r--r--libs/androidfw/tests/data/bags/bags_arsc.h88
-rwxr-xr-xlibs/androidfw/tests/data/bags/build6
-rw-r--r--libs/androidfw/tests/data/bags/res/values/values.xml24
-rw-r--r--libs/androidfw/tests/data/basic/R.h35
-rw-r--r--libs/androidfw/tests/data/basic/basic_arsc.h233
-rw-r--r--libs/androidfw/tests/data/basic/res/drawable/drawable1.pngbin0 -> 69 bytes
-rw-r--r--libs/androidfw/tests/data/basic/res/values/values.xml3
-rw-r--r--libs/androidfw/tests/data/basic/split_de_fr_arsc.h67
-rw-r--r--libs/androidfw/tests/data/feature/feature_arsc.h25
-rw-r--r--libs/androidfw/tests/data/lib/lib_arsc.h57
-rw-r--r--libs/androidfw/tests/data/overlay/overlay_arsc.h86
-rw-r--r--libs/androidfw/tests/data/overlay/res/drawable/drawable1.pngbin0 -> 69 bytes
-rw-r--r--libs/androidfw/tests/data/overlay/res/values/values.xml4
-rw-r--r--libs/androidfw/tests/data/override/AndroidManifest.xml21
-rw-r--r--libs/androidfw/tests/data/override/R.h16
-rwxr-xr-xlibs/androidfw/tests/data/override/build6
-rw-r--r--libs/androidfw/tests/data/override/override_arsc.h53
-rw-r--r--libs/androidfw/tests/data/override/res/values/values.xml4
-rw-r--r--libs/androidfw/tests/data/system/R.h7
-rw-r--r--libs/androidfw/tests/data/system/res/values/filler.xml168
-rw-r--r--libs/androidfw/tests/data/system/res/values/themes.xml8
-rw-r--r--libs/androidfw/tests/data/system/system_arsc.h525
-rw-r--r--packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java1
-rw-r--r--packages/SettingsProvider/res/values/defaults.xml3
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java64
-rw-r--r--packages/SystemUI/res/layout/msim_super_status_bar.xml6
-rw-r--r--packages/SystemUI/res/layout/super_status_bar.xml6
-rw-r--r--packages/SystemUI/res/values/cm_colors.xml36
-rw-r--r--packages/SystemUI/res/values/cm_dimens.xml28
-rwxr-xr-xpackages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java1
-rwxr-xr-xpackages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java64
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java275
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java2
-rwxr-xr-xpackages/SystemUI/src/com/android/systemui/statusbar/policy/MSimNetworkControllerImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java18
-rwxr-xr-xpolicy/src/com/android/internal/policy/impl/GlobalActions.java29
-rw-r--r--policy/src/com/android/internal/policy/impl/PhoneWindowManager.java5
-rw-r--r--services/core/java/com/android/server/ThemeService.java1161
-rwxr-xr-xservices/core/java/com/android/server/am/ActivityManagerService.java89
-rw-r--r--services/core/java/com/android/server/pm/Installer.java40
-rwxr-xr-xservices/core/java/com/android/server/pm/PackageManagerService.java781
-rwxr-xr-xservices/core/java/com/android/server/pm/Settings.java2
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java333
-rw-r--r--services/core/java/com/android/server/wm/Session.java28
-rw-r--r--services/core/java/com/android/server/wm/WindowAnimator.java3
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java33
-rw-r--r--services/java/com/android/server/AppsFailureReceiver.java116
-rw-r--r--services/java/com/android/server/SystemServer.java34
-rw-r--r--test-runner/src/android/test/mock/MockContext.java13
-rw-r--r--test-runner/src/android/test/mock/MockPackageManager.java36
-rwxr-xr-xtests/ThemeTests/cm11_to_cm12/downgrade_to_cm11.sh34
-rw-r--r--tests/ThemesTest/Android.mk19
-rw-r--r--tests/ThemesTest/AndroidManifest.xml21
-rw-r--r--tests/ThemesTest/res/drawable-hdpi/test_wallpaper_thumb.pngbin0 -> 34700 bytes
-rw-r--r--tests/ThemesTest/res/layout/activity_main.xml48
-rw-r--r--tests/ThemesTest/res/layout/theme_list_item.xml14
-rw-r--r--tests/ThemesTest/res/values-v11/styles.xml28
-rw-r--r--tests/ThemesTest/res/values-v21/styles.xml29
-rw-r--r--tests/ThemesTest/res/values/strings.xml22
-rw-r--r--tests/ThemesTest/res/values/styles.xml37
-rw-r--r--tests/ThemesTest/src/com/example/themetests/MainActivity.java326
-rw-r--r--tools/aapt/AaptAssets.cpp152
-rw-r--r--tools/aapt/AaptAssets.h21
-rw-r--r--tools/aapt/AaptConfig.cpp11
-rw-r--r--tools/aapt/Android.mk7
-rw-r--r--tools/aapt/Bundle.h17
-rw-r--r--tools/aapt/Command.cpp33
-rw-r--r--tools/aapt/Images.cpp83
-rw-r--r--tools/aapt/Images.h16
-rw-r--r--tools/aapt/Main.cpp47
-rw-r--r--tools/aapt/Main.h9
-rw-r--r--tools/aapt/OutputSet.h6
-rw-r--r--tools/aapt/Package.cpp310
-rw-r--r--tools/aapt/Resource.cpp24
-rw-r--r--tools/aapt/ResourceTable.cpp7
-rw-r--r--tools/aapt/ResourceTable.h3
-rw-r--r--tools/aapt/XMLNode.cpp43
-rw-r--r--tools/aapt/XMLNode.h3
-rw-r--r--tools/aapt/ZipEntry.h4
-rw-r--r--tools/aapt/ZipFile.cpp65
-rw-r--r--tools/aapt/ZipFile.h7
-rw-r--r--tools/aapt/tests/ZipReading_test.cpp156
-rw-r--r--tools/aapt/tests/mocks/MockZipEntry.h29
-rw-r--r--tools/aapt/tests/mocks/MockZipFile.h29
179 files changed, 13927 insertions, 909 deletions
diff --git a/Android.mk b/Android.mk
index d75542a0849..e583566629c 100755
--- a/Android.mk
+++ b/Android.mk
@@ -147,6 +147,9 @@ LOCAL_SRC_FILES += \
core/java/android/content/pm/IPackageManager.aidl \
core/java/android/content/pm/IPackageMoveObserver.aidl \
core/java/android/content/pm/IPackageStatsObserver.aidl \
+ core/java/android/content/res/IThemeChangeListener.aidl \
+ core/java/android/content/res/IThemeProcessingListener.aidl \
+ core/java/android/content/res/IThemeService.aidl \
core/java/android/database/IContentObserver.aidl \
core/java/android/hardware/ICameraService.aidl \
core/java/android/hardware/ICameraServiceListener.aidl \
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 28c2172294b..a10a6ebe9bd 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -228,3 +228,12 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framew
# ******************************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
# ******************************************************************
+
+# clean steps for aapt
+$(call add-clean-step, rm -rf $(OUT_DIR)/host/$(HOST_PREBUILT_TAG)/bin/aapt)
+$(call add-clean-step, rm -rf $(OUT_DIR)/host/$(HOST_PREBUILT_TAG)/obj32/EXECUTABLES/aapt_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/host/$(HOST_PREBUILT_TAG)/bin/libaapt_tests)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/EXECUTABLES/aapt_intermediates)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/bin/aapt)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/symbols/system/bin/aapt)
+$(call add-clean-step, rm -rf $(OUT_DIR)/host/$(HOST_PREBUILT_TAG)/obj32/STATIC_LIBRARIES/libaapt_intermediates)
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 6160b684149..a4b7206125d 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -62,10 +62,12 @@
#define OEM_BOOTANIMATION_FILE "/oem/media/bootanimation.zip"
#define SYSTEM_BOOTANIMATION_FILE "/system/media/bootanimation.zip"
#define SYSTEM_ENCRYPTED_BOOTANIMATION_FILE "/system/media/bootanimation-encrypted.zip"
+#define THEME_BOOTANIMATION_FILE "/data/system/theme/bootanimation.zip"
#define OEM_SHUTDOWN_ANIMATION_FILE "/oem/media/shutdownanimation.zip"
#define SYSTEM_SHUTDOWN_ANIMATION_FILE "/system/media/shutdownanimation.zip"
#define SYSTEM_ENCRYPTED_SHUTDOWN_ANIMATION_FILE "/system/media/shutdownanimation-encrypted.zip"
+#define THEME_SHUTDOWN_ANIMATION_FILE "/data/system/theme/shutdownanimation.zip"
#define OEM_BOOT_MUSIC_FILE "/oem/media/boot.wav"
#define SYSTEM_BOOT_MUSIC_FILE "/system/media/boot.wav"
@@ -393,6 +395,9 @@ status_t BootAnimation::readyToRun() {
((access(getAnimationFileName(IMG_DATA), R_OK) == 0) &&
((zipFile = ZipFileRO::open(getAnimationFileName(IMG_DATA))) != NULL)) ||
+ ((access(getAnimationFileName(IMG_THEME), R_OK) == 0) &&
+ ((zipFile = ZipFileRO::open(getAnimationFileName(IMG_THEME))) != NULL)) ||
+
((access(getAnimationFileName(IMG_SYS), R_OK) == 0) &&
((zipFile = ZipFileRO::open(getAnimationFileName(IMG_SYS))) != NULL))) {
mZip = zipFile;
@@ -828,12 +833,14 @@ bool BootAnimation::movie()
char *BootAnimation::getAnimationFileName(ImageID image)
{
- char *fileName[2][3] = { { OEM_BOOTANIMATION_FILE,
+ char *fileName[2][4] = { { OEM_BOOTANIMATION_FILE,
SYSTEM_BOOTANIMATION_FILE,
- SYSTEM_ENCRYPTED_BOOTANIMATION_FILE }, {
+ SYSTEM_ENCRYPTED_BOOTANIMATION_FILE,
+ THEME_BOOTANIMATION_FILE }, {
OEM_SHUTDOWN_ANIMATION_FILE,
SYSTEM_SHUTDOWN_ANIMATION_FILE,
- SYSTEM_ENCRYPTED_SHUTDOWN_ANIMATION_FILE} };
+ SYSTEM_ENCRYPTED_SHUTDOWN_ANIMATION_FILE,
+ THEME_SHUTDOWN_ANIMATION_FILE } };
int state;
state = checkBootState() ? 0 : 1;
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index fa99abe6a69..eff81333b01 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -87,7 +87,7 @@ private:
bool readFile(const char* name, String8& outString);
bool movie();
- enum ImageID { IMG_DATA = 0, IMG_SYS = 1, IMG_ENC = 2 };
+ enum ImageID { IMG_DATA = 0, IMG_SYS = 1, IMG_ENC = 2, IMG_THEME = 3 };
char *getAnimationFileName(ImageID image);
char *getBootRingtoneFileName(ImageID image);
void playBackgroundMusic();
diff --git a/cmds/idmap/create.cpp b/cmds/idmap/create.cpp
index 7d734b434c6..35793aca42e 100644
--- a/cmds/idmap/create.cpp
+++ b/cmds/idmap/create.cpp
@@ -160,26 +160,26 @@ fail:
}
int create_idmap(const char *target_apk_path, const char *overlay_apk_path,
- uint32_t **data, size_t *size)
+ const char *cache_path, uint32_t target_hash, uint32_t overlay_hash, uint32_t **data,
+ size_t *size)
{
uint32_t target_crc, overlay_crc;
- if (get_zip_entry_crc(target_apk_path, AssetManager::RESOURCES_FILENAME,
- &target_crc) == -1) {
- return -1;
- }
- if (get_zip_entry_crc(overlay_apk_path, AssetManager::RESOURCES_FILENAME,
- &overlay_crc) == -1) {
- return -1;
- }
+
+ // In the original implementation, crc of the res tables are generated
+ // theme apks however do not need a restable, everything is in assets/
+ // instead timestamps are used
+ target_crc = 0;
+ overlay_crc = 0;
AssetManager am;
- bool b = am.createIdmap(target_apk_path, overlay_apk_path, target_crc, overlay_crc,
- data, size);
+ bool b = am.createIdmap(target_apk_path, overlay_apk_path, cache_path, target_crc,
+ overlay_crc, target_hash, overlay_hash, data, size);
return b ? 0 : -1;
}
int create_and_write_idmap(const char *target_apk_path, const char *overlay_apk_path,
- int fd, bool check_if_stale)
+ const char *cache_path, uint32_t target_hash, uint32_t overlay_hash, int fd,
+ bool check_if_stale)
{
if (check_if_stale) {
if (!is_idmap_stale_fd(target_apk_path, overlay_apk_path, fd)) {
@@ -191,7 +191,8 @@ fail:
uint32_t *data = NULL;
size_t size;
- if (create_idmap(target_apk_path, overlay_apk_path, &data, &size) == -1) {
+ if (create_idmap(target_apk_path, overlay_apk_path, cache_path, target_hash, overlay_hash,
+ &data, &size) == -1) {
return -1;
}
@@ -206,6 +207,7 @@ fail:
}
int idmap_create_path(const char *target_apk_path, const char *overlay_apk_path,
+ const char *cache_path, uint32_t target_hash, uint32_t overlay_hash,
const char *idmap_path)
{
if (!is_idmap_stale_path(target_apk_path, overlay_apk_path, idmap_path)) {
@@ -218,7 +220,8 @@ int idmap_create_path(const char *target_apk_path, const char *overlay_apk_path,
return EXIT_FAILURE;
}
- int r = create_and_write_idmap(target_apk_path, overlay_apk_path, fd, false);
+ int r = create_and_write_idmap(target_apk_path, overlay_apk_path, cache_path,
+ target_hash, overlay_hash, fd, false);
close(fd);
if (r != 0) {
unlink(idmap_path);
@@ -226,8 +229,10 @@ int idmap_create_path(const char *target_apk_path, const char *overlay_apk_path,
return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
-int idmap_create_fd(const char *target_apk_path, const char *overlay_apk_path, int fd)
+int idmap_create_fd(const char *target_apk_path, const char *overlay_apk_path,
+ const char *cache_path, uint32_t target_hash, uint32_t overlay_hash, int fd)
{
- return create_and_write_idmap(target_apk_path, overlay_apk_path, fd, true) == 0 ?
+ return create_and_write_idmap(target_apk_path, overlay_apk_path, cache_path, target_hash,
+ overlay_hash, fd, true) == 0 ?
EXIT_SUCCESS : EXIT_FAILURE;
}
diff --git a/cmds/idmap/idmap.cpp b/cmds/idmap/idmap.cpp
index 90cfa2c610f..cc3f231824a 100644
--- a/cmds/idmap/idmap.cpp
+++ b/cmds/idmap/idmap.cpp
@@ -66,29 +66,31 @@ EXAMPLES \n\
Display an idmap file: \n\
\n\
$ adb shell idmap --inspect /data/resource-cache/vendor@overlay@overlay.apk@idmap \n\
- SECTION ENTRY VALUE COMMENT \n\
- IDMAP HEADER magic 0x706d6469 \n\
- base crc 0xb65a383f \n\
- overlay crc 0x7b9675e8 \n\
- base path .......... /path/to/target.apk \n\
- overlay path .......... /path/to/overlay.apk \n\
- DATA HEADER target pkg 0x0000007f \n\
- types count 0x00000003 \n\
- DATA BLOCK target type 0x00000002 \n\
- overlay type 0x00000002 \n\
- entry count 0x00000001 \n\
- entry offset 0x00000000 \n\
- entry 0x00000000 drawable/drawable \n\
- DATA BLOCK target type 0x00000003 \n\
- overlay type 0x00000003 \n\
- entry count 0x00000001 \n\
- entry offset 0x00000000 \n\
- entry 0x00000000 xml/integer \n\
- DATA BLOCK target type 0x00000004 \n\
- overlay type 0x00000004 \n\
- entry count 0x00000001 \n\
- entry offset 0x00000000 \n\
- entry 0x00000000 raw/lorem_ipsum \n\
+ SECTION ENTRY VALUE COMMENT \n\
+ IDMAP HEADER magic 0x706d6469 \n\
+ base crc 0xb65a383f \n\
+ overlay crc 0x7b9675e8 \n\
+ base mtime 0x1eb47d51 \n\
+ overlay mtime 0x185f87a2 \n\
+ base path .......... /path/to/target.apk \n\
+ overlay path .......... /path/to/overlay.apk \n\
+ DATA HEADER target pkg 0x0000007f \n\
+ types count 0x00000003 \n\
+ DATA BLOCK target type 0x00000002 \n\
+ overlay type 0x00000002 \n\
+ entry count 0x00000001 \n\
+ entry offset 0x00000000 \n\
+ entry 0x00000000 drawable/drawable \n\
+ DATA BLOCK target type 0x00000003 \n\
+ overlay type 0x00000003 \n\
+ entry count 0x00000001 \n\
+ entry offset 0x00000000 \n\
+ entry 0x00000000 xml/integer \n\
+ DATA BLOCK target type 0x00000004 \n\
+ overlay type 0x00000004 \n\
+ entry count 0x00000001 \n\
+ entry offset 0x00000000 \n\
+ entry 0x00000000 raw/lorem_ipsum \n\
\n\
In this example, the overlay package provides three alternative resource values:\n\
drawable/drawable, xml/integer, and raw/lorem_ipsum \n\
@@ -120,7 +122,8 @@ NOTES \n\
}
int maybe_create_fd(const char *target_apk_path, const char *overlay_apk_path,
- const char *idmap_str)
+ const char *cache_path, const char *idmap_str, const char *target_hash_str,
+ const char *overlay_hash_str)
{
// anyone (not just root or system) may do --fd -- the file has
// already been opened by someone else on our behalf
@@ -141,12 +144,16 @@ NOTES \n\
ALOGD("error: failed to read apk %s: %s\n", overlay_apk_path, strerror(errno));
return -1;
}
+ int target_hash = strtol(target_hash_str, 0, 10);
+ int overlay_hash = strtol(overlay_hash_str, 0, 10);
- return idmap_create_fd(target_apk_path, overlay_apk_path, idmap_fd);
+ return idmap_create_fd(target_apk_path, overlay_apk_path, cache_path, target_hash,
+ overlay_hash, idmap_fd);
}
int maybe_create_path(const char *target_apk_path, const char *overlay_apk_path,
- const char *idmap_path)
+ const char *cache_path, const char *idmap_path, const char *target_hash_str,
+ const char *overlay_hash_str)
{
if (!verify_root_or_system()) {
fprintf(stderr, "error: permission denied: not user root or user system\n");
@@ -163,7 +170,10 @@ NOTES \n\
return -1;
}
- return idmap_create_path(target_apk_path, overlay_apk_path, idmap_path);
+ int target_hash = strtol(target_hash_str, 0, 10);
+ int overlay_hash = strtol(overlay_hash_str, 0, 10);
+ return idmap_create_path(target_apk_path, overlay_apk_path, cache_path, target_hash,
+ overlay_hash, idmap_path);
}
int maybe_scan(const char *overlay_dir, const char *target_package_name,
@@ -222,12 +232,12 @@ int main(int argc, char **argv)
return 0;
}
- if (argc == 5 && !strcmp(argv[1], "--fd")) {
- return maybe_create_fd(argv[2], argv[3], argv[4]);
+ if (argc == 8 && !strcmp(argv[1], "--fd")) {
+ return maybe_create_fd(argv[2], argv[3], argv[4], argv[5], argv[6], argv[7]);
}
- if (argc == 5 && !strcmp(argv[1], "--path")) {
- return maybe_create_path(argv[2], argv[3], argv[4]);
+ if (argc == 8 && !strcmp(argv[1], "--path")) {
+ return maybe_create_path(argv[2], argv[3], argv[4], argv[5], argv[6], argv[7]);
}
if (argc == 6 && !strcmp(argv[1], "--scan")) {
diff --git a/cmds/idmap/idmap.h b/cmds/idmap/idmap.h
index f507dd8530a..6a9c5ef7881 100644
--- a/cmds/idmap/idmap.h
+++ b/cmds/idmap/idmap.h
@@ -19,9 +19,12 @@
#endif
int idmap_create_path(const char *target_apk_path, const char *overlay_apk_path,
+ const char *cache_path, uint32_t target_hash, uint32_t overlay_hash,
const char *idmap_path);
-int idmap_create_fd(const char *target_apk_path, const char *overlay_apk_path, int fd);
+int idmap_create_fd(const char *target_apk_path, const char *overlay_apk_path,
+ const char *cache_path, uint32_t target_hash, uint32_t overlay_hash,
+ int fd);
// Regarding target_package_name: the idmap_scan implementation should
// be able to extract this from the manifest in target_apk_path,
diff --git a/cmds/idmap/inspect.cpp b/cmds/idmap/inspect.cpp
index 2691f7bfddd..520a100ce72 100644
--- a/cmds/idmap/inspect.cpp
+++ b/cmds/idmap/inspect.cpp
@@ -200,6 +200,18 @@ namespace {
}
print("", "overlay crc", i, "");
+ err = buf.nextUint32(&i);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ print("", "base mtime", i, "");
+
+ err = buf.nextUint32(&i);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ print("", "overlay mtime", i, "");
+
err = buf.nextPath(path);
if (err != NO_ERROR) {
// printe done from IdmapBuffer::nextPath
diff --git a/cmds/idmap/scan.cpp b/cmds/idmap/scan.cpp
index 51c12219d5e..5c2761e49f4 100644
--- a/cmds/idmap/scan.cpp
+++ b/cmds/idmap/scan.cpp
@@ -227,7 +227,7 @@ namespace {
idmap_path.appendPath(flatten_path(overlay_apk_path + 1));
idmap_path.append("@idmap");
- if (idmap_create_path(target_apk_path, overlay_apk_path,
+ if (idmap_create_path(target_apk_path, overlay_apk_path, NULL, 0, 0,
idmap_path.string()) != 0) {
ALOGE("error: failed to create idmap for target=%s overlay=%s idmap=%s\n",
target_apk_path, overlay_apk_path, idmap_path.string());
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 37e8aa4939c..01ea22918d0 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -38,6 +38,7 @@ import android.content.pm.ConfigurationInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Color;
@@ -56,6 +57,7 @@ import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Size;
import android.util.Slog;
+
import org.xmlpull.v1.XmlSerializer;
import java.io.FileDescriptor;
@@ -2206,6 +2208,16 @@ public class ActivityManager {
return null;
}
}
+ /**
+ * @hide
+ */
+ public Configuration getConfiguration() {
+ try {
+ return ActivityManagerNative.getDefault().getConfiguration();
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
/**
* Returns a list of application processes that are running on the device.
@@ -2786,4 +2798,17 @@ public class ActivityManager {
}
}
}
+
+ /**
+ * @throws SecurityException Throws SecurityException if the caller does
+ * not hold the {@link android.Manifest.permission#CHANGE_CONFIGURATION} permission.
+ *
+ * @hide
+ */
+ public void updateConfiguration(Configuration values) throws SecurityException {
+ try {
+ ActivityManagerNative.getDefault().updateConfiguration(values);
+ } catch (RemoteException e) {
+ }
+ }
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index dd49009281f..3ee7621ec62 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -43,6 +43,7 @@ import android.database.sqlite.SQLiteDebug;
import android.database.sqlite.SQLiteDebug.DbStats;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Typeface;
import android.hardware.display.DisplayManagerGlobal;
import android.net.IConnectivityManager;
import android.net.Proxy;
@@ -71,6 +72,7 @@ import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
@@ -83,6 +85,7 @@ import android.util.Slog;
import android.util.SuperNotCalledException;
import android.view.Display;
import android.view.HardwareRenderer;
+import android.view.InflateException;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewManager;
@@ -1621,9 +1624,19 @@ public final class ActivityThread {
*/
Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
String[] libDirs, int displayId, Configuration overrideConfiguration,
- LoadedApk pkgInfo) {
+ LoadedApk pkgInfo, Context context, String pkgName) {
return mResourcesManager.getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs,
- displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo(), null);
+ displayId, pkgName, overrideConfiguration, pkgInfo.getCompatibilityInfo(), null,
+ context);
+ }
+
+ /**
+ * Creates the top level resources for the given package.
+ */
+ Resources getTopLevelThemedResources(String resDir, int displayId, LoadedApk pkgInfo,
+ String pkgName, String themePkgName) {
+ return mResourcesManager.getTopLevelThemedResources(resDir, displayId, pkgName,
+ themePkgName, pkgInfo.getCompatibilityInfo(), null);
}
final Handler getHandler() {
@@ -1731,8 +1744,7 @@ public final class ActivityThread {
ref = mResourcePackages.get(aInfo.packageName);
}
LoadedApk packageInfo = ref != null ? ref.get() : null;
- if (packageInfo == null || (packageInfo.mResources != null
- && !packageInfo.mResources.getAssets().isUpToDate())) {
+ if (packageInfo == null) {
if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
: "Loading resource-only package ") + aInfo.packageName
+ " (in " + (mBoundApplication != null
@@ -1750,6 +1762,10 @@ public final class ActivityThread {
new WeakReference<LoadedApk>(packageInfo));
}
}
+ if (packageInfo.mResources != null
+ && !packageInfo.mResources.getAssets().isUpToDate()) {
+ packageInfo.mResources = null;
+ }
return packageInfo;
}
}
@@ -4088,8 +4104,10 @@ public final class ActivityThread {
if (configDiff != 0) {
// Ask text layout engine to free its caches if there is a locale change
boolean hasLocaleConfigChange = ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0);
- if (hasLocaleConfigChange) {
+ boolean hasThemeConfigChange = ((configDiff & ActivityInfo.CONFIG_THEME_RESOURCE) != 0);
+ if (hasLocaleConfigChange || hasThemeConfigChange) {
Canvas.freeTextLayoutCaches();
+ Typeface.recreateDefaults();
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Cleared TextLayout Caches");
}
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 93cc521e3dc..05f915c153f 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -18,6 +18,7 @@ package android.app;
import android.content.ComponentName;
import android.content.ContentResolver;
+import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
@@ -925,12 +926,13 @@ final class ApplicationPackageManager extends PackageManager {
if (app.packageName.equals("system")) {
return mContext.mMainThread.getSystemContext().getResources();
}
+
final boolean sameUid = (app.uid == Process.myUid());
Resources r = mContext.mMainThread.getTopLevelResources(
sameUid ? app.sourceDir : app.publicSourceDir,
sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs,
app.resourceDirs, app.sharedLibraryFiles, Display.DEFAULT_DISPLAY,
- null, mContext.mPackageInfo);
+ null, mContext.mPackageInfo, mContext, app.packageName);
if (r != null) {
return r;
}
@@ -965,6 +967,48 @@ final class ApplicationPackageManager extends PackageManager {
throw new NameNotFoundException("Package " + appPackageName + " doesn't exist");
}
+ /** @hide */
+ @Override public Resources getThemedResourcesForApplication(
+ ApplicationInfo app, String themePkgName) throws NameNotFoundException {
+ if (app.packageName.equals("system")) {
+ return mContext.mMainThread.getSystemContext().getResources();
+ }
+
+ Resources r = mContext.mMainThread.getTopLevelThemedResources(
+ app.uid == Process.myUid() ? app.sourceDir : app.publicSourceDir,
+ Display.DEFAULT_DISPLAY, mContext.mPackageInfo, app.packageName, themePkgName);
+ if (r != null) {
+ return r;
+ }
+ throw new NameNotFoundException("Unable to open " + app.publicSourceDir);
+ }
+
+ /** @hide */
+ @Override public Resources getThemedResourcesForApplication(
+ String appPackageName, String themePkgName) throws NameNotFoundException {
+ return getThemedResourcesForApplication(
+ getApplicationInfo(appPackageName, 0), themePkgName);
+ }
+
+ /** @hide */
+ @Override
+ public Resources getThemedResourcesForApplicationAsUser(String appPackageName,
+ String themePackageName, int userId) throws NameNotFoundException {
+ if (userId < 0) {
+ throw new IllegalArgumentException(
+ "Call does not support special user #" + userId);
+ }
+ try {
+ ApplicationInfo ai = mPM.getApplicationInfo(appPackageName, 0, userId);
+ if (ai != null) {
+ return getThemedResourcesForApplication(ai, themePackageName);
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException("Package manager has died", e);
+ }
+ throw new NameNotFoundException("Package " + appPackageName + " doesn't exist");
+ }
+
int mCachedSafeMode = -1;
@Override public boolean isSafeMode() {
try {
@@ -1770,4 +1814,30 @@ final class ApplicationPackageManager extends PackageManager {
= new ArrayMap<ResourceName, WeakReference<Drawable.ConstantState>>();
private static ArrayMap<ResourceName, WeakReference<CharSequence>> sStringCache
= new ArrayMap<ResourceName, WeakReference<CharSequence>>();
+
+ /**
+ * @hide
+ */
+ @Override
+ public void updateIconMaps(String pkgName) {
+ try {
+ mPM.updateIconMapping(pkgName);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Failed to update icon maps", re);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public int processThemeResources(String themePkgName) {
+ try {
+ return mPM.processThemeResources(themePkgName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to process theme resources for " + themePkgName, e);
+ }
+
+ return 0;
+ }
}
diff --git a/core/java/android/app/ComposedIconInfo.aidl b/core/java/android/app/ComposedIconInfo.aidl
new file mode 100644
index 00000000000..8a1bab52d8c
--- /dev/null
+++ b/core/java/android/app/ComposedIconInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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 android.app;
+
+/** @hide */
+parcelable ComposedIconInfo;
diff --git a/core/java/android/app/ComposedIconInfo.java b/core/java/android/app/ComposedIconInfo.java
new file mode 100644
index 00000000000..7fab85241b1
--- /dev/null
+++ b/core/java/android/app/ComposedIconInfo.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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 android.app;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class ComposedIconInfo implements Parcelable {
+ public int iconUpon, iconMask;
+ public int[] iconBacks;
+ public float iconScale;
+ public int iconDensity;
+ public int iconSize;
+ public float[] colorFilter;
+
+ public ComposedIconInfo() {
+ super();
+ }
+
+ private ComposedIconInfo(Parcel source) {
+ iconScale = source.readFloat();
+ iconDensity = source.readInt();
+ iconSize = source.readInt();
+ int backCount = source.readInt();
+ if (backCount > 0) {
+ iconBacks = new int[backCount];
+ for (int i = 0; i < backCount; i++) {
+ iconBacks[i] = source.readInt();
+ }
+ }
+ iconMask = source.readInt();
+ iconUpon = source.readInt();
+ int colorFilterSize = source.readInt();
+ if (colorFilterSize > 0) {
+ colorFilter = new float[colorFilterSize];
+ for (int i = 0; i < colorFilterSize; i++) {
+ colorFilter[i] = source.readFloat();
+ }
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeFloat(iconScale);
+ dest.writeInt(iconDensity);
+ dest.writeInt(iconSize);
+ dest.writeInt(iconBacks != null ? iconBacks.length : 0);
+ if (iconBacks != null) {
+ for (int resId : iconBacks) {
+ dest.writeInt(resId);
+ }
+ }
+ dest.writeInt(iconMask);
+ dest.writeInt(iconUpon);
+ if (colorFilter != null) {
+ dest.writeInt(colorFilter.length);
+ for (float val : colorFilter) {
+ dest.writeFloat(val);
+ }
+ } else {
+ dest.writeInt(0);
+ }
+ }
+
+ public static final Creator<ComposedIconInfo> CREATOR
+ = new Creator<ComposedIconInfo>() {
+ @Override
+ public ComposedIconInfo createFromParcel(Parcel source) {
+ return new ComposedIconInfo(source);
+ }
+
+ @Override
+ public ComposedIconInfo[] newArray(int size) {
+ return new ComposedIconInfo[0];
+ }
+ };
+}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index d607f81adec..3539b7ffe7c 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -19,6 +19,8 @@ package android.app;
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageStatsManager;
import android.appwidget.AppWidgetManager;
+import android.content.res.IThemeService;
+import android.content.res.ThemeManager;
import android.os.Build;
import android.service.persistentdata.IPersistentDataBlockService;
@@ -769,6 +771,14 @@ class ContextImpl extends Context {
return new ProfileManager (outerContext, ctx.mMainThread.getHandler());
}
});
+
+ registerService(THEME_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(THEME_SERVICE);
+ IThemeService service = IThemeService.Stub.asInterface(b);
+ return new ThemeManager(ctx.getOuterContext(),
+ service);
+ }});
}
static ContextImpl getImpl(Context context) {
@@ -2100,13 +2110,19 @@ class ContextImpl extends Context {
@Override
public Context createApplicationContext(ApplicationInfo application, int flags)
throws NameNotFoundException {
+ return createApplicationContext(application, null, flags);
+ }
+
+ @Override
+ public Context createApplicationContext(ApplicationInfo application, String themePackageName,
+ int flags) throws NameNotFoundException {
LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(),
flags | CONTEXT_REGISTER_PACKAGE);
if (pi != null) {
final boolean restricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED;
ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
new UserHandle(UserHandle.getUserId(application.uid)), restricted,
- mDisplay, mOverrideConfiguration);
+ mDisplay, mOverrideConfiguration, themePackageName);
if (c.mResources != null) {
return c;
}
@@ -2119,24 +2135,30 @@ class ContextImpl extends Context {
@Override
public Context createPackageContext(String packageName, int flags)
throws NameNotFoundException {
- return createPackageContextAsUser(packageName, flags,
+ return createPackageContextAsUser(packageName, null, flags,
mUser != null ? mUser : Process.myUserHandle());
}
@Override
public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
throws NameNotFoundException {
+ return createPackageContextAsUser(packageName, null, flags, user);
+ }
+
+ @Override
+ public Context createPackageContextAsUser(String packageName, String themePackageName,
+ int flags, UserHandle user) throws NameNotFoundException {
final boolean restricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED;
if (packageName.equals("system") || packageName.equals("android")) {
return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
- user, restricted, mDisplay, mOverrideConfiguration);
+ user, restricted, mDisplay, mOverrideConfiguration, themePackageName);
}
LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(),
flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier());
if (pi != null) {
ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
- user, restricted, mDisplay, mOverrideConfiguration);
+ user, restricted, mDisplay, mOverrideConfiguration, themePackageName);
if (c.mResources != null) {
return c;
}
@@ -2208,7 +2230,7 @@ class ContextImpl extends Context {
static ContextImpl createSystemContext(ActivityThread mainThread) {
LoadedApk packageInfo = new LoadedApk(mainThread);
ContextImpl context = new ContextImpl(null, mainThread,
- packageInfo, null, null, false, null, null);
+ packageInfo, null, null, false, null, null, null);
context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
context.mResourcesManager.getDisplayMetricsLocked(Display.DEFAULT_DISPLAY));
return context;
@@ -2217,7 +2239,7 @@ class ContextImpl extends Context {
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
return new ContextImpl(null, mainThread,
- packageInfo, null, null, false, null, null);
+ packageInfo, null, null, false, null, null, null);
}
static ContextImpl createActivityContext(ActivityThread mainThread,
@@ -2225,12 +2247,19 @@ class ContextImpl extends Context {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
if (activityToken == null) throw new IllegalArgumentException("activityInfo");
return new ContextImpl(null, mainThread,
- packageInfo, activityToken, null, false, null, null);
+ packageInfo, activityToken, null, false, null, null, null);
}
private ContextImpl(ContextImpl container, ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
Display display, Configuration overrideConfiguration) {
+ this(container, mainThread, packageInfo, activityToken, user, restricted, display,
+ overrideConfiguration, null);
+ }
+
+ private ContextImpl(ContextImpl container, ActivityThread mainThread,
+ LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
+ Display display, Configuration overrideConfiguration, String themePackageName) {
mOuterContext = this;
mMainThread = mainThread;
@@ -2260,15 +2289,19 @@ class ContextImpl extends Context {
Resources resources = packageInfo.getResources(mainThread);
if (resources != null) {
- if (activityToken != null
+ if (activityToken != null || themePackageName != null
|| displayId != Display.DEFAULT_DISPLAY
|| overrideConfiguration != null
|| (compatInfo != null && compatInfo.applicationScale
!= resources.getCompatibilityInfo().applicationScale)) {
- resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(),
- packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(),
+ resources = themePackageName == null ? mResourcesManager.getTopLevelResources(
+ packageInfo.getResDir(), packageInfo.getSplitResDirs(),
+ packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles, displayId,
- overrideConfiguration, compatInfo, activityToken);
+ packageInfo.getAppDir(), overrideConfiguration, compatInfo, activityToken,
+ mOuterContext) :
+ mResourcesManager.getTopLevelThemedResources(packageInfo.getResDir(), displayId,
+ packageInfo.getPackageName(), themePackageName, compatInfo ,activityToken);
}
}
mResources = resources;
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 3b5900b4a5d..3117c2d4388 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -30,6 +30,12 @@ interface IWallpaperManager {
* Set the wallpaper.
*/
ParcelFileDescriptor setWallpaper(String name);
+
+ /**
+ * Set the keyguard wallpaper.
+ * @hide
+ */
+ ParcelFileDescriptor setKeyguardWallpaper(String name);
/**
* Set the live wallpaper.
@@ -41,6 +47,13 @@ interface IWallpaperManager {
*/
ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb,
out Bundle outParams);
+
+ /**
+ * Get the keyguard wallpaper.
+ * @hide
+ */
+ ParcelFileDescriptor getKeyguardWallpaper(IWallpaperManagerCallback cb,
+ out Bundle outParams);
/**
* Get information about a live wallpaper.
@@ -52,6 +65,12 @@ interface IWallpaperManager {
*/
void clearWallpaper();
+ /*
+ * Clear the keyguard wallpaper.
+ * @hide
+ */
+ void clearKeyguardWallpaper();
+
/**
* Return whether there is a wallpaper set with the given name.
*/
diff --git a/core/java/android/app/IWallpaperManagerCallback.aidl b/core/java/android/app/IWallpaperManagerCallback.aidl
index 991b2bc924b..b217318291d 100644
--- a/core/java/android/app/IWallpaperManagerCallback.aidl
+++ b/core/java/android/app/IWallpaperManagerCallback.aidl
@@ -28,4 +28,9 @@ oneway interface IWallpaperManagerCallback {
* Called when the wallpaper has changed
*/
void onWallpaperChanged();
+
+ /**
+ * Called when the keygaurd wallpaper has changed
+ */
+ void onKeyguardWallpaperChanged();
}
diff --git a/core/java/android/app/IconPackHelper.java b/core/java/android/app/IconPackHelper.java
new file mode 100644
index 00000000000..057633f89ee
--- /dev/null
+++ b/core/java/android/app/IconPackHelper.java
@@ -0,0 +1,848 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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 android.app;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+import android.content.pm.PackageInfo;
+import android.content.res.IThemeService;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.PaintDrawable;
+import android.graphics.drawable.VectorDrawable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.util.TypedValue;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ThemeUtils;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+
+/** @hide */
+public class IconPackHelper {
+ private static final String TAG = IconPackHelper.class.getSimpleName();
+ private static final String ICON_MASK_TAG = "iconmask";
+ private static final String ICON_BACK_TAG = "iconback";
+ private static final String ICON_UPON_TAG = "iconupon";
+ private static final String ICON_SCALE_TAG = "scale";
+ private static final String ICON_BACK_FORMAT = "iconback%d";
+
+ private static final ComponentName ICON_BACK_COMPONENT;
+ private static final ComponentName ICON_MASK_COMPONENT;
+ private static final ComponentName ICON_UPON_COMPONENT;
+ private static final ComponentName ICON_SCALE_COMPONENT;
+
+ private static final float DEFAULT_SCALE = 1.0f;
+ private static final int COMPOSED_ICON_COOKIE = 128;
+
+ private final Context mContext;
+ private Map<ComponentName, String> mIconPackResourceMap;
+ private String mLoadedIconPackName;
+ private Resources mLoadedIconPackResource;
+ private ComposedIconInfo mComposedIconInfo;
+ private int mIconBackCount = 0;
+ private ColorFilterUtils.Builder mFilterBuilder;
+
+ static {
+ ICON_BACK_COMPONENT = new ComponentName(ICON_BACK_TAG, "");
+ ICON_MASK_COMPONENT = new ComponentName(ICON_MASK_TAG, "");
+ ICON_UPON_COMPONENT = new ComponentName(ICON_UPON_TAG, "");
+ ICON_SCALE_COMPONENT = new ComponentName(ICON_SCALE_TAG, "");
+ }
+
+ public IconPackHelper(Context context) {
+ mContext = context;
+ mIconPackResourceMap = new HashMap<ComponentName, String>();
+ ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ mComposedIconInfo = new ComposedIconInfo();
+ mComposedIconInfo.iconSize = am.getLauncherLargeIconSize();
+ mComposedIconInfo.iconDensity = am.getLauncherLargeIconDensity();
+ mFilterBuilder = new ColorFilterUtils.Builder();
+ }
+
+ private void loadResourcesFromXmlParser(XmlPullParser parser,
+ Map<ComponentName, String> iconPackResources)
+ throws XmlPullParserException, IOException {
+ mIconBackCount = 0;
+ int eventType = parser.getEventType();
+ do {
+
+ if (eventType != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ if (parseComposedIconComponent(parser, iconPackResources)) {
+ continue;
+ }
+
+ if (ColorFilterUtils.parseIconFilter(parser, mFilterBuilder)) {
+ continue;
+ }
+
+ if (parser.getName().equalsIgnoreCase(ICON_SCALE_TAG)) {
+ String factor = parser.getAttributeValue(null, "factor");
+ if (factor == null) {
+ if (parser.getAttributeCount() == 1) {
+ factor = parser.getAttributeValue(0);
+ }
+ }
+ iconPackResources.put(ICON_SCALE_COMPONENT, factor);
+ continue;
+ }
+
+ if (!parser.getName().equalsIgnoreCase("item")) {
+ continue;
+ }
+
+ String component = parser.getAttributeValue(null, "component");
+ String drawable = parser.getAttributeValue(null, "drawable");
+
+ // Validate component/drawable exist
+ if (TextUtils.isEmpty(component) || TextUtils.isEmpty(drawable)) {
+ continue;
+ }
+
+ // Validate format/length of component
+ if (!component.startsWith("ComponentInfo{") || !component.endsWith("}")
+ || component.length() < 16 || drawable.length() == 0) {
+ continue;
+ }
+
+ // Sanitize stored value
+ component = component.substring(14, component.length() - 1).toLowerCase();
+
+ ComponentName name = null;
+ if (!component.contains("/")) {
+ // Package icon reference
+ name = new ComponentName(component.toLowerCase(), "");
+ } else {
+ name = ComponentName.unflattenFromString(component);
+ }
+
+ if (name != null) {
+ iconPackResources.put(name, drawable);
+ }
+ } while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT);
+ }
+
+ private boolean isComposedIconComponent(String tag) {
+ return tag.equalsIgnoreCase(ICON_MASK_TAG) ||
+ tag.equalsIgnoreCase(ICON_BACK_TAG) ||
+ tag.equalsIgnoreCase(ICON_UPON_TAG);
+ }
+
+ private boolean parseComposedIconComponent(XmlPullParser parser,
+ Map<ComponentName, String> iconPackResources) {
+ String icon;
+ String tag = parser.getName();
+ if (!isComposedIconComponent(tag)) {
+ return false;
+ }
+
+ if (parser.getAttributeCount() >= 1) {
+ if (tag.equalsIgnoreCase(ICON_BACK_TAG)) {
+ mIconBackCount = parser.getAttributeCount();
+ for (int i = 0; i < mIconBackCount; i++) {
+ tag = String.format(ICON_BACK_FORMAT, i);
+ icon = parser.getAttributeValue(i);
+ iconPackResources.put(new ComponentName(tag, ""), icon);
+ }
+ } else {
+ icon = parser.getAttributeValue(0);
+ iconPackResources.put(new ComponentName(tag, ""),
+ icon);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ public void loadIconPack(String packageName) throws NameNotFoundException {
+ if (packageName == null) {
+ mLoadedIconPackResource = null;
+ mLoadedIconPackName = null;
+ mComposedIconInfo.iconBacks = null;
+ mComposedIconInfo.iconMask = mComposedIconInfo.iconUpon = 0;
+ mComposedIconInfo.iconScale = 0;
+ mComposedIconInfo.colorFilter = null;
+ } else {
+ mIconBackCount = 0;
+ Resources res = createIconResource(mContext, packageName);
+ mIconPackResourceMap = getIconResMapFromXml(res, packageName);
+ mLoadedIconPackResource = res;
+ mLoadedIconPackName = packageName;
+ loadComposedIconComponents();
+ ColorMatrix cm = mFilterBuilder.build();
+ if (cm != null) {
+ mComposedIconInfo.colorFilter = cm.getArray().clone();
+ }
+ }
+ }
+
+ public ComposedIconInfo getComposedIconInfo() {
+ return mComposedIconInfo;
+ }
+
+ private void loadComposedIconComponents() {
+ mComposedIconInfo.iconMask = getResourceIdForName(ICON_MASK_COMPONENT);
+ mComposedIconInfo.iconUpon = getResourceIdForName(ICON_UPON_COMPONENT);
+
+ // Take care of loading iconback which can have multiple images
+ if (mIconBackCount > 0) {
+ mComposedIconInfo.iconBacks = new int[mIconBackCount];
+ for (int i = 0; i < mIconBackCount; i++) {
+ mComposedIconInfo.iconBacks[i] =
+ getResourceIdForName(
+ new ComponentName(String.format(ICON_BACK_FORMAT, i), ""));
+ }
+ }
+
+ // Get the icon scale from this pack
+ String scale = mIconPackResourceMap.get(ICON_SCALE_COMPONENT);
+ if (scale != null) {
+ try {
+ mComposedIconInfo.iconScale = Float.valueOf(scale);
+ } catch (NumberFormatException e) {
+ mComposedIconInfo.iconScale = DEFAULT_SCALE;
+ }
+ } else {
+ mComposedIconInfo.iconScale = DEFAULT_SCALE;
+ }
+ }
+
+ private int getResourceIdForName(ComponentName component) {
+ String item = mIconPackResourceMap.get(component);
+ if (!TextUtils.isEmpty(item)) {
+ return getResourceIdForDrawable(item);
+ }
+ return 0;
+ }
+
+ public static Resources createIconResource(Context context, String packageName)
+ throws NameNotFoundException {
+ PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
+ String themeApk = info.applicationInfo.publicSourceDir;
+
+ String prefixPath;
+ String iconApkPath;
+ String iconResPath;
+ if (info.isLegacyIconPackApk) {
+ iconResPath = "";
+ iconApkPath = "";
+ prefixPath = "";
+ } else {
+ prefixPath = ThemeUtils.ICONS_PATH; //path inside APK
+ iconApkPath = ThemeUtils.getIconPackApkPath(packageName);
+ iconResPath = ThemeUtils.getIconPackResPath(packageName);
+ }
+
+ AssetManager assets = new AssetManager();
+ assets.addIconPath(themeApk, iconApkPath,
+ prefixPath, Resources.THEME_ICON_PKG_ID);
+
+ DisplayMetrics dm = context.getResources().getDisplayMetrics();
+ Configuration config = context.getResources().getConfiguration();
+ Resources res = new Resources(assets, dm, config);
+ return res;
+ }
+
+ public Map<ComponentName, String> getIconResMapFromXml(Resources res, String packageName) {
+ XmlPullParser parser = null;
+ InputStream inputStream = null;
+ Map<ComponentName, String> iconPackResources = new HashMap<ComponentName, String>();
+
+ try {
+ inputStream = res.getAssets().open("appfilter.xml");
+ XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+ parser = factory.newPullParser();
+ parser.setInput(inputStream, "UTF-8");
+ } catch (Exception e) {
+ // Catch any exception since we want to fall back to parsing the xml/
+ // resource in all cases
+ int resId = res.getIdentifier("appfilter", "xml", packageName);
+ if (resId != 0) {
+ parser = res.getXml(resId);
+ }
+ }
+
+ if (parser != null) {
+ try {
+ loadResourcesFromXmlParser(parser, iconPackResources);
+ return iconPackResources;
+ } catch (XmlPullParserException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ // Cleanup resources
+ if (parser instanceof XmlResourceParser) {
+ ((XmlResourceParser) parser).close();
+ } else if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ // Application uses a different theme format (most likely launcher pro)
+ int arrayId = res.getIdentifier("theme_iconpack", "array", packageName);
+ if (arrayId == 0) {
+ arrayId = res.getIdentifier("icon_pack", "array", packageName);
+ }
+
+ if (arrayId != 0) {
+ String[] iconPack = res.getStringArray(arrayId);
+ ComponentName compName = null;
+ for (String entry : iconPack) {
+
+ if (TextUtils.isEmpty(entry)) {
+ continue;
+ }
+
+ String icon = entry;
+ entry = entry.replaceAll("_", ".");
+
+ compName = new ComponentName(entry.toLowerCase(), "");
+ iconPackResources.put(compName, icon);
+
+ int activityIndex = entry.lastIndexOf(".");
+ if (activityIndex <= 0 || activityIndex == entry.length() - 1) {
+ continue;
+ }
+
+ String iconPackage = entry.substring(0, activityIndex);
+ if (TextUtils.isEmpty(iconPackage)) {
+ continue;
+ }
+
+ String iconActivity = entry.substring(activityIndex + 1);
+ if (TextUtils.isEmpty(iconActivity)) {
+ continue;
+ }
+
+ // Store entries as lower case to ensure match
+ iconPackage = iconPackage.toLowerCase();
+ iconActivity = iconActivity.toLowerCase();
+
+ iconActivity = iconPackage + "." + iconActivity;
+ compName = new ComponentName(iconPackage, iconActivity);
+ iconPackResources.put(compName, icon);
+ }
+ }
+ return iconPackResources;
+ }
+
+ boolean isIconPackLoaded() {
+ return mLoadedIconPackResource != null &&
+ mLoadedIconPackName != null &&
+ mIconPackResourceMap != null;
+ }
+
+ private int getResourceIdForDrawable(String resource) {
+ int resId =
+ mLoadedIconPackResource.getIdentifier(resource, "drawable",mLoadedIconPackName);
+ return resId;
+ }
+
+ public int getResourceIdForActivityIcon(ActivityInfo info) {
+ if (!isIconPackLoaded()) {
+ return 0;
+ }
+ ComponentName compName = new ComponentName(info.packageName.toLowerCase(),
+ info.name.toLowerCase());
+ String drawable = mIconPackResourceMap.get(compName);
+ if (drawable != null) {
+ int resId = getResourceIdForDrawable(drawable);
+ if (resId != 0) return resId;
+ }
+
+ // Icon pack doesn't have an icon for the activity, fallback to package icon
+ compName = new ComponentName(info.packageName.toLowerCase(), "");
+ drawable = mIconPackResourceMap.get(compName);
+ if (drawable == null) {
+ return 0;
+ }
+ return getResourceIdForDrawable(drawable);
+ }
+
+ public int getResourceIdForApp(String pkgName) {
+ ActivityInfo info = new ActivityInfo();
+ info.packageName = pkgName;
+ info.name = "";
+ return getResourceIdForActivityIcon(info);
+ }
+
+ public Drawable getDrawableForActivity(ActivityInfo info) {
+ int id = getResourceIdForActivityIcon(info);
+ if (id == 0) return null;
+ return mLoadedIconPackResource.getDrawable(id, null, false);
+ }
+
+ public Drawable getDrawableForActivityWithDensity(ActivityInfo info, int density) {
+ int id = getResourceIdForActivityIcon(info);
+ if (id == 0) return null;
+ return mLoadedIconPackResource.getDrawableForDensity(id, density, null, false);
+ }
+
+ public static boolean shouldComposeIcon(ComposedIconInfo iconInfo) {
+ return iconInfo != null &&
+ (iconInfo.iconBacks != null || iconInfo.iconMask != 0 ||
+ iconInfo.iconUpon != 0 || iconInfo.colorFilter != null);
+ }
+
+ public static class IconCustomizer {
+ private static final Random sRandom = new Random();
+ private static final IThemeService sThemeService;
+
+ static {
+ sThemeService = IThemeService.Stub.asInterface(
+ ServiceManager.getService(Context.THEME_SERVICE));
+ }
+
+ public static Drawable getComposedIconDrawable(Drawable icon, Context context,
+ ComposedIconInfo iconInfo) {
+ final Resources res = context.getResources();
+ return getComposedIconDrawable(icon, res, iconInfo);
+ }
+
+ public static Drawable getComposedIconDrawable(Drawable icon, Resources res,
+ ComposedIconInfo iconInfo) {
+ if (iconInfo == null) return icon;
+ int back = 0;
+ if (iconInfo.iconBacks != null && iconInfo.iconBacks.length > 0) {
+ back = iconInfo.iconBacks[sRandom.nextInt(iconInfo.iconBacks.length)];
+ }
+ Bitmap bmp = createIconBitmap(icon, res, back, iconInfo.iconMask, iconInfo.iconUpon,
+ iconInfo.iconScale, iconInfo.iconSize, iconInfo.colorFilter);
+ return bmp != null ? new BitmapDrawable(res, bmp): null;
+ }
+
+ public static void getValue(Resources res, int resId, TypedValue outValue,
+ Drawable baseIcon) {
+ final String pkgName = res.getAssets().getAppName();
+ TypedValue tempValue = new TypedValue();
+ tempValue.setTo(outValue);
+ outValue.assetCookie = COMPOSED_ICON_COOKIE;
+ outValue.data = resId & (COMPOSED_ICON_COOKIE << 24 | 0x00ffffff);
+ outValue.string = getCachedIconPath(pkgName, resId, outValue.density);
+
+ if (!(new File(outValue.string.toString()).exists())) {
+ // compose the icon and cache it
+ final ComposedIconInfo iconInfo = res.getComposedIconInfo();
+ int back = 0;
+ if (iconInfo.iconBacks != null && iconInfo.iconBacks.length > 0) {
+ back = iconInfo.iconBacks[(outValue.string.hashCode() & 0x7fffffff)
+ % iconInfo.iconBacks.length];
+ }
+ Bitmap bmp = createIconBitmap(baseIcon, res, back, iconInfo.iconMask,
+ iconInfo.iconUpon, iconInfo.iconScale, iconInfo.iconSize,
+ iconInfo.colorFilter);
+ if (!cacheComposedIcon(bmp, getCachedIconName(pkgName, resId, outValue.density))) {
+ Log.w(TAG, "Unable to cache icon " + outValue.string);
+ // restore the original TypedValue
+ outValue.setTo(tempValue);
+ }
+ }
+ }
+
+ private static Bitmap createIconBitmap(Drawable icon, Resources res, int iconBack,
+ int iconMask, int iconUpon, float scale, int iconSize, float[] colorFilter) {
+ if (iconSize <= 0) return null;
+
+ final Canvas canvas = new Canvas();
+ canvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.ANTI_ALIAS_FLAG,
+ Paint.FILTER_BITMAP_FLAG));
+
+ int width = 0, height = 0;
+ if (icon instanceof PaintDrawable) {
+ PaintDrawable painter = (PaintDrawable) icon;
+ painter.setIntrinsicWidth(iconSize);
+ painter.setIntrinsicHeight(iconSize);
+
+ // A PaintDrawable does not have an exact size
+ width = iconSize;
+ height = iconSize;
+ } else if (icon instanceof BitmapDrawable) {
+ // Ensure the bitmap has a density.
+ BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
+ Bitmap bitmap = bitmapDrawable.getBitmap();
+ if (bitmap.getDensity() == Bitmap.DENSITY_NONE) {
+ bitmapDrawable.setTargetDensity(res.getDisplayMetrics());
+ }
+ canvas.setDensity(bitmap.getDensity());
+
+ // If the original size of the icon isn't greater
+ // than twice the size of recommended large icons
+ // respect the original size of the icon
+ // otherwise enormous icons can easily create
+ // OOM situations.
+ if ((bitmap.getWidth() < (iconSize * 2))
+ && (bitmap.getHeight() < (iconSize * 2))) {
+ width = bitmap.getWidth();
+ height = bitmap.getHeight();
+ } else {
+ width = iconSize;
+ height = iconSize;
+ }
+ } else if (icon instanceof VectorDrawable) {
+ width = height = iconSize;
+ }
+
+ if (width <= 0 || height <= 0) return null;
+
+ Bitmap bitmap = Bitmap.createBitmap(width, height,
+ Bitmap.Config.ARGB_8888);
+ canvas.setBitmap(bitmap);
+
+ // Scale the original
+ Rect oldBounds = new Rect();
+ oldBounds.set(icon.getBounds());
+ icon.setBounds(0, 0, width, height);
+ canvas.save();
+ canvas.scale(scale, scale, width / 2, height / 2);
+ if (colorFilter != null) {
+ Paint p = null;
+ if (icon instanceof BitmapDrawable) {
+ p = ((BitmapDrawable) icon).getPaint();
+ } else if (icon instanceof PaintDrawable) {
+ p = ((PaintDrawable) icon).getPaint();
+ }
+ if (p != null) p.setColorFilter(new ColorMatrixColorFilter(colorFilter));
+ }
+ icon.draw(canvas);
+ canvas.restore();
+
+ // Mask off the original if iconMask is not null
+ if (iconMask != 0) {
+ Drawable mask = res.getDrawable(iconMask);
+ if (mask != null) {
+ mask.setBounds(icon.getBounds());
+ ((BitmapDrawable) mask).getPaint().setXfermode(
+ new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+ mask.draw(canvas);
+ }
+ }
+ // Draw the iconBacks if not null and then the original (scaled and masked) icon on top
+ if (iconBack != 0) {
+ Drawable back = res.getDrawable(iconBack);
+ if (back != null) {
+ back.setBounds(icon.getBounds());
+ ((BitmapDrawable) back).getPaint().setXfermode(
+ new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));
+ back.draw(canvas);
+ }
+ }
+ // Finally draw the foreground if one was supplied
+ if (iconUpon != 0) {
+ Drawable upon = res.getDrawable(iconUpon);
+ if (upon != null) {
+ upon.setBounds(icon.getBounds());
+ upon.draw(canvas);
+ }
+ }
+ icon.setBounds(oldBounds);
+ bitmap.setDensity(canvas.getDensity());
+
+ return bitmap;
+ }
+
+ private static boolean cacheComposedIcon(Bitmap bmp, String path) {
+ try {
+ return sThemeService.cacheComposedIcon(bmp, path);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to cache icon.", e);
+ }
+
+ return false;
+ }
+
+ private static String getCachedIconPath(String pkgName, int resId, int density) {
+ return String.format("%s/%s", ThemeUtils.SYSTEM_THEME_ICON_CACHE_DIR,
+ getCachedIconName(pkgName, resId, density));
+ }
+
+ private static String getCachedIconName(String pkgName, int resId, int density) {
+ return String.format("%s_%08x_%d.png", pkgName, resId, density);
+ }
+ }
+
+ public static class ColorFilterUtils {
+ private static final String TAG_FILTER = "filter";
+ private static final String FILTER_HUE = "hue";
+ private static final String FILTER_SATURATION = "saturation";
+ private static final String FILTER_INVERT = "invert";
+ private static final String FILTER_BRIGHTNESS = "brightness";
+ private static final String FILTER_CONTRAST = "contrast";
+ private static final String FILTER_ALPHA = "alpha";
+ private static final String FILTER_TINT = "tint";
+
+ private static final int MIN_HUE = -180;
+ private static final int MAX_HUE = 180;
+ private static final int MIN_SATURATION = 0;
+ private static final int MAX_SATURATION = 200;
+ private static final int MIN_BRIGHTNESS = 0;
+ private static final int MAX_BRIGHTNESS = 200;
+ private static final int MIN_CONTRAST = -100;
+ private static final int MAX_CONTRAST = 100;
+ private static final int MIN_ALPHA = 0;
+ private static final int MAX_ALPHA = 100;
+
+ public static boolean parseIconFilter(XmlPullParser parser, Builder builder)
+ throws IOException, XmlPullParserException {
+ String tag = parser.getName();
+ if (!TAG_FILTER.equals(tag)) return false;
+
+ int attrCount = parser.getAttributeCount();
+ String attrName;
+ String attr = null;
+ int intValue;
+ while (attrCount-- > 0) {
+ attrName = parser.getAttributeName(attrCount);
+ if (attrName.equals("name")) {
+ attr = parser.getAttributeValue(attrCount);
+ }
+ }
+ String content = parser.nextText();
+ if (attr != null && content != null && content.length() > 0) {
+ content = content.trim();
+ if (FILTER_HUE.equalsIgnoreCase(attr)) {
+ intValue = clampValue(getInt(content, 0),MIN_HUE, MAX_HUE);
+ builder.hue(intValue);
+ } else if (FILTER_SATURATION.equalsIgnoreCase(attr)) {
+ intValue = clampValue(getInt(content, 100),
+ MIN_SATURATION, MAX_SATURATION);
+ builder.saturate(intValue);
+ } else if (FILTER_INVERT.equalsIgnoreCase(attr)) {
+ if ("true".equalsIgnoreCase(content)) {
+ builder.invertColors();
+ }
+ } else if (FILTER_BRIGHTNESS.equalsIgnoreCase(attr)) {
+ intValue = clampValue(getInt(content, 100),
+ MIN_BRIGHTNESS, MAX_BRIGHTNESS);
+ builder.brightness(intValue);
+ } else if (FILTER_CONTRAST.equalsIgnoreCase(attr)) {
+ intValue = clampValue(getInt(content, 0),
+ MIN_CONTRAST, MAX_CONTRAST);
+ builder.contrast(intValue);
+ } else if (FILTER_ALPHA.equalsIgnoreCase(attr)) {
+ intValue = clampValue(getInt(content, 100), MIN_ALPHA, MAX_ALPHA);
+ builder.alpha(intValue);
+ } else if (FILTER_TINT.equalsIgnoreCase(attr)) {
+ try {
+ intValue = Color.parseColor(content);
+ builder.tint(intValue);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Cannot apply tint, invalid argument: " + content);
+ }
+ }
+ }
+ return true;
+ }
+
+ private static int getInt(String value, int defaultValue) {
+ try {
+ return Integer.valueOf(value);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+
+ private static int clampValue(int value, int min, int max) {
+ return Math.min(max, Math.max(min, value));
+ }
+
+ /**
+ * See the following links for reference
+ * http://groups.google.com/group/android-developers/browse_thread/thread/9e215c83c3819953
+ * http://gskinner.com/blog/archives/2007/12/colormatrix_cla.html
+ * @param value
+ */
+ public static ColorMatrix adjustHue(float value) {
+ ColorMatrix cm = new ColorMatrix();
+ value = value / 180 * (float) Math.PI;
+ if (value != 0) {
+ float cosVal = (float) Math.cos(value);
+ float sinVal = (float) Math.sin(value);
+ float lumR = 0.213f;
+ float lumG = 0.715f;
+ float lumB = 0.072f;
+ float[] mat = new float[]{
+ lumR + cosVal * (1 - lumR) + sinVal * (-lumR),
+ lumG + cosVal * (-lumG) + sinVal * (-lumG),
+ lumB + cosVal * (-lumB) + sinVal * (1 - lumB), 0, 0,
+ lumR + cosVal * (-lumR) + sinVal * (0.143f),
+ lumG + cosVal * (1 - lumG) + sinVal * (0.140f),
+ lumB + cosVal * (-lumB) + sinVal * (-0.283f), 0, 0,
+ lumR + cosVal * (-lumR) + sinVal * (-(1 - lumR)),
+ lumG + cosVal * (-lumG) + sinVal * (lumG),
+ lumB + cosVal * (1 - lumB) + sinVal * (lumB), 0, 0,
+ 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 1};
+ cm.set(mat);
+ }
+ return cm;
+ }
+
+ public static ColorMatrix adjustSaturation(float saturation) {
+ saturation = saturation / 100;
+ ColorMatrix cm = new ColorMatrix();
+ cm.setSaturation(saturation);
+
+ return cm;
+ }
+
+ public static ColorMatrix invertColors() {
+ float[] matrix = {
+ -1, 0, 0, 0, 255, //red
+ 0, -1, 0, 0, 255, //green
+ 0, 0, -1, 0, 255, //blue
+ 0, 0, 0, 1, 0 //alpha
+ };
+
+ return new ColorMatrix(matrix);
+ }
+
+ public static ColorMatrix adjustBrightness(float brightness) {
+ brightness = brightness / 100;
+ ColorMatrix cm = new ColorMatrix();
+ cm.setScale(brightness, brightness, brightness, 1);
+
+ return cm;
+ }
+
+ public static ColorMatrix adjustContrast(float contrast) {
+ contrast = contrast / 100 + 1;
+ float o = (-0.5f * contrast + 0.5f) * 255;
+ float[] matrix = {
+ contrast, 0, 0, 0, o, //red
+ 0, contrast, 0, 0, o, //green
+ 0, 0, contrast, 0, o, //blue
+ 0, 0, 0, 1, 0 //alpha
+ };
+
+ return new ColorMatrix(matrix);
+ }
+
+ public static ColorMatrix adjustAlpha(float alpha) {
+ alpha = alpha / 100;
+ ColorMatrix cm = new ColorMatrix();
+ cm.setScale(1, 1, 1, alpha);
+
+ return cm;
+ }
+
+ public static ColorMatrix applyTint(int color) {
+ float alpha = Color.alpha(color) / 255f;
+ float red = Color.red(color) * alpha;
+ float green = Color.green(color) * alpha;
+ float blue = Color.blue(color) * alpha;
+
+ float[] matrix = {
+ 1, 0, 0, 0, red, //red
+ 0, 1, 0, 0, green, //green
+ 0, 0, 1, 0, blue, //blue
+ 0, 0, 0, 1, 0 //alpha
+ };
+
+ return new ColorMatrix(matrix);
+ }
+
+ public static class Builder {
+ private List<ColorMatrix> mMatrixList;
+
+ public Builder() {
+ mMatrixList = new ArrayList<ColorMatrix>();
+ }
+
+ public Builder hue(float value) {
+ mMatrixList.add(adjustHue(value));
+ return this;
+ }
+
+ public Builder saturate(float saturation) {
+ mMatrixList.add(adjustSaturation(saturation));
+ return this;
+ }
+
+ public Builder brightness(float brightness) {
+ mMatrixList.add(adjustBrightness(brightness));
+ return this;
+ }
+
+ public Builder contrast(float contrast) {
+ mMatrixList.add(adjustContrast(contrast));
+ return this;
+ }
+
+ public Builder alpha(float alpha) {
+ mMatrixList.add(adjustAlpha(alpha));
+ return this;
+ }
+
+ public Builder invertColors() {
+ mMatrixList.add(ColorFilterUtils.invertColors());
+ return this;
+ }
+
+ public Builder tint(int color) {
+ mMatrixList.add(applyTint(color));
+ return this;
+ }
+
+ public ColorMatrix build() {
+ if (mMatrixList == null || mMatrixList.size() == 0) return null;
+
+ ColorMatrix colorMatrix = new ColorMatrix();
+ for (ColorMatrix cm : mMatrixList) {
+ colorMatrix.postConcat(cm);
+ }
+ return colorMatrix;
+ }
+ }
+ }
+}
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index aa98e973855..c8aa72053eb 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -531,7 +531,8 @@ public final class LoadedApk {
public Resources getResources(ActivityThread mainThread) {
if (mResources == null) {
mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
- mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this);
+ mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this,
+ mainThread.getSystemContext(), mPackageName);
}
return mResources;
}
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 1691d8e28f5..26f9887458e 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -18,21 +18,36 @@ package android.app;
import static android.app.ActivityThread.DEBUG_CONFIGURATION;
+import android.content.Context;
import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ThemeUtils;
import android.content.res.AssetManager;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
+import android.content.res.ThemeConfig;
import android.content.res.Resources;
import android.content.res.ResourcesKey;
import android.hardware.display.DisplayManagerGlobal;
import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayAdjustments;
import java.lang.ref.WeakReference;
+import java.util.List;
import java.util.Locale;
/** @hide */
@@ -49,6 +64,7 @@ public class ResourcesManager {
= new ArrayMap<DisplayAdjustments, DisplayMetrics>();
CompatibilityInfo mResCompatibilityInfo;
+ static IPackageManager sPackageManager;
Configuration mResConfiguration;
final Configuration mTmpConfig = new Configuration();
@@ -150,10 +166,13 @@ public class ResourcesManager {
* @param token the application token for determining stack bounds.
*/
public Resources getTopLevelResources(String resDir, String[] splitResDirs,
- String[] overlayDirs, String[] libDirs, int displayId,
- Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
+ String[] overlayDirs, String[] libDirs, int displayId, String packageName,
+ Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token,
+ Context context) {
final float scale = compatInfo.applicationScale;
- ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale, token);
+ final boolean isThemeable = compatInfo.isThemeable;
+ ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale,
+ isThemeable, token);
Resources r;
synchronized (this) {
// Resources is app scale dependent.
@@ -178,6 +197,8 @@ public class ResourcesManager {
//}
AssetManager assets = new AssetManager();
+ assets.setAppName(packageName);
+ assets.setThemeSupport(compatInfo.isThemeable);
// resDir can be null if the 'android' package is creating a new Resources object.
// This is fine, since each AssetManager automatically loads the 'android' package
// already.
@@ -197,7 +218,7 @@ public class ResourcesManager {
if (overlayDirs != null) {
for (String idmapPath : overlayDirs) {
- assets.addOverlayPath(idmapPath);
+ assets.addOverlayPath(idmapPath, null, null, null, null);
}
}
@@ -226,7 +247,29 @@ public class ResourcesManager {
} else {
config = getConfiguration();
}
+
+ boolean iconsAttached = false;
+ /* Attach theme information to the resulting AssetManager when appropriate. */
+ if (compatInfo.isThemeable && config != null && !context.getPackageManager().isSafeMode()) {
+ if (config.themeConfig == null) {
+ try {
+ config.themeConfig = ThemeConfig.getBootTheme(context.getContentResolver());
+ } catch (Exception e) {
+ Slog.d(TAG, "ThemeConfig.getBootTheme failed, falling back to system theme", e);
+ config.themeConfig = ThemeConfig.getSystemTheme();
+ }
+ }
+
+ if (config.themeConfig != null) {
+ attachThemeAssets(assets, config.themeConfig);
+ attachCommonAssets(assets, config.themeConfig);
+ iconsAttached = attachIconAssets(assets, config.themeConfig);
+ }
+ }
+
r = new Resources(assets, dm, config, compatInfo, token);
+ if (iconsAttached) setActivityIcons(r);
+
if (false) {
Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
+ r.getConfiguration() + " appScale="
@@ -249,6 +292,115 @@ public class ResourcesManager {
}
}
+ /**
+ * Creates the top level Resources for applications with the given compatibility info.
+ *
+ * @param resDir the resource directory.
+ * @param compatInfo the compability info. Must not be null.
+ * @param token the application token for determining stack bounds.
+ *
+ * @hide
+ */
+ public Resources getTopLevelThemedResources(String resDir, int displayId,
+ String packageName,
+ String themePackageName,
+ CompatibilityInfo compatInfo, IBinder token) {
+ Resources r;
+
+ AssetManager assets = new AssetManager();
+ assets.setAppName(packageName);
+ assets.setThemeSupport(true);
+ if (assets.addAssetPath(resDir) == 0) {
+ return null;
+ }
+
+ //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
+ DisplayMetrics dm = getDisplayMetricsLocked(displayId);
+ Configuration config;
+ boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
+ if (!isDefaultDisplay) {
+ config = new Configuration(getConfiguration());
+ applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);
+ } else {
+ config = getConfiguration();
+ }
+
+ /* Attach theme information to the resulting AssetManager when appropriate. */
+ ThemeConfig.Builder builder = new ThemeConfig.Builder();
+ builder.defaultOverlay(themePackageName);
+ builder.defaultIcon(themePackageName);
+ builder.defaultFont(themePackageName);
+
+ ThemeConfig themeConfig = builder.build();
+ attachThemeAssets(assets, themeConfig);
+ attachCommonAssets(assets, themeConfig);
+ attachIconAssets(assets, themeConfig);
+
+ r = new Resources(assets, dm, config, compatInfo, token);
+ setActivityIcons(r);
+
+ return r;
+ }
+
+ /**
+ * Creates a map between an activity & app's icon ids to its component info. This map
+ * is then stored in the resource object.
+ * When resource.getDrawable(id) is called it will check this mapping and replace
+ * the id with the themed resource id if one is available
+ * @param context
+ * @param pkgName
+ * @param r
+ */
+ private void setActivityIcons(Resources r) {
+ SparseArray<PackageItemInfo> iconResources = new SparseArray<PackageItemInfo>();
+ String pkgName = r.getAssets().getAppName();
+ PackageInfo pkgInfo = null;
+ ApplicationInfo appInfo = null;
+
+ try {
+ pkgInfo = getPackageManager().getPackageInfo(pkgName, PackageManager.GET_ACTIVITIES,
+ UserHandle.getCallingUserId());
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Unable to get pkg " + pkgName, e1);
+ return;
+ }
+
+ final ThemeConfig themeConfig = r.getConfiguration().themeConfig;
+ if (pkgName != null && themeConfig != null &&
+ pkgName.equals(themeConfig.getIconPackPkgName())) {
+ return;
+ }
+
+ //Map application icon
+ if (pkgInfo != null && pkgInfo.applicationInfo != null) {
+ appInfo = pkgInfo.applicationInfo;
+ if (appInfo.themedIcon != 0 || iconResources.get(appInfo.icon) == null) {
+ iconResources.put(appInfo.icon, appInfo);
+ }
+ }
+
+ //Map activity icons.
+ if (pkgInfo != null && pkgInfo.activities != null) {
+ for (ActivityInfo ai : pkgInfo.activities) {
+ if (ai.icon != 0 && (ai.themedIcon != 0 || iconResources.get(ai.icon) == null)) {
+ iconResources.put(ai.icon, ai);
+ } else if (appInfo != null && appInfo.icon != 0 &&
+ (ai.themedIcon != 0 || iconResources.get(appInfo.icon) == null)) {
+ iconResources.put(appInfo.icon, ai);
+ }
+ }
+ }
+
+ r.setIconResources(iconResources);
+ final IPackageManager pm = getPackageManager();
+ try {
+ ComposedIconInfo iconInfo = pm.getComposedIconInfo();
+ r.setComposedIconInfo(iconInfo);
+ } catch (Exception e) {
+ Log.wtf(TAG, "Failed to retrieve ComposedIconInfo", e);
+ }
+ }
+
public final boolean applyConfigurationToResourcesLocked(Configuration config,
CompatibilityInfo compat) {
if (mResConfiguration == null) {
@@ -293,6 +445,22 @@ public class ResourcesManager {
boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
DisplayMetrics dm = defaultDisplayMetrics;
final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
+ boolean themeChanged = (changes & ActivityInfo.CONFIG_THEME_RESOURCE) != 0;
+ if (themeChanged) {
+ AssetManager am = r.getAssets();
+ if (am.hasThemeSupport()) {
+ r.setIconResources(null);
+ r.setComposedIconInfo(null);
+ detachThemeAssets(am);
+ if (config.themeConfig != null) {
+ attachThemeAssets(am, config.themeConfig);
+ attachCommonAssets(am, config.themeConfig);
+ if (attachIconAssets(am, config.themeConfig)) {
+ setActivityIcons(r);
+ }
+ }
+ }
+ }
if (!isDefaultDisplay || hasOverrideConfiguration) {
if (tmpConfig == null) {
tmpConfig = new Configuration();
@@ -309,6 +477,9 @@ public class ResourcesManager {
} else {
r.updateConfiguration(config, dm, compat);
}
+ if (themeChanged) {
+ r.updateStringCache();
+ }
//Slog.i(TAG, "Updated app resources " + v.getKey()
// + " " + r + ": " + r.getConfiguration());
} else {
@@ -320,4 +491,234 @@ public class ResourcesManager {
return changes != 0;
}
+ public static IPackageManager getPackageManager() {
+ if (sPackageManager != null) {
+ return sPackageManager;
+ }
+ IBinder b = ServiceManager.getService("package");
+ sPackageManager = IPackageManager.Stub.asInterface(b);
+ return sPackageManager;
+ }
+
+
+ /**
+ * Attach the necessary theme asset paths and meta information to convert an
+ * AssetManager to being globally "theme-aware".
+ *
+ * @param assets
+ * @param theme
+ * @return true if the AssetManager is now theme-aware; false otherwise.
+ * This can fail, for example, if the theme package has been been
+ * removed and the theme manager has yet to revert formally back to
+ * the framework default.
+ */
+ private boolean attachThemeAssets(AssetManager assets, ThemeConfig theme) {
+ PackageInfo piTheme = null;
+ PackageInfo piTarget = null;
+ PackageInfo piAndroid = null;
+
+ // Some apps run in process of another app (eg keyguard/systemUI) so we must get the
+ // package name from the res tables. The 0th base package name will be the android group.
+ // The 1st base package name will be the app group if one is attached. Check if it is there
+ // first or else the system will crash!
+ String basePackageName = null;
+ String resourcePackageName = null;
+ int count = assets.getBasePackageCount();
+ if (count > 1) {
+ basePackageName = assets.getBasePackageName(1);
+ resourcePackageName = assets.getBaseResourcePackageName(1);
+ } else if (count == 1) {
+ basePackageName = assets.getBasePackageName(0);
+ } else {
+ return false;
+ }
+
+ try {
+ piTheme = getPackageManager().getPackageInfo(
+ theme.getOverlayPkgNameForApp(basePackageName), 0,
+ UserHandle.getCallingUserId());
+ piTarget = getPackageManager().getPackageInfo(
+ basePackageName, 0, UserHandle.getCallingUserId());
+
+ // Handle special case where a system app (ex trebuchet) may have had its pkg name
+ // renamed during an upgrade. basePackageName would be the manifest value which will
+ // fail on getPackageInfo(). resource pkg is assumed to have the original name
+ if (piTarget == null && resourcePackageName != null) {
+ piTarget = getPackageManager().getPackageInfo(resourcePackageName,
+ 0, UserHandle.getCallingUserId());
+ }
+ piAndroid = getPackageManager().getPackageInfo("android", 0,
+ UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ }
+
+ if (piTheme == null || piTheme.applicationInfo == null ||
+ piTarget == null || piTarget.applicationInfo == null ||
+ piAndroid == null || piAndroid.applicationInfo == null ||
+ piTheme.mOverlayTargets == null) {
+ return false;
+ }
+
+ String themePackageName = basePackageName;
+ String themePath = piTheme.applicationInfo.publicSourceDir;
+ if (!piTarget.isThemeApk && piTheme.mOverlayTargets.contains(basePackageName)) {
+ String targetPackagePath = piTarget.applicationInfo.sourceDir;
+ String prefixPath = ThemeUtils.getOverlayPathToTarget(basePackageName);
+
+ String resCachePath = ThemeUtils.getTargetCacheDir(piTarget.packageName, piTheme);
+ String resApkPath = resCachePath + "/resources.apk";
+ String idmapPath = ThemeUtils.getIdmapPath(piTarget.packageName, piTheme.packageName);
+ int cookie = assets.addOverlayPath(idmapPath, themePath, resApkPath,
+ targetPackagePath, prefixPath);
+
+ if (cookie != 0) {
+ assets.setThemePackageName(basePackageName);
+ assets.addThemeCookie(cookie);
+ }
+ }
+
+ if (!piTarget.isThemeApk && piTheme.mOverlayTargets.contains("android")) {
+ String resCachePath= ThemeUtils.getTargetCacheDir(piAndroid.packageName, piTheme);
+ String prefixPath = ThemeUtils.getOverlayPathToTarget(piAndroid.packageName);
+ String targetPackagePath = piAndroid.applicationInfo.publicSourceDir;
+ String resApkPath = resCachePath + "/resources.apk";
+ String idmapPath = ThemeUtils.getIdmapPath("android", piTheme.packageName);
+ int cookie = assets.addOverlayPath(idmapPath, themePath,
+ resApkPath, targetPackagePath, prefixPath);
+ if (cookie != 0) {
+ assets.setThemePackageName(themePackageName);
+ assets.addThemeCookie(cookie);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Attach the necessary icon asset paths. Icon assets should be in a different
+ * namespace than the standard 0x7F.
+ *
+ * @param assets
+ * @param theme
+ * @return true if succes, false otherwise
+ */
+ private boolean attachIconAssets(AssetManager assets, ThemeConfig theme) {
+ PackageInfo piIcon = null;
+ try {
+ piIcon = getPackageManager().getPackageInfo(theme.getIconPackPkgName(), 0,
+ UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ }
+
+ if (piIcon == null || piIcon.applicationInfo == null) {
+ return false;
+ }
+
+ String iconPkg = theme.getIconPackPkgName();
+ if (iconPkg != null && !iconPkg.isEmpty()) {
+ String themeIconPath = piIcon.applicationInfo.publicSourceDir;
+ String prefixPath = ThemeUtils.ICONS_PATH;
+ String iconDir = ThemeUtils.getIconPackDir(iconPkg);
+ String resTablePath = iconDir + "/resources.arsc";
+ String resApkPath = iconDir + "/resources.apk";
+
+ // Legacy Icon packs have everything in their APK
+ if (piIcon.isLegacyIconPackApk) {
+ prefixPath = "";
+ resApkPath = "";
+ resTablePath = "";
+ }
+
+ int cookie = assets.addIconPath(themeIconPath, resApkPath, prefixPath,
+ Resources.THEME_ICON_PKG_ID);
+ if (cookie != 0) {
+ assets.setIconPackCookie(cookie);
+ assets.setIconPackageName(iconPkg);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Attach the necessary common asset paths. Common assets should be in a different
+ * namespace than the standard 0x7F.
+ *
+ * @param assets
+ * @param theme
+ * @return true if succes, false otherwise
+ */
+ private boolean attachCommonAssets(AssetManager assets, ThemeConfig theme) {
+ // Some apps run in process of another app (eg keyguard/systemUI) so we must get the
+ // package name from the res tables. The 0th base package name will be the android group.
+ // The 1st base package name will be the app group if one is attached. Check if it is there
+ // first or else the system will crash!
+ String basePackageName;
+ int count = assets.getBasePackageCount();
+ if (count > 1) {
+ basePackageName = assets.getBasePackageName(1);
+ } else if (count == 1) {
+ basePackageName = assets.getBasePackageName(0);
+ } else {
+ return false;
+ }
+
+ PackageInfo piTheme = null;
+ try {
+ piTheme = getPackageManager().getPackageInfo(
+ theme.getOverlayPkgNameForApp(basePackageName), 0,
+ UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ }
+
+ if (piTheme == null || piTheme.applicationInfo == null) {
+ return false;
+ }
+
+ String themePackageName =
+ ThemeUtils.getCommonPackageName(piTheme.applicationInfo.packageName);
+ if (themePackageName != null && !themePackageName.isEmpty()) {
+ String themePath = piTheme.applicationInfo.publicSourceDir;
+ String prefixPath = ThemeUtils.COMMON_RES_PATH;
+ String resCachePath =
+ ThemeUtils.getTargetCacheDir(ThemeUtils.COMMON_RES_TARGET, piTheme);
+ String resApkPath = resCachePath + "/resources.apk";
+ int cookie = assets.addCommonOverlayPath(themePath, resApkPath,
+ prefixPath);
+ if (cookie != 0) {
+ assets.setCommonResCookie(cookie);
+ assets.setCommonResPackageName(themePackageName);
+ }
+ }
+
+ return true;
+ }
+
+ private void detachThemeAssets(AssetManager assets) {
+ String themePackageName = assets.getThemePackageName();
+ String iconPackageName = assets.getIconPackageName();
+ String commonResPackageName = assets.getCommonResPackageName();
+
+ //Remove Icon pack if it exists
+ if (!TextUtils.isEmpty(iconPackageName) && assets.getIconPackCookie() > 0) {
+ assets.removeOverlayPath(iconPackageName, assets.getIconPackCookie());
+ assets.setIconPackageName(null);
+ assets.setIconPackCookie(0);
+ }
+ //Remove common resources if it exists
+ if (!TextUtils.isEmpty(commonResPackageName) && assets.getCommonResCookie() > 0) {
+ assets.removeOverlayPath(commonResPackageName, assets.getCommonResCookie());
+ assets.setCommonResPackageName(null);
+ assets.setCommonResCookie(0);
+ }
+ final List<Integer> themeCookies = assets.getThemeCookies();
+ if (!TextUtils.isEmpty(themePackageName) && !themeCookies.isEmpty()) {
+ // remove overlays in reverse order
+ for (int i = themeCookies.size() - 1; i >= 0; i--) {
+ assets.removeOverlayPath(themePackageName, themeCookies.get(i));
+ }
+ }
+ assets.getThemeCookies().clear();
+ assets.setThemePackageName(null);
+ }
}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 8bfe6d3859a..1e52d86ff76 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -32,6 +32,7 @@ import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
+import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
@@ -226,9 +227,10 @@ public class WallpaperManager {
private IWallpaperManager mService;
private Bitmap mWallpaper;
private Bitmap mDefaultWallpaper;
+ private Bitmap mKeyguardWallpaper;
private static final int MSG_CLEAR_WALLPAPER = 1;
-
+
Globals(Looper looper) {
IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
mService = IWallpaperManager.Stub.asInterface(b);
@@ -246,6 +248,12 @@ public class WallpaperManager {
}
}
+ public void onKeyguardWallpaperChanged() {
+ synchronized (this) {
+ mKeyguardWallpaper = null;
+ }
+ }
+
public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) {
synchronized (this) {
if (mWallpaper != null) {
@@ -272,6 +280,23 @@ public class WallpaperManager {
}
}
+ /**
+ * @hide
+ */
+ public Bitmap peekKeyguardWallpaperBitmap(Context context) {
+ synchronized (this) {
+ if (mKeyguardWallpaper != null) {
+ return mKeyguardWallpaper;
+ }
+ try {
+ mKeyguardWallpaper = getCurrentKeyguardWallpaperLocked(context);
+ } catch (OutOfMemoryError e) {
+ Log.w(TAG, "No memory load current keyguard wallpaper", e);
+ }
+ return mKeyguardWallpaper;
+ }
+ }
+
public void forgetLoadedWallpaper() {
synchronized (this) {
mWallpaper = null;
@@ -308,7 +333,33 @@ public class WallpaperManager {
}
return null;
}
-
+
+ private Bitmap getCurrentKeyguardWallpaperLocked(Context context) {
+ try {
+ Bundle params = new Bundle();
+ ParcelFileDescriptor fd = mService.getKeyguardWallpaper(this, params);
+ if (fd != null) {
+ try {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ Bitmap bm = BitmapFactory.decodeFileDescriptor(
+ fd.getFileDescriptor(), null, options);
+ return bm;
+ } catch (OutOfMemoryError e) {
+ Log.w(TAG, "Can't decode file", e);
+ } finally {
+ try {
+ fd.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ return null;
+ }
+
private Bitmap getDefaultWallpaperLocked(Context context) {
InputStream is = openDefaultWallpaper(context);
if (is != null) {
@@ -327,6 +378,18 @@ public class WallpaperManager {
}
return null;
}
+
+ /** @hide */
+ public void clearKeyguardWallpaper() {
+ synchronized (this) {
+ try {
+ mService.clearKeyguardWallpaper();
+ } catch (RemoteException e) {
+ // ignore
+ }
+ mKeyguardWallpaper = null;
+ }
+ }
}
private static final Object sSync = new Object[0];
@@ -586,6 +649,15 @@ public class WallpaperManager {
return null;
}
+ /** @hide */
+ public Drawable getFastKeyguardDrawable() {
+ Bitmap bm = sGlobals.peekKeyguardWallpaperBitmap(mContext);
+ if (bm != null) {
+ return new FastBitmapDrawable(bm);
+ }
+ return null;
+ }
+
/**
* Like {@link #getFastDrawable()}, but if there is no wallpaper set,
* a null pointer is returned.
@@ -611,6 +683,13 @@ public class WallpaperManager {
}
/**
+ * @hide
+ */
+ public Bitmap getKeyguardBitmap() {
+ return sGlobals.peekKeyguardWallpaperBitmap(mContext);
+ }
+
+ /**
* Remove all internal references to the last loaded wallpaper. Useful
* for apps that want to reduce memory usage when they only temporarily
* need to have the wallpaper. After calling, the next request for the
@@ -771,6 +850,35 @@ public class WallpaperManager {
}
/**
+ * @param bitmap
+ * @throws IOException
+ * @hide
+ */
+ public void setKeyguardBitmap(Bitmap bitmap) throws IOException {
+ if (sGlobals.mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ return;
+ }
+ try {
+ ParcelFileDescriptor fd = sGlobals.mService.setKeyguardWallpaper(null);
+ if (fd == null) {
+ return;
+ }
+ FileOutputStream fos = null;
+ try {
+ fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+ bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos);
+ } finally {
+ if (fos != null) {
+ fos.close();
+ }
+ }
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ }
+
+ /**
* Change the current system wallpaper to a specific byte stream. The
* give InputStream is copied into persistent storage and will now be
* used as the wallpaper. Currently it must be either a JPEG or PNG
@@ -809,6 +917,33 @@ public class WallpaperManager {
}
}
+ /**
+ * @hide
+ */
+ public void setKeyguardStream(InputStream data) throws IOException {
+ if (sGlobals.mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ return;
+ }
+ try {
+ ParcelFileDescriptor fd = sGlobals.mService.setKeyguardWallpaper(null);
+ if (fd == null) {
+ return;
+ }
+ FileOutputStream fos = null;
+ try {
+ fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+ setWallpaper(data, fos);
+ } finally {
+ if (fos != null) {
+ fos.close();
+ }
+ }
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ }
+
private void setWallpaper(InputStream data, FileOutputStream fos)
throws IOException {
byte[] buffer = new byte[32768];
@@ -1029,7 +1164,29 @@ public class WallpaperManager {
mWallpaperXStep = xStep;
mWallpaperYStep = yStep;
}
-
+
+ /** @hide */
+ public int getLastWallpaperX() {
+ try {
+ return WindowManagerGlobal.getWindowSession().getLastWallpaperX();
+ } catch (RemoteException e) {
+ // Ignore.
+ }
+
+ return -1;
+ }
+
+ /** @hide */
+ public int getLastWallpaperY() {
+ try {
+ return WindowManagerGlobal.getWindowSession().getLastWallpaperY();
+ } catch (RemoteException e) {
+ // Ignore.
+ }
+
+ return -1;
+ }
+
/**
* Send an arbitrary command to the current active wallpaper.
*
@@ -1086,7 +1243,25 @@ public class WallpaperManager {
* wallpaper.
*/
public void clear() throws IOException {
- setStream(openDefaultWallpaper(mContext));
+ clear(true);
+ }
+
+ /** @hide */
+ public void clear(boolean setToDefault) throws IOException {
+ if (setToDefault) {
+ setStream(openDefaultWallpaper(mContext));
+ } else {
+ Bitmap blackBmp = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565);
+ blackBmp.setPixel(0, 0, mContext.getResources().getColor(android.R.color.black));
+ setBitmap(blackBmp);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void clearKeyguardWallpaper() {
+ sGlobals.clearKeyguardWallpaper();
}
/**
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 57b6df14679..9831d915107 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2706,6 +2706,16 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
+ * {@link android.content.res.ThemeManager} for accessing theme service.
+ *
+ * @see #getSystemService
+ * @see android.content.res.ThemeManager
+ * @hide
+ */
+ public static final String THEME_SERVICE = "themes";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
* {@link android.nfc.NfcManager} for using NFC.
*
* @see #getSystemService
@@ -3368,6 +3378,26 @@ public abstract class Context {
int flags) throws PackageManager.NameNotFoundException;
/**
+ * Similar to {@link #createPackageContext(String, int)}, but with a
+ * different {@link UserHandle}. For example, {@link #getContentResolver()}
+ * will open any {@link Uri} as the given user. A theme package can be
+ * specified which will be used when adding resources to this context
+ *
+ * @hide
+ */
+ public abstract Context createPackageContextAsUser(
+ String packageName, String themePackageName, int flags, UserHandle user)
+ throws PackageManager.NameNotFoundException;
+
+ /**
+ * Creates a context given an {@link android.content.pm.ApplicationInfo}.
+ *
+ * @hide
+ */
+ public abstract Context createApplicationContext(ApplicationInfo application,
+ String themePackageName, int flags) throws PackageManager.NameNotFoundException;
+
+ /**
* Get the userId associated with this context
* @return user id
*
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index ad7c350aa24..837208619d7 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -668,7 +668,20 @@ public class ContextWrapper extends Context {
/** @hide */
public Context createApplicationContext(ApplicationInfo application,
int flags) throws PackageManager.NameNotFoundException {
- return mBase.createApplicationContext(application, flags);
+ return createApplicationContext(application, null, flags);
+ }
+
+ /** @hide */
+ public Context createApplicationContext(ApplicationInfo application,
+ String themePackageName, int flags) throws PackageManager.NameNotFoundException {
+ return mBase.createApplicationContext(application, themePackageName, flags);
+ }
+
+ /** @hide */
+ @Override
+ public Context createPackageContextAsUser(String packageName, String themePackageName,
+ int flags, UserHandle user) throws PackageManager.NameNotFoundException {
+ return mBase.createPackageContextAsUser(packageName, themePackageName, flags, user);
}
/** @hide */
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 02c7395cd42..21fe8d60cdb 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2006 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1847,6 +1848,15 @@ public class Intent implements Parcelable, Cloneable {
*/
@Deprecated @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
+
+ /**
+ * Broadcast Action: The current keyguard wallpaper configuration
+ * has changed and should be re-read.
+ * {@hide}
+ */
+ public static final String ACTION_KEYGUARD_WALLPAPER_CHANGED =
+ "android.intent.action.KEYGUARD_WALLPAPER_CHANGED";
+
/**
* Broadcast Action: The current device {@link android.content.res.Configuration}
* (orientation, locale, etc) has changed. When such a change happens, the
@@ -2611,6 +2621,21 @@ public class Intent implements Parcelable, Cloneable {
"android.intent.action.QUICK_CLOCK";
/**
+ * Broadcast Action: Indicate that unrecoverable error happened during app launch.
+ * Could indicate that curently applied theme is malicious.
+ * @hide
+ */
+ public static final String ACTION_APP_FAILURE =
+ "com.tmobile.intent.action.APP_FAILURE";
+
+ /**
+ * Broadcast Action: Request to reset the unrecoverable errors count to 0.
+ * @hide
+ */
+ public static final String ACTION_APP_FAILURE_RESET =
+ "com.tmobile.intent.action.APP_FAILURE_RESET";
+
+ /**
* Activity Action: Shows the brightness setting dialog.
* @hide
*/
@@ -2706,6 +2731,19 @@ public class Intent implements Parcelable, Cloneable {
public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
/**
+ * Broadcast Action: A theme's resources were cached. Includes two extra fields,
+ * {@link #EXTRA_THEME_PACKAGE_NAME}, containing the package name of the theme that was
+ * processed, and {@link #EXTRA_THEME_RESULT}, containing the result code.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.</p>
+ *
+ * @hide
+ */
+ public static final String ACTION_THEME_RESOURCES_CACHED =
+ "android.intent.action.THEME_RESOURCES_CACHED";
+
+ /**
* Activity Action: Allow the user to pick a directory subtree. When
* invoked, the system will display the various {@link DocumentsProvider}
* instances installed on the device, letting the user navigate through
@@ -2923,6 +2961,14 @@ public class Intent implements Parcelable, Cloneable {
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
public static final String CATEGORY_CAR_MODE = "android.intent.category.CAR_MODE";
+ /**
+ * Used to indicate that a theme package has been installed or un-installed.
+ *
+ * @hide
+ */
+ public static final String CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE =
+ "com.tmobile.intent.category.THEME_PACKAGE_INSTALL_STATE_CHANGE";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Application launch intent categories (see addCategory()).
@@ -3432,6 +3478,26 @@ public class Intent implements Parcelable, Cloneable {
/** {@hide} */
public static final String EXTRA_REASON = "android.intent.extra.REASON";
+ /**
+ * Extra for {@link #ACTION_THEME_RESOURCES_CACHED} that provides the return value
+ * from processThemeResources. A value of 0 indicates a successful caching of resources.
+ * Error results are:
+ * {@link android.content.pm.PackageManager#INSTALL_FAILED_THEME_AAPT_ERROR}
+ * {@link android.content.pm.PackageManager#INSTALL_FAILED_THEME_IDMAP_ERROR}
+ * {@link android.content.pm.PackageManager#INSTALL_FAILED_THEME_UNKNOWN_ERROR}
+ *
+ * @hide
+ */
+ public static final String EXTRA_THEME_RESULT = "android.intent.extra.RESULT";
+
+ /**
+ * Extra for {@link #ACTION_THEME_RESOURCES_CACHED} that provides the package name of the
+ * theme that was processed.
+ *
+ * @hide
+ */
+ public static final String EXTRA_THEME_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Intent flags (see mFlags variable).
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 641f843ea2d..1ff75531a14 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2007 The Android Open Source Project
- *
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
+
* 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
@@ -492,6 +493,10 @@ public class ActivityInfo extends ComponentInfo
*/
public static final int CONFIG_ORIENTATION = 0x0080;
/**
+ * @hide
+ */
+ public static final int CONFIG_THEME_RESOURCE = 0x008000;
+ /**
* Bit in {@link #configChanges} that indicates that the activity
* can itself handle changes to the screen layout. Set from the
* {@link android.R.attr#configChanges} attribute.
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 76d71c17845..5e342c9c5f0 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2007 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -588,6 +589,12 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public int installLocation = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
/**
+ * Is given application theme agnostic, i.e. behaves properly when default theme is changed.
+ * {@hide}
+ */
+ public boolean isThemeable = false;
+
+ /**
* When true, indicates that any one component within this application is
* protected.
* @hide
@@ -717,6 +724,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
uiOptions = orig.uiOptions;
backupAgentName = orig.backupAgentName;
protect = orig.protect;
+ isThemeable = orig.isThemeable;
}
@@ -768,6 +776,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dest.writeInt(descriptionRes);
dest.writeInt(uiOptions);
dest.writeInt(protect ? 1 : 0);
+ dest.writeInt(isThemeable ? 1 : 0);
}
public static final Parcelable.Creator<ApplicationInfo> CREATOR
@@ -818,6 +827,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
descriptionRes = source.readInt();
uiOptions = source.readInt();
protect = source.readInt() != 0;
+ isThemeable = source.readInt() != 0;
}
/**
diff --git a/core/java/android/content/pm/BaseThemeInfo.java b/core/java/android/content/pm/BaseThemeInfo.java
new file mode 100644
index 00000000000..8ece42d1933
--- /dev/null
+++ b/core/java/android/content/pm/BaseThemeInfo.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2010, T-Mobile USA, Inc.
+ *
+ * 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 android.content.pm;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+import android.util.Log;
+import android.util.AttributeSet;
+import android.content.res.Resources;
+
+/**
+ * @hide
+ */
+public class BaseThemeInfo implements Parcelable {
+ /**
+ * The theme id, which does not change when the theme is modified.
+ * Specifies an Android UI Style using style name.
+ *
+ * @see themeId attribute
+ *
+ */
+ public String themeId;
+
+ /**
+ * The name of the theme (as displayed by UI).
+ *
+ * @see name attribute
+ *
+ */
+ public String name;
+
+ /**
+ * The author name of the theme package.
+ *
+ * @see author attribute
+ *
+ */
+ public String author;
+
+ /*
+ * Describe the kinds of special objects contained in this Parcelable's
+ * marshalled representation.
+ *
+ * @return a bitmask indicating the set of special object types marshalled
+ * by the Parcelable.
+ *
+ * @see android.os.Parcelable#describeContents()
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /*
+ * Flatten this object in to a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written.
+ * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
+ *
+ * @see android.os.Parcelable#writeToParcel(android.os.Parcel, int)
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(themeId);
+ dest.writeString(name);
+ dest.writeString(author);
+ }
+
+ /** @hide */
+ public static final Parcelable.Creator<BaseThemeInfo> CREATOR
+ = new Parcelable.Creator<BaseThemeInfo>() {
+ public BaseThemeInfo createFromParcel(Parcel source) {
+ return new BaseThemeInfo(source);
+ }
+
+ public BaseThemeInfo[] newArray(int size) {
+ return new BaseThemeInfo[size];
+ }
+ };
+
+ /** @hide */
+ public final String getResolvedString(Resources res, AttributeSet attrs, int index) {
+ int resId = attrs.getAttributeResourceValue(index, 0);
+ if (resId !=0 ) {
+ return res.getString(resId);
+ }
+ return attrs.getAttributeValue(index);
+ }
+
+ protected BaseThemeInfo() {
+ }
+
+ protected BaseThemeInfo(Parcel source) {
+ themeId = source.readString();
+ name = source.readString();
+ author = source.readString();
+ }
+}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 5700e44341f..a24ccad7b90 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -17,6 +17,7 @@
package android.content.pm;
+import android.app.ComposedIconInfo;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
@@ -459,4 +460,9 @@ interface IPackageManager {
/** Protected Apps */
void setComponentProtectedSetting(in ComponentName componentName,
in boolean newState, int userId);
+
+ /** Themes */
+ void updateIconMapping(String pkgName);
+ ComposedIconInfo getComposedIconInfo();
+ int processThemeResources(String themePkgName);
}
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index b66bd017275..ed6d734d5a4 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2007 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +17,11 @@
package android.content.pm;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
import android.os.Parcel;
import android.os.Parcelable;
@@ -237,6 +243,34 @@ public class PackageInfo implements Parcelable {
/** @hide */
public boolean coreApp;
+ // Is Theme Apk
+ /**
+ * {@hide}
+ */
+ public boolean isThemeApk = false;
+
+ /**
+ * {@hide}
+ */
+ public boolean hasIconPack = false;
+
+ /**
+ * {@hide}
+ */
+ public ArrayList<String> mOverlayTargets;
+
+ // Is Legacy Icon Apk
+ /**
+ * {@hide}
+ */
+ public boolean isLegacyIconPackApk = false;
+
+ // ThemeInfo
+ /**
+ * {@hide}
+ */
+ public ThemeInfo themeInfo;
+
/** @hide */
public boolean requiredForAllUsers;
@@ -301,6 +335,13 @@ public class PackageInfo implements Parcelable {
dest.writeString(restrictedAccountType);
dest.writeString(requiredAccountType);
dest.writeString(overlayTarget);
+
+ /* Theme-specific. */
+ dest.writeInt((isThemeApk) ? 1 : 0);
+ dest.writeStringList(mOverlayTargets);
+ dest.writeParcelable(themeInfo, parcelableFlags);
+ dest.writeInt(hasIconPack ? 1 : 0);
+ dest.writeInt((isLegacyIconPackApk) ? 1 : 0);
}
public static final Parcelable.Creator<PackageInfo> CREATOR
@@ -346,5 +387,12 @@ public class PackageInfo implements Parcelable {
restrictedAccountType = source.readString();
requiredAccountType = source.readString();
overlayTarget = source.readString();
+
+ /* Theme-specific. */
+ isThemeApk = (source.readInt() != 0);
+ mOverlayTargets = source.createStringArrayList();
+ themeInfo = source.readParcelable(null);
+ hasIconPack = source.readInt() == 1;
+ isLegacyIconPackApk = source.readInt() == 1;
}
}
diff --git a/core/java/android/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java
index e336c5f752a..3622df4d174 100644
--- a/core/java/android/content/pm/PackageInfoLite.java
+++ b/core/java/android/content/pm/PackageInfoLite.java
@@ -52,6 +52,7 @@ public class PackageInfoLite implements Parcelable {
*/
public int recommendedInstallLocation;
public int installLocation;
+ public boolean isTheme;
public VerifierInfo[] verifiers;
@@ -74,6 +75,7 @@ public class PackageInfoLite implements Parcelable {
dest.writeInt(recommendedInstallLocation);
dest.writeInt(installLocation);
dest.writeInt(multiArch ? 1 : 0);
+ dest.writeInt(isTheme ? 1 : 0);
if (verifiers == null || verifiers.length == 0) {
dest.writeInt(0);
@@ -100,6 +102,7 @@ public class PackageInfoLite implements Parcelable {
recommendedInstallLocation = source.readInt();
installLocation = source.readInt();
multiArch = (source.readInt() != 0);
+ isTheme = source.readInt() == 1 ? true : false;
final int verifiersLength = source.readInt();
if (verifiersLength == 0) {
diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java
index cacdf8e3625..cbd6198607b 100644
--- a/core/java/android/content/pm/PackageItemInfo.java
+++ b/core/java/android/content/pm/PackageItemInfo.java
@@ -66,7 +66,14 @@ public class PackageItemInfo {
* component's icon. From the "icon" attribute or, if not set, 0.
*/
public int icon;
-
+
+ /**
+ * A drawable resource identifier in the icon pack's resources
+ * If there isn't an icon pack or not set, then 0.
+ * @hide
+ */
+ public int themedIcon;
+
/**
* A drawable resource identifier (in the package's resources) of this
* component's banner. From the "banner" attribute or, if not set, 0.
@@ -110,6 +117,7 @@ public class PackageItemInfo {
logo = orig.logo;
metaData = orig.metaData;
showUserIcon = orig.showUserIcon;
+ themedIcon = orig.themedIcon;
}
/**
@@ -292,8 +300,9 @@ public class PackageItemInfo {
dest.writeBundle(metaData);
dest.writeInt(banner);
dest.writeInt(showUserIcon);
+ dest.writeInt(themedIcon);
}
-
+
protected PackageItemInfo(Parcel source) {
name = source.readString();
packageName = source.readString();
@@ -305,6 +314,7 @@ public class PackageItemInfo {
metaData = source.readBundle();
banner = source.readInt();
showUserIcon = source.readInt();
+ themedIcon = source.readInt();
}
/**
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c37322a1a5a..cbdf40b9598 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -782,6 +782,38 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_ABORTED = -115;
/**
+ * Used by themes
+ * Installation failed return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+ * if the system failed to install the theme because aapt could not compile the app
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_THEME_AAPT_ERROR = -400;
+
+ /**
+ * Used by themes
+ * Installation failed return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+ * if the system failed to install the theme because idmap failed
+ * apps.
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_THEME_IDMAP_ERROR = -401;
+
+ /**
+ * Used by themes
+ * Installation failed return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+ * if the system failed to install the theme for an unknown reason
+ * apps.
+ * @hide
+ */
+ @SystemApi
+ public static final int INSTALL_FAILED_THEME_UNKNOWN_ERROR = -402;
+
+ /**
* Flag parameter for {@link #deletePackage} to indicate that you don't want to delete the
* package's data directory.
*
@@ -3121,6 +3153,18 @@ public abstract class PackageManager {
public abstract Resources getResourcesForApplicationAsUser(String appPackageName, int userId)
throws NameNotFoundException;
+ /** @hide */
+ public abstract Resources getThemedResourcesForApplication(ApplicationInfo app,
+ String themePkgName) throws NameNotFoundException;
+
+ /** @hide */
+ public abstract Resources getThemedResourcesForApplication(String appPackageName,
+ String themePkgName) throws NameNotFoundException;
+
+ /** @hide */
+ public abstract Resources getThemedResourcesForApplicationAsUser(String appPackageName,
+ String themePkgName, int userId) throws NameNotFoundException;
+
/**
* Retrieve overall information about an application package defined
* in a package archive file
@@ -4123,4 +4167,22 @@ public abstract class PackageManager {
}
}
}
+
+ /**
+ * Updates the theme icon res id for the new theme
+ * @hide
+ */
+ public abstract void updateIconMaps(String pkgName);
+
+ /**
+ * Used to compile theme resources for a given theme
+ * @param themePkgName
+ * @return A value of 0 indicates success. Possible errors returned are:
+ * {@link android.content.pm.PackageManager#INSTALL_FAILED_THEME_AAPT_ERROR},
+ * {@link android.content.pm.PackageManager#INSTALL_FAILED_THEME_IDMAP_ERROR}, or
+ * {@link android.content.pm.PackageManager#INSTALL_FAILED_THEME_UNKNOWN_ERROR}
+ *
+ * @hide
+ */
+ public abstract int processThemeResources(String themePkgName);
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index afb439247fe..23bf2789ca9 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2007 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -58,6 +59,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
@@ -74,13 +76,17 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.jar.StrictJarFile;
import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
/**
* Parser for package files (APKs) on disk. This supports apps packaged either
@@ -110,6 +116,17 @@ public class PackageParser {
/** File name in an APK for the Android manifest. */
private static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
+ /** Path to overlay directory in a theme APK */
+ private static final String OVERLAY_PATH = "assets/overlays/";
+ /** Path to icon directory in a theme APK */
+ private static final String ICON_PATH = "assets/icons/";
+
+ private static final String PACKAGE_REDIRECTIONS_XML = "res/xml/redirections.xml";
+
+ private static final String TAG_PACKAGE_REDIRECTIONS = "package-redirections";
+ private static final String TAG_RESOURCE_REDIRECTIONS = "resource-redirections";
+ private static final String TAG_ITEM = "item";
+ private static final String ATTRIBUTE_ITEM_NAME = "name";
/** @hide */
public static class NewPermissionInfo {
@@ -246,6 +263,7 @@ public class PackageParser {
public final int versionCode;
public final int installLocation;
public final VerifierInfo[] verifiers;
+ public boolean isTheme;
/** Names of any split APKs, ordered by parsed splitName */
public final String[] splitNames;
@@ -265,6 +283,7 @@ public class PackageParser {
public final boolean coreApp;
public final boolean multiArch;
+
public PackageLite(String codePath, ApkLite baseApk, String[] splitNames,
String[] splitCodePaths) {
this.packageName = baseApk.packageName;
@@ -277,6 +296,7 @@ public class PackageParser {
this.splitCodePaths = splitCodePaths;
this.coreApp = baseApk.coreApp;
this.multiArch = baseApk.multiArch;
+ this.isTheme = baseApk.isTheme;
}
public List<String> getAllCodePaths() {
@@ -302,10 +322,11 @@ public class PackageParser {
public final Signature[] signatures;
public final boolean coreApp;
public final boolean multiArch;
+ public final boolean isTheme;
public ApkLite(String codePath, String packageName, String splitName, int versionCode,
int installLocation, List<VerifierInfo> verifiers, Signature[] signatures,
- boolean coreApp, boolean multiArch) {
+ boolean coreApp, boolean multiArch, boolean isTheme) {
this.codePath = codePath;
this.packageName = packageName;
this.splitName = splitName;
@@ -315,6 +336,7 @@ public class PackageParser {
this.signatures = signatures;
this.coreApp = coreApp;
this.multiArch = multiArch;
+ this.isTheme = isTheme;
}
}
@@ -413,6 +435,14 @@ public class PackageParser {
pi.versionName = p.mVersionName;
pi.sharedUserId = p.mSharedUserId;
pi.sharedUserLabel = p.mSharedUserLabel;
+ pi.isThemeApk = p.mIsThemeApk;
+ pi.hasIconPack = p.hasIconPack;
+ pi.isLegacyIconPackApk = p.mIsLegacyIconPackApk;
+
+ if (pi.isThemeApk) {
+ pi.mOverlayTargets = p.mOverlayTargets;
+ pi.themeInfo = p.mThemeInfo;
+ }
pi.applicationInfo = generateApplicationInfo(p, flags, state, userId);
pi.installLocation = p.installLocation;
pi.coreApp = p.coreApp;
@@ -874,6 +904,18 @@ public class PackageParser {
pkg.baseCodePath = apkPath;
pkg.mSignatures = null;
+ // If the pkg is a theme, we need to know what themes it overlays
+ // and determine if it has an icon pack
+ if (pkg.mIsThemeApk) {
+ //Determine existance of Overlays
+ ArrayList<String> overlayTargets = scanPackageOverlays(apkFile);
+ for(String overlay : overlayTargets) {
+ pkg.mOverlayTargets.add(overlay);
+ }
+
+ pkg.hasIconPack = packageHasIconPack(apkFile);
+ }
+
return pkg;
} catch (PackageParserException e) {
@@ -996,6 +1038,51 @@ public class PackageParser {
return pkg;
}
+
+ private ArrayList<String> scanPackageOverlays(File originalFile) {
+ Set<String> overlayTargets = new HashSet<String>();
+
+ try {
+ final ZipFile privateZip = new ZipFile(originalFile.getPath());
+ final Enumeration<? extends ZipEntry> privateZipEntries = privateZip.entries();
+ while (privateZipEntries.hasMoreElements()) {
+ final ZipEntry zipEntry = privateZipEntries.nextElement();
+ final String zipEntryName = zipEntry.getName();
+
+ if (zipEntryName.startsWith(OVERLAY_PATH) && zipEntryName.length() > 16) {
+ String[] subdirs = zipEntryName.split("/");
+ overlayTargets.add(subdirs[2]);
+ }
+ }
+ } catch(Exception e) {
+ e.printStackTrace();
+ overlayTargets.clear();
+ }
+
+ ArrayList<String> overlays = new ArrayList<String>();
+ overlays.addAll(overlayTargets);
+ return overlays;
+ }
+
+ private boolean packageHasIconPack(File originalFile) {
+ try {
+ final ZipFile privateZip = new ZipFile(originalFile.getPath());
+ final Enumeration<? extends ZipEntry> privateZipEntries = privateZip.entries();
+ while (privateZipEntries.hasMoreElements()) {
+ final ZipEntry zipEntry = privateZipEntries.nextElement();
+ final String zipEntryName = zipEntry.getName();
+
+ if (zipEntryName.startsWith(ICON_PATH) &&
+ zipEntryName.length() > ICON_PATH.length()) {
+ return true;
+ }
+ }
+ } catch(Exception e) {
+ Log.e(TAG, "Could not read zip entries while checking if apk has icon pack", e);
+ }
+ return false;
+ }
+
/**
* Gathers the {@link ManifestDigest} for {@code pkg} if it exists in the
* APK. If it successfully scanned the package and found the
@@ -1275,6 +1362,9 @@ public class PackageParser {
// Only search the tree when the tag is directly below <manifest>
int type;
final int searchDepth = parser.getDepth() + 1;
+ // Search for category and actions inside <intent-filter>
+ final int iconPackSearchDepth = parser.getDepth() + 4;
+ boolean isTheme = false;
final List<VerifierInfo> verifiers = new ArrayList<VerifierInfo>();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -1299,10 +1389,52 @@ public class PackageParser {
}
}
}
+
+ if (parser.getDepth() == searchDepth && "meta-data".equals(parser.getName())) {
+ for (int i=0; i < parser.getAttributeCount(); i++) {
+ if ("name".equals(parser.getAttributeName(i)) &&
+ ThemeInfo.META_TAG_NAME.equals(parser.getAttributeValue(i))) {
+ isTheme = true;
+ installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
+ break;
+ }
+ }
+ }
+
+ if (parser.getDepth() == searchDepth && "theme".equals(parser.getName())) {
+ isTheme = true;
+ installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
+ }
+
+ if (parser.getDepth() == iconPackSearchDepth && isLegacyIconPack(parser)) {
+ isTheme = true;
+ installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
+ }
}
return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode,
- installLocation, verifiers, signatures, coreApp, multiArch);
+ installLocation, verifiers, signatures, coreApp, multiArch, isTheme);
+ }
+
+ private static boolean isLegacyIconPack(XmlPullParser parser) {
+ boolean isAction = "action".equals(parser.getName());
+ boolean isCategory = "category".equals(parser.getName());
+ String[] items = isAction ? ThemeUtils.sSupportedActions
+ : (isCategory ? ThemeUtils.sSupportedCategories : null);
+
+ if (items != null) {
+ for (int i = 0; i < parser.getAttributeCount(); i++) {
+ if ("name".equals(parser.getAttributeName(i))) {
+ final String value = parser.getAttributeValue(i);
+ for (String item : items) {
+ if (item.equals(value)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
}
/**
@@ -1354,6 +1486,8 @@ public class PackageParser {
}
final Package pkg = new Package(pkgName);
+ Bundle metaDataBundle = new Bundle();
+
boolean foundApp = false;
TypedArray sa = res.obtainAttributes(attrs,
@@ -1759,6 +1893,11 @@ public class PackageParser {
XmlUtils.skipCurrentTag(parser);
continue;
+ } else if (parser.getName().equals("meta-data")) {
+ if ((metaDataBundle=parseMetaData(res, parser, attrs, metaDataBundle,
+ outError)) == null) {
+ return null;
+ }
} else if (RIGID_PARSER) {
outError[0] = "Bad element under <manifest>: "
+ parser.getName();
@@ -1849,6 +1988,9 @@ public class PackageParser {
>= android.os.Build.VERSION_CODES.DONUT)) {
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES;
}
+ if (pkg.mIsThemeApk || pkg.mIsLegacyIconPackApk) {
+ pkg.applicationInfo.isThemeable = false;
+ }
/*
* b/8528162: Ignore the <uses-permission android:required> attribute if
@@ -1861,6 +2003,14 @@ public class PackageParser {
}
}
+ //Is this pkg a theme?
+ if (metaDataBundle.containsKey(ThemeInfo.META_TAG_NAME)) {
+ pkg.mIsThemeApk = true;
+ pkg.mTrustedOverlay = true;
+ pkg.mOverlayPriority = 1;
+ pkg.mThemeInfo = new ThemeInfo(metaDataBundle);
+ }
+
return pkg;
}
@@ -2389,6 +2539,9 @@ public class PackageParser {
final ApplicationInfo ai = owner.applicationInfo;
final String pkgName = owner.applicationInfo.packageName;
+ // assume that this package is themeable unless explicitly set to false.
+ ai.isThemeable = true;
+
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestApplication);
@@ -3175,6 +3328,26 @@ public class PackageParser {
if (!parseIntent(res, parser, attrs, true, intent, outError)) {
return null;
}
+
+ // Check if package is a legacy icon pack
+ if (!owner.mIsLegacyIconPackApk) {
+ for(String action : ThemeUtils.sSupportedActions) {
+ if (intent.hasAction(action)) {
+ owner.mIsLegacyIconPackApk = true;
+ break;
+ }
+
+ }
+ }
+ if (!owner.mIsLegacyIconPackApk) {
+ for(String category : ThemeUtils.sSupportedCategories) {
+ if (intent.hasCategory(category)) {
+ owner.mIsLegacyIconPackApk = true;
+ break;
+ }
+ }
+ }
+
if (intent.countActions() == 0) {
Slog.w(TAG, "No actions in intent filter at "
+ mArchiveSourcePath + " "
@@ -4246,6 +4419,17 @@ public class PackageParser {
// For use by package manager to keep track of when a package was last used.
public long mLastPackageUsageTimeInMills;
+ // Is Theme Apk
+ public boolean mIsThemeApk = false;
+ public final ArrayList<String> mOverlayTargets = new ArrayList<String>(0);
+ public Map<String, Map<String, String>> mPackageRedirections
+ = new HashMap<String, Map<String, String>>();
+
+ // Theme info
+ public ThemeInfo mThemeInfo = null;
+
+ // Legacy icon pack
+ public boolean mIsLegacyIconPackApk = false;
// // User set enabled state.
// public int mSetEnabled = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
@@ -4291,6 +4475,8 @@ public class PackageParser {
public int mOverlayPriority;
public boolean mTrustedOverlay;
+ public boolean hasIconPack;
+
/**
* Data used to feed the KeySetManagerService
*/
diff --git a/core/java/android/content/pm/ThemeInfo.aidl b/core/java/android/content/pm/ThemeInfo.aidl
new file mode 100644
index 00000000000..acbc85e9c8b
--- /dev/null
+++ b/core/java/android/content/pm/ThemeInfo.aidl
@@ -0,0 +1,3 @@
+package android.content.pm;
+
+parcelable ThemeInfo;
diff --git a/core/java/android/content/pm/ThemeInfo.java b/core/java/android/content/pm/ThemeInfo.java
new file mode 100644
index 00000000000..ab798db7b00
--- /dev/null
+++ b/core/java/android/content/pm/ThemeInfo.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2010, T-Mobile USA, Inc.
+ *
+ * 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 android.content.pm;
+
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParser;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.content.res.Resources;
+
+/**
+ * Overall information about "theme" package. This corresponds
+ * to the information collected from AndroidManifest.xml
+ *
+ * Below is an example of the manifest:
+ *
+ * <meta-data android:name="org.cyanogenmod.theme.name" android:value="Foobar's Theme"/>
+ * <meta-data android:name="org.cyanogenmod.theme.author" android:value="Mr.Foo" />
+ *
+ * @hide
+ */
+public final class ThemeInfo extends BaseThemeInfo {
+
+ public static final String META_TAG_NAME = "org.cyanogenmod.theme.name";
+ public static final String META_TAG_AUTHOR = "org.cyanogenmod.theme.author";
+
+ public ThemeInfo(Bundle bundle) {
+ super();
+ name = bundle.getString(META_TAG_NAME);
+ themeId = name;
+ author = bundle.getString(META_TAG_AUTHOR);
+ }
+
+ public static final Parcelable.Creator<ThemeInfo> CREATOR
+ = new Parcelable.Creator<ThemeInfo>() {
+ public ThemeInfo createFromParcel(Parcel source) {
+ return new ThemeInfo(source);
+ }
+
+ public ThemeInfo[] newArray(int size) {
+ return new ThemeInfo[size];
+ }
+ };
+
+ private ThemeInfo(Parcel source) {
+ super(source);
+ }
+}
diff --git a/core/java/android/content/pm/ThemeUtils.java b/core/java/android/content/pm/ThemeUtils.java
new file mode 100644
index 00000000000..38391d481b1
--- /dev/null
+++ b/core/java/android/content/pm/ThemeUtils.java
@@ -0,0 +1,718 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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 android.content.pm;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.IntentFilter;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.ThemeConfig;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.FileUtils;
+import android.os.SystemProperties;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.provider.ThemesContract;
+import android.provider.ThemesContract.ThemesColumns;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.WindowManager;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.CRC32;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+import static android.content.res.ThemeConfig.SYSTEM_DEFAULT;
+
+/**
+ * @hide
+ */
+public class ThemeUtils {
+ private static final String TAG = "ThemeUtils";
+
+ /* Path inside a theme APK to the overlay folder */
+ public static final String OVERLAY_PATH = "assets/overlays/";
+ public static final String ICONS_PATH = "assets/icons/";
+ public static final String COMMON_RES_PATH = "assets/overlays/common/";
+ public static final String FONT_XML = "fonts.xml";
+ public static final String RESOURCE_CACHE_DIR = "/data/resource-cache/";
+ public static final String IDMAP_SUFFIX = "@idmap";
+ public static final String COMMON_RES_SUFFIX = ".common";
+ public static final String COMMON_RES_TARGET = "common";
+ public static final String ICON_HASH_FILENAME = "hash";
+
+ // path to external theme resources, i.e. bootanimation.zip
+ public static final String SYSTEM_THEME_PATH = "/data/system/theme";
+ public static final String SYSTEM_THEME_FONT_PATH = SYSTEM_THEME_PATH + File.separator + "fonts";
+ public static final String SYSTEM_THEME_RINGTONE_PATH = SYSTEM_THEME_PATH
+ + File.separator + "ringtones";
+ public static final String SYSTEM_THEME_NOTIFICATION_PATH = SYSTEM_THEME_PATH
+ + File.separator + "notifications";
+ public static final String SYSTEM_THEME_ALARM_PATH = SYSTEM_THEME_PATH
+ + File.separator + "alarms";
+ public static final String SYSTEM_THEME_ICON_CACHE_DIR = SYSTEM_THEME_PATH
+ + File.separator + "icons";
+ // internal path to bootanimation.zip inside theme apk
+ public static final String THEME_BOOTANIMATION_PATH = "assets/bootanimation/bootanimation.zip";
+
+ public static final String SYSTEM_MEDIA_PATH = "/system/media/audio";
+ public static final String SYSTEM_ALARMS_PATH = SYSTEM_MEDIA_PATH + File.separator
+ + "alarms";
+ public static final String SYSTEM_RINGTONES_PATH = SYSTEM_MEDIA_PATH + File.separator
+ + "ringtones";
+ public static final String SYSTEM_NOTIFICATIONS_PATH = SYSTEM_MEDIA_PATH + File.separator
+ + "notifications";
+
+ private static final String MEDIA_CONTENT_URI = "content://media/internal/audio/media";
+
+ public static final String ACTION_THEME_CHANGED = "org.cyanogenmod.intent.action.THEME_CHANGED";
+
+ public static final String CATEGORY_THEME_COMPONENT_PREFIX = "org.cyanogenmod.intent.category.";
+
+ public static final int SYSTEM_TARGET_API = 0;
+
+ private static final String SETTINGS_DB =
+ "/data/data/com.android.providers.settings/databases/settings.db";
+ private static final String SETTINGS_SECURE_TABLE = "secure";
+
+ // Actions in manifests which identify legacy icon packs
+ public static final String[] sSupportedActions = new String[] {
+ "org.adw.launcher.THEMES",
+ "com.gau.go.launcherex.theme"
+ };
+
+ // Categories in manifests which identify legacy icon packs
+ public static final String[] sSupportedCategories = new String[] {
+ "com.fede.launcher.THEME_ICONPACK",
+ "com.anddoes.launcher.THEME",
+ "com.teslacoilsw.launcher.THEME"
+ };
+
+
+ /**
+ * Get the root path of the resource cache for the given theme
+ * @param themePkgName
+ * @return Root resource cache path for the given theme
+ */
+ public static String getOverlayResourceCacheDir(String themePkgName) {
+ return RESOURCE_CACHE_DIR + themePkgName;
+ }
+
+ /**
+ * Get the path of the resource cache for the given target and theme
+ * @param targetPkgName
+ * @param themePkg
+ * @return Path to the resource cache for this target and theme
+ */
+ public static String getTargetCacheDir(String targetPkgName, PackageInfo themePkg) {
+ return getTargetCacheDir(targetPkgName, themePkg.packageName);
+ }
+
+ public static String getTargetCacheDir(String targetPkgName, PackageParser.Package themePkg) {
+ return getTargetCacheDir(targetPkgName, themePkg.packageName);
+ }
+
+ public static String getTargetCacheDir(String targetPkgName, String themePkgName) {
+ return getOverlayResourceCacheDir(themePkgName) + File.separator + targetPkgName;
+ }
+
+ /**
+ * Get the path to the icons for the given theme
+ * @param pkgName
+ * @return
+ */
+ public static String getIconPackDir(String pkgName) {
+ return getOverlayResourceCacheDir(pkgName) + File.separator + "icons";
+ }
+
+ public static String getIconHashFile(String pkgName) {
+ return getIconPackDir(pkgName) + File.separator + ICON_HASH_FILENAME;
+ }
+
+ public static String getIconPackApkPath(String pkgName) {
+ return getIconPackDir(pkgName) + "/resources.apk";
+ }
+
+ public static String getIconPackResPath(String pkgName) {
+ return getIconPackDir(pkgName) + "/resources.arsc";
+ }
+
+ public static String getIdmapPath(String targetPkgName, String overlayPkgName) {
+ return getTargetCacheDir(targetPkgName, overlayPkgName) + File.separator + "idmap";
+ }
+
+ public static String getOverlayPathToTarget(String targetPkgName) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(OVERLAY_PATH);
+ sb.append(targetPkgName);
+ sb.append('/');
+ return sb.toString();
+ }
+
+ public static String getCommonPackageName(String themePackageName) {
+ if (TextUtils.isEmpty(themePackageName)) return null;
+
+ return COMMON_RES_TARGET;
+ }
+
+ public static void createCacheDirIfNotExists() throws IOException {
+ File file = new File(RESOURCE_CACHE_DIR);
+ if (!file.exists() && !file.mkdir()) {
+ throw new IOException("Could not create dir: " + file.toString());
+ }
+ FileUtils.setPermissions(file, FileUtils.S_IRWXU
+ | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH, -1, -1);
+ }
+
+ public static void createResourcesDirIfNotExists(String targetPkgName, String overlayPkgName)
+ throws IOException {
+ createDirIfNotExists(getOverlayResourceCacheDir(overlayPkgName));
+ File file = new File(getTargetCacheDir(targetPkgName, overlayPkgName));
+ if (!file.exists() && !file.mkdir()) {
+ throw new IOException("Could not create dir: " + file.toString());
+ }
+ FileUtils.setPermissions(file, FileUtils.S_IRWXU
+ | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH, -1, -1);
+ }
+
+ public static void createIconDirIfNotExists(String pkgName) throws IOException {
+ createDirIfNotExists(getOverlayResourceCacheDir(pkgName));
+ File file = new File(getIconPackDir(pkgName));
+ if (!file.exists() && !file.mkdir()) {
+ throw new IOException("Could not create dir: " + file.toString());
+ }
+ FileUtils.setPermissions(file, FileUtils.S_IRWXU
+ | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH, -1, -1);
+ }
+
+ private static boolean dirExists(String dirPath) {
+ final File dir = new File(dirPath);
+ return dir.exists() && dir.isDirectory();
+ }
+
+ private static void createDirIfNotExists(String dirPath) {
+ if (!dirExists(dirPath)) {
+ File dir = new File(dirPath);
+ if (dir.mkdir()) {
+ FileUtils.setPermissions(dir, FileUtils.S_IRWXU |
+ FileUtils.S_IRWXG| FileUtils.S_IROTH | FileUtils.S_IXOTH, -1, -1);
+ }
+ }
+ }
+
+ /**
+ * Create SYSTEM_THEME_PATH directory if it does not exist
+ */
+ public static void createThemeDirIfNotExists() {
+ createDirIfNotExists(SYSTEM_THEME_PATH);
+ }
+
+ /**
+ * Create SYSTEM_FONT_PATH directory if it does not exist
+ */
+ public static void createFontDirIfNotExists() {
+ createDirIfNotExists(SYSTEM_THEME_FONT_PATH);
+ }
+
+ /**
+ * Create SYSTEM_THEME_RINGTONE_PATH directory if it does not exist
+ */
+ public static void createRingtoneDirIfNotExists() {
+ createDirIfNotExists(SYSTEM_THEME_RINGTONE_PATH);
+ }
+
+ /**
+ * Create SYSTEM_THEME_NOTIFICATION_PATH directory if it does not exist
+ */
+ public static void createNotificationDirIfNotExists() {
+ createDirIfNotExists(SYSTEM_THEME_NOTIFICATION_PATH);
+ }
+
+ /**
+ * Create SYSTEM_THEME_ALARM_PATH directory if it does not exist
+ */
+ public static void createAlarmDirIfNotExists() {
+ createDirIfNotExists(SYSTEM_THEME_ALARM_PATH);
+ }
+
+ /**
+ * Create SYSTEM_THEME_ICON_CACHE_DIR directory if it does not exist
+ */
+ public static void createIconCacheDirIfNotExists() {
+ createDirIfNotExists(SYSTEM_THEME_ICON_CACHE_DIR);
+ }
+
+ public static void clearIconCache() {
+ FileUtils.deleteContents(new File(SYSTEM_THEME_ICON_CACHE_DIR));
+ }
+
+ public static InputStream getInputStreamFromAsset(Context ctx, String path) throws IOException {
+ if (ctx == null || path == null)
+ return null;
+ InputStream is = null;
+ String ASSET_BASE = "file:///android_asset/";
+ path = path.substring(ASSET_BASE.length());
+ AssetManager assets = ctx.getAssets();
+ is = assets.open(path);
+ return is;
+ }
+
+ public static void closeQuietly(InputStream stream) {
+ if (stream == null)
+ return;
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+
+ public static void closeQuietly(OutputStream stream) {
+ if (stream == null)
+ return;
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+
+ /**
+ * Scale the boot animation to better fit the device by editing the desc.txt found
+ * in the bootanimation.zip
+ * @param context Context to use for getting an instance of the WindowManager
+ * @param input InputStream of the original bootanimation.zip
+ * @param dst Path to store the newly created bootanimation.zip
+ * @throws IOException
+ */
+ public static void copyAndScaleBootAnimation(Context context, InputStream input, String dst)
+ throws IOException {
+ final OutputStream os = new FileOutputStream(dst);
+ final ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(os));
+ final ZipInputStream bootAni = new ZipInputStream(new BufferedInputStream(input));
+ ZipEntry ze;
+
+ zos.setMethod(ZipOutputStream.STORED);
+ final byte[] bytes = new byte[4096];
+ int len;
+ while ((ze = bootAni.getNextEntry()) != null) {
+ ZipEntry entry = new ZipEntry(ze.getName());
+ entry.setMethod(ZipEntry.STORED);
+ entry.setCrc(ze.getCrc());
+ entry.setSize(ze.getSize());
+ entry.setCompressedSize(ze.getSize());
+ if (!ze.getName().equals("desc.txt")) {
+ // just copy this entry straight over into the output zip
+ zos.putNextEntry(entry);
+ while ((len = bootAni.read(bytes)) > 0) {
+ zos.write(bytes, 0, len);
+ }
+ } else {
+ String line;
+ BufferedReader reader = new BufferedReader(new InputStreamReader(bootAni));
+ final String[] info = reader.readLine().split(" ");
+
+ int scaledWidth;
+ int scaledHeight;
+ WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+ DisplayMetrics dm = new DisplayMetrics();
+ wm.getDefaultDisplay().getRealMetrics(dm);
+ // just in case the device is in landscape orientation we will
+ // swap the values since most (if not all) animations are portrait
+ if (dm.widthPixels > dm.heightPixels) {
+ scaledWidth = dm.heightPixels;
+ scaledHeight = dm.widthPixels;
+ } else {
+ scaledWidth = dm.widthPixels;
+ scaledHeight = dm.heightPixels;
+ }
+
+ int width = Integer.parseInt(info[0]);
+ int height = Integer.parseInt(info[1]);
+
+ if (width == height)
+ scaledHeight = scaledWidth;
+ else {
+ // adjust scaledHeight to retain original aspect ratio
+ float scale = (float)scaledWidth / (float)width;
+ int newHeight = (int)((float)height * scale);
+ if (newHeight < scaledHeight)
+ scaledHeight = newHeight;
+ }
+
+ CRC32 crc32 = new CRC32();
+ int size = 0;
+ ByteBuffer buffer = ByteBuffer.wrap(bytes);
+ line = String.format("%d %d %s\n", scaledWidth, scaledHeight, info[2]);
+ buffer.put(line.getBytes());
+ size += line.getBytes().length;
+ crc32.update(line.getBytes());
+ while ((line = reader.readLine()) != null) {
+ line = String.format("%s\n", line);
+ buffer.put(line.getBytes());
+ size += line.getBytes().length;
+ crc32.update(line.getBytes());
+ }
+ entry.setCrc(crc32.getValue());
+ entry.setSize(size);
+ entry.setCompressedSize(size);
+ zos.putNextEntry(entry);
+ zos.write(buffer.array(), 0, size);
+ }
+ zos.closeEntry();
+ }
+ zos.close();
+ }
+
+ public static boolean isValidAudible(String fileName) {
+ return (fileName != null &&
+ (fileName.endsWith(".mp3") || fileName.endsWith(".ogg")));
+ }
+
+ public static boolean setAudible(Context context, File ringtone, int type, String name) {
+ final String path = ringtone.getAbsolutePath();
+ final String mimeType = name.endsWith(".ogg") ? "audio/ogg" : "audio/mp3";
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.DATA, path);
+ values.put(MediaStore.MediaColumns.TITLE, name);
+ values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
+ values.put(MediaStore.MediaColumns.SIZE, ringtone.length());
+ values.put(MediaStore.Audio.Media.IS_RINGTONE, type == RingtoneManager.TYPE_RINGTONE);
+ values.put(MediaStore.Audio.Media.IS_NOTIFICATION,
+ type == RingtoneManager.TYPE_NOTIFICATION);
+ values.put(MediaStore.Audio.Media.IS_ALARM, type == RingtoneManager.TYPE_ALARM);
+ values.put(MediaStore.Audio.Media.IS_MUSIC, false);
+
+ Uri uri = MediaStore.Audio.Media.getContentUriForPath(path);
+ Uri newUri = null;
+ Cursor c = context.getContentResolver().query(uri,
+ new String[] {MediaStore.MediaColumns._ID},
+ MediaStore.MediaColumns.DATA + "='" + path + "'",
+ null, null);
+ if (c != null && c.getCount() > 0) {
+ c.moveToFirst();
+ long id = c.getLong(0);
+ c.close();
+ newUri = Uri.withAppendedPath(Uri.parse(MEDIA_CONTENT_URI), "" + id);
+ context.getContentResolver().update(uri, values,
+ MediaStore.MediaColumns._ID + "=" + id, null);
+ }
+ if (newUri == null)
+ newUri = context.getContentResolver().insert(uri, values);
+ try {
+ RingtoneManager.setActualDefaultRingtoneUri(context, type, newUri);
+ } catch (Exception e) {
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean setDefaultAudible(Context context, int type) {
+ final String audiblePath = getDefaultAudiblePath(type);
+ if (audiblePath != null) {
+ Uri uri = MediaStore.Audio.Media.getContentUriForPath(audiblePath);
+ Cursor c = context.getContentResolver().query(uri,
+ new String[] {MediaStore.MediaColumns._ID},
+ MediaStore.MediaColumns.DATA + "='" + audiblePath + "'",
+ null, null);
+ if (c != null && c.getCount() > 0) {
+ c.moveToFirst();
+ long id = c.getLong(0);
+ c.close();
+ uri = Uri.withAppendedPath(
+ Uri.parse(MEDIA_CONTENT_URI), "" + id);
+ }
+ if (uri != null)
+ RingtoneManager.setActualDefaultRingtoneUri(context, type, uri);
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ public static String getDefaultAudiblePath(int type) {
+ final String name;
+ final String path;
+ switch (type) {
+ case RingtoneManager.TYPE_ALARM:
+ name = SystemProperties.get("ro.config.alarm_alert", null);
+ path = name != null ? SYSTEM_ALARMS_PATH + File.separator + name : null;
+ break;
+ case RingtoneManager.TYPE_NOTIFICATION:
+ name = SystemProperties.get("ro.config.notification_sound", null);
+ path = name != null ? SYSTEM_NOTIFICATIONS_PATH + File.separator + name : null;
+ break;
+ case RingtoneManager.TYPE_RINGTONE:
+ name = SystemProperties.get("ro.config.ringtone", null);
+ path = name != null ? SYSTEM_RINGTONES_PATH + File.separator + name : null;
+ break;
+ default:
+ path = null;
+ break;
+ }
+ return path;
+ }
+
+ public static void clearAudibles(Context context, String audiblePath) {
+ final File audibleDir = new File(audiblePath);
+ if (audibleDir.exists()) {
+ String[] files = audibleDir.list();
+ final ContentResolver resolver = context.getContentResolver();
+ for (String s : files) {
+ final String filePath = audiblePath + File.separator + s;
+ Uri uri = MediaStore.Audio.Media.getContentUriForPath(filePath);
+ resolver.delete(uri, MediaStore.MediaColumns.DATA + "=\""
+ + filePath + "\"", null);
+ (new File(filePath)).delete();
+ }
+ }
+ }
+
+ public static Context createUiContext(final Context context) {
+ try {
+ Context uiContext = context.createPackageContext("com.android.systemui",
+ Context.CONTEXT_RESTRICTED);
+ return new ThemedUiContext(uiContext, context.getPackageName());
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+
+ return null;
+ }
+
+ public static void registerThemeChangeReceiver(final Context context,
+ final BroadcastReceiver receiver) {
+ IntentFilter filter = new IntentFilter(ACTION_THEME_CHANGED);
+
+ context.registerReceiver(receiver, filter);
+ }
+
+ public static String getLockscreenWallpaperPath(AssetManager assetManager) throws IOException {
+ String[] assets = assetManager.list("lockscreen");
+ String asset = getFirstNonEmptyAsset(assets);
+ if (asset == null) return null;
+ return "lockscreen/" + asset;
+ }
+
+ public static String getWallpaperPath(AssetManager assetManager) throws IOException {
+ String[] assets = assetManager.list("wallpapers");
+ String asset = getFirstNonEmptyAsset(assets);
+ if (asset == null) return null;
+ return "wallpapers/" + asset;
+ }
+
+ // Returns the first non-empty asset name. Empty assets can occur if the APK is built
+ // with folders included as zip entries in the APK. Searching for files inside "folderName" via
+ // assetManager.list("folderName") can cause these entries to be included as empty strings.
+ private static String getFirstNonEmptyAsset(String[] assets) {
+ if (assets == null) return null;
+ String filename = null;
+ for(String asset : assets) {
+ if (!asset.isEmpty()) {
+ filename = asset;
+ break;
+ }
+ }
+ return filename;
+ }
+
+ public static String getDefaultThemePackageName(Context context) {
+ final String defaultThemePkg = Settings.Secure.getString(context.getContentResolver(),
+ Settings.Secure.DEFAULT_THEME_PACKAGE);
+ if (!TextUtils.isEmpty(defaultThemePkg)) {
+ PackageManager pm = context.getPackageManager();
+ try {
+ if (pm.getPackageInfo(defaultThemePkg, 0) != null) {
+ return defaultThemePkg;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // doesn't exist so system will be default
+ Log.w(TAG, "Default theme " + defaultThemePkg + " not found", e);
+ }
+ }
+
+ return SYSTEM_DEFAULT;
+ }
+
+ private static class ThemedUiContext extends ContextWrapper {
+ private String mPackageName;
+
+ public ThemedUiContext(Context context, String packageName) {
+ super(context);
+ mPackageName = packageName;
+ }
+
+ @Override
+ public String getPackageName() {
+ return mPackageName;
+ }
+ }
+
+ // Returns a mutable list of all theme components
+ public static List<String> getAllComponents() {
+ List<String> components = new ArrayList<String>(9);
+ components.add(ThemesColumns.MODIFIES_FONTS);
+ components.add(ThemesColumns.MODIFIES_LAUNCHER);
+ components.add(ThemesColumns.MODIFIES_ALARMS);
+ components.add(ThemesColumns.MODIFIES_BOOT_ANIM);
+ components.add(ThemesColumns.MODIFIES_ICONS);
+ components.add(ThemesColumns.MODIFIES_LOCKSCREEN);
+ components.add(ThemesColumns.MODIFIES_NOTIFICATIONS);
+ components.add(ThemesColumns.MODIFIES_OVERLAYS);
+ components.add(ThemesColumns.MODIFIES_RINGTONES);
+ components.add(ThemesColumns.MODIFIES_STATUS_BAR);
+ components.add(ThemesColumns.MODIFIES_NAVIGATION_BAR);
+ return components;
+ }
+
+ /**
+ * Returns a mutable list of all the theme components supported by a given package
+ * NOTE: This queries the themes content provider. If there isn't a provider installed
+ * or if it is too early in the boot process this method will not work.
+ */
+ public static List<String> getSupportedComponents(Context context, String pkgName) {
+ List<String> supportedComponents = new ArrayList<String>();
+
+ String selection = ThemesContract.ThemesColumns.PKG_NAME + "= ?";
+ String[] selectionArgs = new String[]{ pkgName };
+ Cursor c = context.getContentResolver().query(ThemesContract.ThemesColumns.CONTENT_URI,
+ null, selection, selectionArgs, null);
+
+ if (c != null && c.moveToFirst()) {
+ List<String> allComponents = getAllComponents();
+ for(String component : allComponents) {
+ int index = c.getColumnIndex(component);
+ if (c.getInt(index) == 1) {
+ supportedComponents.add(component);
+ }
+ }
+ }
+ return supportedComponents;
+ }
+
+ /**
+ * Get the components from the default theme. If the default theme is not SYSTEM then any
+ * components that are not in the default theme will come from SYSTEM to create a complete
+ * component map.
+ * @param context
+ * @return
+ */
+ public static Map<String, String> getDefaultComponents(Context context) {
+ String defaultThemePkg = getDefaultThemePackageName(context);
+ List<String> defaultComponents = null;
+ List<String> systemComponents = getSupportedComponents(context, SYSTEM_DEFAULT);
+ if (!SYSTEM_DEFAULT.equals(defaultThemePkg)) {
+ defaultComponents = getSupportedComponents(context, defaultThemePkg);
+ }
+
+ Map<String, String> componentMap = new HashMap<String, String>(systemComponents.size());
+ if (defaultComponents != null) {
+ for (String component : defaultComponents) {
+ componentMap.put(component, defaultThemePkg);
+ }
+ }
+ for (String component : systemComponents) {
+ if (!componentMap.containsKey(component)) {
+ componentMap.put(component, SYSTEM_DEFAULT);
+ }
+ }
+
+ return componentMap;
+ }
+
+ /**
+ * Takes an existing component map and adds any missing components from the default
+ * map of components.
+ * @param context
+ * @param componentMap An existing component map
+ */
+ public static void completeComponentMap(Context context,
+ Map<String, String> componentMap) {
+ if (componentMap == null) return;
+
+ Map<String, String> defaultComponents = getDefaultComponents(context);
+ for (String component : defaultComponents.keySet()) {
+ if (!componentMap.containsKey(component)) {
+ componentMap.put(component, defaultComponents.get(component));
+ }
+ }
+ }
+
+ /**
+ * Get the boot theme by accessing the settings.db directly instead of using a content resolver.
+ * Only use this when the system is starting up and the settings content provider is not ready.
+ *
+ * Note: This method will only succeed if the system is calling this since normal apps will not
+ * be able to access the settings db path.
+ *
+ * @return The boot theme or null if unable to read the database or get the entry for theme
+ * config
+ */
+ public static ThemeConfig getBootThemeDirty() {
+ ThemeConfig config = null;
+ SQLiteDatabase db = null;
+ try {
+ db = SQLiteDatabase.openDatabase(SETTINGS_DB, null,
+ SQLiteDatabase.OPEN_READONLY);
+ if (db != null) {
+ String selection = "name=?";
+ String[] selectionArgs =
+ { Configuration.THEME_PKG_CONFIGURATION_PERSISTENCE_PROPERTY };
+ String[] columns = {"value"};
+ Cursor c = db.query(SETTINGS_SECURE_TABLE, columns, selection, selectionArgs,
+ null, null, null);
+ if (c != null) {
+ if (c.getCount() > 0) {
+ c.moveToFirst();
+ String json = c.getString(0);
+ if (json != null) {
+ config = ThemeConfig.fromJson(json);
+ }
+ }
+ c.close();
+ }
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to open " + SETTINGS_DB, e);
+ } finally {
+ if (db != null) {
+ db.close();
+ }
+ }
+
+ return config;
+ }
+}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index e413ed29be8..74a906ffb4e 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -21,9 +21,11 @@ import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;
+import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.util.ArrayList;
import java.util.HashMap;
/**
@@ -77,6 +79,16 @@ public final class AssetManager implements AutoCloseable {
private boolean mOpen = true;
private HashMap<Long, RuntimeException> mRefStacks;
+ private String mAppName;
+
+ private boolean mThemeSupport;
+ private String mThemePackageName;
+ private String mIconPackageName;
+ private String mCommonResPackageName;
+ private ArrayList<Integer> mThemeCookies = new ArrayList<Integer>(2);
+ private int mIconPackCookie;
+ private int mCommonResCookie;
+
/**
* Create a new AssetManager containing only the basic system assets.
* Applications will not generally use this method, instead retrieving the
@@ -252,6 +264,12 @@ public final class AssetManager implements AutoCloseable {
}
}
+ /*package*/ final void recreateStringBlocks() {
+ synchronized (this) {
+ makeStringBlocks(sSystem.mStringBlocks);
+ }
+ }
+
/*package*/ final void makeStringBlocks(StringBlock[] seed) {
final int seedNum = (seed != null) ? seed.length : 0;
final int num = getStringBlockCount();
@@ -629,17 +647,72 @@ public final class AssetManager implements AutoCloseable {
*
* {@hide}
*/
- public final int addOverlayPath(String idmapPath) {
+ // TODO: change the signature of this method to match addOverlayPathNative
+ public final int addOverlayPath(String idmapPath, String themeApkPath,
+ String resApkPath, String targetPkgPath, String prefixPath) {
synchronized (this) {
- int res = addOverlayPathNative(idmapPath);
- if (mStringBlocks != null) {
- makeStringBlocks(mStringBlocks);
+ return addOverlayPathNative(idmapPath, themeApkPath, resApkPath, targetPkgPath,
+ prefixPath);
+ }
+ }
+
+ private native final int addOverlayPathNative(String idmapPath, String themeApkPath,
+ String resApkPath, String targetPkgPath, String prefixPath);
+
+ /**
+ * Add a set of common assets.
+ *
+ * {@hide}
+ */
+ public final int addCommonOverlayPath(String themeApkPath,
+ String resApkPath, String prefixPath) {
+ synchronized (this) {
+ if ((new File(themeApkPath).exists()) && (new File(resApkPath).exists())) {
+ return addCommonOverlayPathNative(themeApkPath, resApkPath, prefixPath);
}
- return res;
+
+ return 0;
}
}
- private native final int addOverlayPathNative(String idmapPath);
+ private native final int addCommonOverlayPathNative(String themeApkPath,
+ String resApkPath, String prefixPath);
+
+ /**
+ * Add a set of assets as an icon pack. A pkgIdOverride value will change the package's id from
+ * what is in the resource table to a new value. Manage this carefully, if icon pack has more
+ * than one package then that next package's id will use pkgIdOverride+1.
+ *
+ * Icon packs are different from overlays as they have a different pkg id and
+ * do not use idmap so no targetPkg is required
+ *
+ * {@hide}
+ */
+ public final int addIconPath(String idmapPath, String resApkPath,
+ String prefixPath, int pkgIdOverride) {
+ synchronized (this) {
+ return addIconPathNative(idmapPath, resApkPath, prefixPath, pkgIdOverride);
+ }
+ }
+
+ private native final int addIconPathNative(String idmapPath,
+ String resApkPath, String prefixPath, int pkgIdOverride);
+
+ /**
+ * Delete a set of overlay assets from the asset manager. Not for use by
+ * applications. Returns true if succeeded or false on failure.
+ *
+ * Also works for icon packs
+ *
+ * {@hide}
+ */
+ public final boolean removeOverlayPath(String packageName, int cookie) {
+ synchronized (this) {
+ return removeOverlayPathNative(packageName, cookie);
+ }
+ }
+
+ private native final boolean removeOverlayPathNative(String packageName, int cookie);
/**
* Add multiple sets of assets to the asset manager at once. See
@@ -662,6 +735,126 @@ public final class AssetManager implements AutoCloseable {
}
/**
+ * Sets a flag indicating that this AssetManager should have themes
+ * attached, according to the initial request to create it by the
+ * ApplicationContext.
+ *
+ * {@hide}
+ */
+ public final void setThemeSupport(boolean themeSupport) {
+ mThemeSupport = themeSupport;
+ }
+
+ /**
+ * Should this AssetManager have themes attached, according to the initial
+ * request to create it by the ApplicationContext?
+ *
+ * {@hide}
+ */
+ public final boolean hasThemeSupport() {
+ return mThemeSupport;
+ }
+
+ /**
+ * Get package name of current icon pack (may return null).
+ * {@hide}
+ */
+ public String getIconPackageName() {
+ return mIconPackageName;
+ }
+
+ /**
+ * Sets icon package name
+ * {@hide}
+ */
+ public void setIconPackageName(String packageName) {
+ mIconPackageName = packageName;
+ }
+
+ /**
+ * Get package name of current common resources (may return null).
+ * {@hide}
+ */
+ public String getCommonResPackageName() {
+ return mCommonResPackageName;
+ }
+
+ /**
+ * Sets common resources package name
+ * {@hide}
+ */
+ public void setCommonResPackageName(String packageName) {
+ mCommonResPackageName = packageName;
+ }
+
+ /**
+ * Get package name of current theme (may return null).
+ * {@hide}
+ */
+ public String getThemePackageName() {
+ return mThemePackageName;
+ }
+
+ /**
+ * Sets package name and highest level style id for current theme (null, 0 is allowed).
+ * {@hide}
+ */
+ public void setThemePackageName(String packageName) {
+ mThemePackageName = packageName;
+ }
+
+ /**
+ * Get asset cookie for current theme (may return 0).
+ * {@hide}
+ */
+ public ArrayList<Integer> getThemeCookies() {
+ return mThemeCookies;
+ }
+
+ /** {@hide} */
+ public void setIconPackCookie(int cookie) {
+ mIconPackCookie = cookie;
+ }
+
+ /** {@hide} */
+ public int getIconPackCookie() {
+ return mIconPackCookie;
+ }
+
+ /** {@hide} */
+ public void setCommonResCookie(int cookie) {
+ mCommonResCookie = cookie;
+ }
+
+ /** {@hide} */
+ public int getCommonResCookie() {
+ return mCommonResCookie;
+ }
+
+ /**
+ * Sets asset cookie for current theme (0 if not a themed asset manager).
+ * {@hide}
+ */
+ public void addThemeCookie(int cookie) {
+ mThemeCookies.add(cookie);
+ }
+
+ /** {@hide} */
+ public String getAppName() {
+ return mAppName;
+ }
+
+ /** {@hide} */
+ public void setAppName(String pkgName) {
+ mAppName = pkgName;
+ }
+
+ /** {@hide} */
+ public boolean hasThemedAssets() {
+ return mThemeCookies.size() > 0;
+ }
+
+ /**
* Determine whether the state in this asset manager is up-to-date with
* the files on the filesystem. If false is returned, you need to
* instantiate a new AssetManager class to see the new data.
@@ -788,6 +981,26 @@ public final class AssetManager implements AutoCloseable {
/*package*/ native final int[] getStyleAttributes(int themeRes);
private native final void init(boolean isSystem);
+ /**
+ * {@hide}
+ */
+ public native final int getBasePackageCount();
+
+ /**
+ * {@hide}
+ */
+ public native final String getBasePackageName(int index);
+
+ /**
+ * {@hide}
+ */
+ public native final String getBaseResourcePackageName(int index);
+
+ /**
+ * {@hide}
+ */
+ public native final int getBasePackageId(int index);
+
private native final void destroy();
private final void incRefsLocked(long id) {
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index da35ee92267..47d5d0551ee 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -92,9 +92,15 @@ public class CompatibilityInfo implements Parcelable {
*/
public final float applicationInvertedScale;
+ /**
+ * Whether the application supports third-party theming.
+ */
+ public final boolean isThemeable;
+
public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
boolean forceCompat) {
int compatFlags = 0;
+ isThemeable = appInfo.isThemeable;
if (appInfo.requiresSmallestWidthDp != 0 || appInfo.compatibleWidthLimitDp != 0
|| appInfo.largestWidthLimitDp != 0) {
@@ -242,17 +248,19 @@ public class CompatibilityInfo implements Parcelable {
}
private CompatibilityInfo(int compFlags,
- int dens, float scale, float invertedScale) {
+ int dens, float scale, float invertedScale, boolean isThemeable) {
mCompatibilityFlags = compFlags;
applicationDensity = dens;
applicationScale = scale;
applicationInvertedScale = invertedScale;
+ this.isThemeable = isThemeable;
}
private CompatibilityInfo() {
this(NEVER_NEEDS_COMPAT, DisplayMetrics.DENSITY_DEVICE,
1.0f,
- 1.0f);
+ 1.0f,
+ true);
}
/**
@@ -526,6 +534,7 @@ public class CompatibilityInfo implements Parcelable {
if (applicationDensity != oc.applicationDensity) return false;
if (applicationScale != oc.applicationScale) return false;
if (applicationInvertedScale != oc.applicationInvertedScale) return false;
+ if (isThemeable != oc.isThemeable) return false;
return true;
} catch (ClassCastException e) {
return false;
@@ -563,6 +572,7 @@ public class CompatibilityInfo implements Parcelable {
result = 31 * result + applicationDensity;
result = 31 * result + Float.floatToIntBits(applicationScale);
result = 31 * result + Float.floatToIntBits(applicationInvertedScale);
+ result = 31 * result + (isThemeable ? 1 : 0);
return result;
}
@@ -577,6 +587,7 @@ public class CompatibilityInfo implements Parcelable {
dest.writeInt(applicationDensity);
dest.writeFloat(applicationScale);
dest.writeFloat(applicationInvertedScale);
+ dest.writeInt(isThemeable ? 1 : 0);
}
public static final Parcelable.Creator<CompatibilityInfo> CREATOR
@@ -597,5 +608,6 @@ public class CompatibilityInfo implements Parcelable {
applicationDensity = source.readInt();
applicationScale = source.readFloat();
applicationInvertedScale = source.readFloat();
+ isThemeable = source.readInt() == 1 ? true : false;
}
}
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 27bbb242f57..121c1375b55 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2008 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -82,6 +83,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration
public Locale locale;
/**
+ * @hide
+ */
+ public ThemeConfig themeConfig;
+
+ /**
* Locale should persist on setting. This is hidden because it is really
* questionable whether this is the right way to expose the functionality.
* @hide
@@ -412,7 +418,47 @@ public final class Configuration implements Parcelable, Comparable<Configuration
public static final int ORIENTATION_LANDSCAPE = 2;
/** @deprecated Not currently supported or used. */
@Deprecated public static final int ORIENTATION_SQUARE = 3;
-
+
+ /**
+ * @hide
+ * @deprecated
+ */
+ public static final String THEME_PACKAGE_NAME_PERSISTENCE_PROPERTY
+ = "persist.sys.themePackageName";
+
+ /**
+ * @hide
+ * @deprecated
+ */
+ public static final String THEME_ICONPACK_PACKAGE_NAME_PERSISTENCE_PROPERTY
+ = "themeIconPackPkgName";
+
+ /**
+ * @hide
+ * @deprecated
+ */
+ public static final String THEME_FONT_PACKAGE_NAME_PERSISTENCE_PROPERTY
+ = "themeFontPackPkgName";
+
+ /**
+ * @hide
+ * Serialized json structure mapping app pkgnames to their set theme.
+ *
+ * {
+ * "default":{
+ *" stylePkgName":"com.jasonevil.theme.miuiv5dark",
+ * "iconPkgName":"com.cyngn.hexo",
+ * "fontPkgName":"com.cyngn.hexo"
+ * }
+ * }
+
+ * If an app does not have a specific theme set then it will use the 'default' theme+
+ * example: 'default' -> overlayPkgName: 'org.blue.theme'
+ * 'com.android.phone' -> 'com.red.theme'
+ * 'com.google.vending' -> 'com.white.theme'
+ */
+ public static final String THEME_PKG_CONFIGURATION_PERSISTENCE_PROPERTY = "themeConfig";
+
/**
* Overall orientation of the screen. May be one of
* {@link #ORIENTATION_LANDSCAPE}, {@link #ORIENTATION_PORTRAIT}.
@@ -644,8 +690,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration
compatScreenHeightDp = o.compatScreenHeightDp;
compatSmallestScreenWidthDp = o.compatSmallestScreenWidthDp;
seq = o.seq;
+ if (o.themeConfig != null) {
+ themeConfig = (ThemeConfig) o.themeConfig.clone();
+ }
}
-
+
public String toString() {
StringBuilder sb = new StringBuilder(128);
sb.append("{");
@@ -780,6 +829,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
sb.append(" s.");
sb.append(seq);
}
+ sb.append(" themeResource=");
+ sb.append(themeConfig);
sb.append('}');
return sb.toString();
}
@@ -806,6 +857,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
smallestScreenWidthDp = compatSmallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
densityDpi = DENSITY_DPI_UNDEFINED;
seq = 0;
+ themeConfig = null;
}
/** {@hide} */
@@ -948,7 +1000,13 @@ public final class Configuration implements Parcelable, Comparable<Configuration
if (delta.seq != 0) {
seq = delta.seq;
}
-
+
+ if (delta.themeConfig != null
+ && (themeConfig == null || !themeConfig.equals(delta.themeConfig))) {
+ changed |= ActivityInfo.CONFIG_THEME_RESOURCE;
+ themeConfig = (ThemeConfig)delta.themeConfig.clone();
+ }
+
return changed;
}
@@ -1058,7 +1116,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration
&& densityDpi != delta.densityDpi) {
changed |= ActivityInfo.CONFIG_DENSITY;
}
-
+ if (delta.themeConfig != null &&
+ (themeConfig == null || !themeConfig.equals(delta.themeConfig))) {
+ changed |= ActivityInfo.CONFIG_THEME_RESOURCE;
+ }
return changed;
}
@@ -1074,7 +1135,9 @@ public final class Configuration implements Parcelable, Comparable<Configuration
* @return Return true if the resource needs to be loaded, else false.
*/
public static boolean needNewResources(int configChanges, int interestingChanges) {
- return (configChanges & (interestingChanges|ActivityInfo.CONFIG_FONT_SCALE)) != 0;
+ return (configChanges & (interestingChanges |
+ ActivityInfo.CONFIG_FONT_SCALE |
+ ActivityInfo.CONFIG_THEME_RESOURCE)) != 0;
}
/**
@@ -1147,6 +1210,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
dest.writeInt(compatScreenHeightDp);
dest.writeInt(compatSmallestScreenWidthDp);
dest.writeInt(seq);
+ dest.writeParcelable(themeConfig, flags);
}
public void readFromParcel(Parcel source) {
@@ -1175,6 +1239,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
compatScreenHeightDp = source.readInt();
compatSmallestScreenWidthDp = source.readInt();
seq = source.readInt();
+ themeConfig = source.readParcelable(ThemeConfig.class.getClassLoader());
}
public static final Parcelable.Creator<Configuration> CREATOR
@@ -1242,7 +1307,12 @@ public final class Configuration implements Parcelable, Comparable<Configuration
n = this.smallestScreenWidthDp - that.smallestScreenWidthDp;
if (n != 0) return n;
n = this.densityDpi - that.densityDpi;
- //if (n != 0) return n;
+ if (n != 0) return n;
+ if (this.themeConfig == null) {
+ if (that.themeConfig != null) return 1;
+ } else {
+ n = this.themeConfig.compareTo(that.themeConfig);
+ }
return n;
}
@@ -1279,6 +1349,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
result = 31 * result + screenHeightDp;
result = 31 * result + smallestScreenWidthDp;
result = 31 * result + densityDpi;
+ result = 31 * result + (this.themeConfig != null ?
+ this.themeConfig.hashCode() : 0);
return result;
}
diff --git a/core/java/android/content/res/IThemeChangeListener.aidl b/core/java/android/content/res/IThemeChangeListener.aidl
new file mode 100644
index 00000000000..a2e2abd6def
--- /dev/null
+++ b/core/java/android/content/res/IThemeChangeListener.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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 android.content.res;
+
+/** {@hide} */
+oneway interface IThemeChangeListener {
+ void onProgress(int progress);
+ void onFinish(boolean isSuccess);
+}
diff --git a/core/java/android/content/res/IThemeProcessingListener.aidl b/core/java/android/content/res/IThemeProcessingListener.aidl
new file mode 100644
index 00000000000..2e1c16e53bf
--- /dev/null
+++ b/core/java/android/content/res/IThemeProcessingListener.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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 android.content.res;
+
+/** {@hide} */
+oneway interface IThemeProcessingListener {
+ void onFinishedProcessing(String pkgName);
+}
diff --git a/core/java/android/content/res/IThemeService.aidl b/core/java/android/content/res/IThemeService.aidl
new file mode 100644
index 00000000000..e8bb5c4329e
--- /dev/null
+++ b/core/java/android/content/res/IThemeService.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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 android.content.res;
+
+import android.content.res.IThemeChangeListener;
+import android.content.res.IThemeProcessingListener;
+import android.graphics.Bitmap;
+
+import java.util.Map;
+
+/** {@hide} */
+interface IThemeService {
+ void requestThemeChangeUpdates(in IThemeChangeListener listener);
+ void removeUpdates(in IThemeChangeListener listener);
+
+ void requestThemeChange(in Map componentMap);
+ void applyDefaultTheme();
+ boolean isThemeApplying();
+ int getProgress();
+
+ boolean cacheComposedIcon(in Bitmap icon, String path);
+
+ boolean processThemeResources(String themePkgName);
+ boolean isThemeBeingProcessed(String themePkgName);
+ void registerThemeProcessingListener(in IThemeProcessingListener listener);
+ void unregisterThemeProcessingListener(in IThemeProcessingListener listener);
+}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 7f276c24dc7..cc8a6049298 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -16,6 +16,9 @@
package android.content.res;
+import android.app.ComposedIconInfo;
+import android.app.IconPackHelper;
+import android.app.IconPackHelper.IconCustomizer;
import android.util.Pools.SynchronizedPool;
import android.view.ViewDebug;
import com.android.internal.util.XmlUtils;
@@ -25,6 +28,7 @@ import org.xmlpull.v1.XmlPullParserException;
import android.annotation.Nullable;
import android.content.pm.ActivityInfo;
+import android.content.pm.PackageItemInfo;
import android.graphics.Movie;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ColorDrawable;
@@ -38,6 +42,7 @@ import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.TypedValue;
import android.util.LongSparseArray;
@@ -86,6 +91,20 @@ public class Resources {
private static final int ID_OTHER = 0x01000004;
+ // Package IDs for themes. Aapt will compile the res table with this id.
+ /** @hide */
+ public static final int THEME_FRAMEWORK_PKG_ID = 0x60;
+ /** @hide */
+ public static final int THEME_APP_PKG_ID = 0x61;
+ /** @hide */
+ public static final int THEME_ICON_PKG_ID = 0x62;
+ /**
+ * The common resource pkg id needs to be less than the THEME_FRAMEWORK_PKG_ID
+ * otherwise aapt will complain and fail
+ * @hide
+ */
+ public static final int THEME_COMMON_PKG_ID = THEME_FRAMEWORK_PKG_ID - 1;
+
private static final Object sSync = new Object();
// Information about preloaded resources. Note that they are not
@@ -136,6 +155,9 @@ public class Resources {
@SuppressWarnings("unused")
private WeakReference<IBinder> mToken;
+ private SparseArray<PackageItemInfo> mIcons;
+ private ComposedIconInfo mComposedIconInfo;
+
static {
sPreloadedDrawables = new LongSparseArray[2];
sPreloadedDrawables[0] = new LongSparseArray<ConstantState>();
@@ -230,7 +252,7 @@ public class Resources {
}
mToken = new WeakReference<IBinder>(token);
updateConfiguration(config, metrics);
- assets.ensureStringBlocks();
+ assets.recreateStringBlocks();
}
/**
@@ -745,6 +767,18 @@ public class Resources {
* not exist.
*/
public Drawable getDrawable(int id, @Nullable Theme theme) throws NotFoundException {
+ return getDrawable(id, theme, true);
+ }
+
+ /** @hide */
+ public Drawable getDrawable(int id, @Nullable Theme theme, boolean supportComposedIcons)
+ throws NotFoundException {
+ //Check if an icon is themed
+ PackageItemInfo info = mIcons != null ? mIcons.get(id) : null;
+ if (info != null && info.themedIcon != 0) {
+ id = info.themedIcon;
+ }
+
TypedValue value;
synchronized (mAccessLock) {
value = mTmpValue;
@@ -753,9 +787,24 @@ public class Resources {
} else {
mTmpValue = null;
}
- getValue(id, value, true);
+ getValue(id, value, true, supportComposedIcons);
+ }
+ Drawable res = null;
+ try {
+ res = loadDrawable(value, id, theme);
+ } catch (NotFoundException e) {
+ // The below statement will be true if we were trying to load a composed icon.
+ // Since we received a NotFoundException, try to load the original if this
+ // condition is true, otherwise throw the original exception.
+ if (supportComposedIcons && mComposedIconInfo != null && info != null &&
+ info.themedIcon == 0) {
+ Log.e(TAG, "Failed to retrieve composed icon.", e);
+ getValue(id, value, true, false);
+ res = loadDrawable(value, id, theme);
+ } else {
+ throw e;
+ }
}
- final Drawable res = loadDrawable(value, id, theme);
synchronized (mAccessLock) {
if (mTmpValue == null) {
mTmpValue = value;
@@ -808,6 +857,18 @@ public class Resources {
* not exist.
*/
public Drawable getDrawableForDensity(int id, int density, @Nullable Theme theme) {
+ return getDrawableForDensity(id, density, theme, true);
+ }
+
+ /** @hide */
+ public Drawable getDrawableForDensity(int id, int density, @Nullable Theme theme,
+ boolean supportComposedIcons) {
+ //Check if an icon was themed
+ PackageItemInfo info = mIcons != null ? mIcons.get(id) : null;
+ if (info != null && info.themedIcon != 0) {
+ id = info.themedIcon;
+ }
+
TypedValue value;
synchronized (mAccessLock) {
value = mTmpValue;
@@ -816,7 +877,7 @@ public class Resources {
} else {
mTmpValue = null;
}
- getValueForDensity(id, density, value, true);
+ getValueForDensity(id, density, value, true, supportComposedIcons);
/*
* Pretend the requested density is actually the display density. If
@@ -1226,8 +1287,24 @@ public class Resources {
*/
public void getValue(int id, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
+ getValue(id, outValue, resolveRefs, true);
+ }
+
+ /** @hide */
+ public void getValue(int id, TypedValue outValue, boolean resolveRefs,
+ boolean supportComposedIcons) throws NotFoundException {
+ //Check if an icon was themed
+ PackageItemInfo info = mIcons != null ? mIcons.get(id) : null;
+ if (info != null && info.themedIcon != 0) {
+ id = info.themedIcon;
+ }
boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
if (found) {
+ if (supportComposedIcons && IconPackHelper.shouldComposeIcon(mComposedIconInfo)
+ && info != null && info.themedIcon == 0) {
+ Drawable dr = loadDrawable(outValue, id, null);
+ IconCustomizer.getValue(this, id, outValue, dr);
+ }
return;
}
throw new NotFoundException("Resource ID #0x"
@@ -1249,8 +1326,44 @@ public class Resources {
*/
public void getValueForDensity(int id, int density, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
+ getValueForDensity(id, density, outValue, resolveRefs, true);
+ }
+
+ /** @hide */
+ public void getValueForDensity(int id, int density, TypedValue outValue, boolean resolveRefs,
+ boolean supportComposedIcons) throws NotFoundException {
+ //Check if an icon was themed
+ PackageItemInfo info = mIcons != null ? mIcons.get(id) : null;
+ if (info != null && info.themedIcon != 0) {
+ id = info.themedIcon;
+ }
boolean found = mAssets.getResourceValue(id, density, outValue, resolveRefs);
if (found) {
+ if (supportComposedIcons && IconPackHelper.shouldComposeIcon(mComposedIconInfo) &&
+ info != null && info.themedIcon == 0) {
+ int tmpDensity = outValue.density;
+ /*
+ * Pretend the requested density is actually the display density. If
+ * the drawable returned is not the requested density, then force it
+ * to be scaled later by dividing its density by the ratio of
+ * requested density to actual device density. Drawables that have
+ * undefined density or no density don't need to be handled here.
+ */
+ if (outValue.density > 0 && outValue.density != TypedValue.DENSITY_NONE) {
+ if (outValue.density == density) {
+ outValue.density = mMetrics.densityDpi;
+ } else {
+ outValue.density = (outValue.density * mMetrics.densityDpi) / density;
+ }
+ }
+ Drawable dr = loadDrawable(outValue, id, null);
+
+ // Return to original density. If we do not do this then
+ // the caller will get the wrong density for the given id and perform
+ // more of its own scaling in loadDrawable
+ outValue.density = tmpDensity;
+ IconCustomizer.getValue(this, id, outValue, dr);
+ }
return;
}
throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
@@ -1776,7 +1889,15 @@ public class Resources {
mTmpConfig.setLayoutDirection(mTmpConfig.locale);
}
configChanges = mConfiguration.updateFrom(mTmpConfig);
- configChanges = ActivityInfo.activityInfoConfigToNative(configChanges);
+
+ /* This is ugly, but modifying the activityInfoConfigToNative
+ * adapter would be messier */
+ if ((configChanges & ActivityInfo.CONFIG_THEME_RESOURCE) != 0) {
+ configChanges = ActivityInfo.activityInfoConfigToNative(configChanges);
+ configChanges |= ActivityInfo.CONFIG_THEME_RESOURCE;
+ } else {
+ configChanges = ActivityInfo.activityInfoConfigToNative(configChanges);
+ }
}
if (mConfiguration.locale == null) {
mConfiguration.locale = Locale.getDefault();
@@ -1848,6 +1969,19 @@ public class Resources {
private void clearDrawableCacheLocked(
LongSparseArray<WeakReference<ConstantState>> cache, int configChanges) {
+ /*
+ * Quick test to find out if the config change that occurred should
+ * trigger a full cache wipe.
+ */
+ if (Configuration.needNewResources(configChanges, 0)) {
+ if (DEBUG_CONFIG) {
+ Log.d(TAG, "Clear drawable cache from config changes: 0x"
+ + Integer.toHexString(configChanges));
+ }
+ cache.clear();
+ return;
+ }
+
if (DEBUG_CONFIG) {
Log.d(TAG, "Cleaning up drawables config changes: 0x"
+ Integer.toHexString(configChanges));
@@ -2278,6 +2412,13 @@ public class Resources {
return true;
}
+ /** @hide */
+ public final void updateStringCache() {
+ synchronized (mAccessLock) {
+ mAssets.recreateStringBlocks();
+ }
+ }
+
/*package*/ Drawable loadDrawable(TypedValue value, int id, Theme theme) throws NotFoundException {
if (TRACE_FOR_PRELOAD) {
// Log only framework resources
@@ -2316,9 +2457,10 @@ public class Resources {
// themeable attributes.
final ConstantState cs;
if (isColorDrawable) {
- cs = sPreloadedColorDrawables.get(key);
+ cs = mAssets.hasThemedAssets() ? null : sPreloadedColorDrawables.get(key);
} else {
- cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
+ cs = mAssets.hasThemedAssets() ? null :
+ sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
}
final Drawable dr;
@@ -2496,7 +2638,7 @@ public class Resources {
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
- csl = sPreloadedColorStateLists.get(key);
+ csl = mAssets.hasThemedAssets() ? null : sPreloadedColorStateLists.get(key);
if (csl != null) {
return csl;
}
@@ -2517,7 +2659,7 @@ public class Resources {
return csl;
}
- csl = sPreloadedColorStateLists.get(key);
+ csl = mAssets.hasThemedAssets() ? null : sPreloadedColorStateLists.get(key);
if (csl != null) {
return csl;
}
@@ -2664,6 +2806,21 @@ public class Resources {
}
}
+ /** @hide */
+ public void setIconResources(SparseArray<PackageItemInfo> icons) {
+ mIcons = icons;
+ }
+
+ /** @hide */
+ public void setComposedIconInfo(ComposedIconInfo iconInfo) {
+ mComposedIconInfo = iconInfo;
+ }
+
+ /** @hide */
+ public ComposedIconInfo getComposedIconInfo() {
+ return mComposedIconInfo;
+ }
+
private Resources() {
mAssets = AssetManager.getSystem();
// NOTE: Intentionally leaving this uninitialized (all values set
diff --git a/core/java/android/content/res/ResourcesKey.java b/core/java/android/content/res/ResourcesKey.java
index e0f1b3a3f07..e3ab161e754 100644
--- a/core/java/android/content/res/ResourcesKey.java
+++ b/core/java/android/content/res/ResourcesKey.java
@@ -22,6 +22,7 @@ import android.os.IBinder;
public final class ResourcesKey {
final String mResDir;
final float mScale;
+ final private boolean mIsThemeable;
private final int mHash;
private final IBinder mToken;
@@ -29,13 +30,14 @@ public final class ResourcesKey {
public final Configuration mOverrideConfiguration = new Configuration();
public ResourcesKey(String resDir, int displayId, Configuration overrideConfiguration,
- float scale, IBinder token) {
+ float scale, boolean isThemeable, IBinder token) {
mResDir = resDir;
mDisplayId = displayId;
if (overrideConfiguration != null) {
mOverrideConfiguration.setTo(overrideConfiguration);
}
mScale = scale;
+ mIsThemeable = isThemeable;
mToken = token;
int hash = 17;
@@ -44,6 +46,7 @@ public final class ResourcesKey {
hash = 31 * hash + (mOverrideConfiguration != null
? mOverrideConfiguration.hashCode() : 0);
hash = 31 * hash + Float.floatToIntBits(mScale);
+ hash = 31 * hash + (mIsThemeable ? 1 : 0);
mHash = hash;
}
@@ -83,7 +86,7 @@ public final class ResourcesKey {
if (mScale != peer.mScale) {
return false;
}
- return true;
+ return mIsThemeable == peer.mIsThemeable;
}
@Override
diff --git a/core/java/android/content/res/ThemeConfig.java b/core/java/android/content/res/ThemeConfig.java
new file mode 100644
index 00000000000..1b1837d7420
--- /dev/null
+++ b/core/java/android/content/res/ThemeConfig.java
@@ -0,0 +1,553 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod Project
+ * Portions copyright (C) 2014, T-Mobile USA, Inc.
+ *
+ * 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 android.content.res;
+
+import android.content.ContentResolver;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.JsonReader;
+import android.util.JsonToken;
+import android.util.JsonWriter;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+/**
+ * The Theme Configuration allows lookup of a theme element (fonts, icon, overlay) for a given
+ * application. If there isn't a particular theme designated to an app, it will fallback on the
+ * default theme. If there isn't a default theme then it will simply fallback to holo.
+ *
+ * @hide
+ */
+public class ThemeConfig implements Cloneable, Parcelable, Comparable<ThemeConfig> {
+ public static final String TAG = ThemeConfig.class.getCanonicalName();
+ public static final String SYSTEM_DEFAULT = "system";
+
+ /**
+ * Special package name for theming the navbar separate from the rest of SystemUI
+ */
+ public static final String SYSTEMUI_NAVBAR_PKG = "com.android.systemui.navbar";
+ public static final String SYSTEMUI_STATUS_BAR_PKG = "com.android.systemui";
+
+ // Key for any app which does not have a specific theme applied
+ private static final String KEY_DEFAULT_PKG = "default";
+ private static final SystemConfig mSystemConfig = new SystemConfig();
+ private static final SystemAppTheme mSystemAppTheme = new SystemAppTheme();
+
+ // Maps pkgname to theme (ex com.angry.birds -> red theme)
+ protected final Map<String, AppTheme> mThemes = new HashMap<String, AppTheme>();
+
+ public ThemeConfig(Map<String, AppTheme> appThemes) {
+ mThemes.putAll(appThemes);
+ }
+
+ public String getOverlayPkgName() {
+ AppTheme theme = getDefaultTheme();
+ return theme.mOverlayPkgName;
+ }
+
+ public String getOverlayForStatusBar() {
+ return getOverlayPkgNameForApp(SYSTEMUI_STATUS_BAR_PKG);
+ }
+
+ public String getOverlayForNavBar() {
+ return getOverlayPkgNameForApp(SYSTEMUI_NAVBAR_PKG);
+ }
+
+ public String getOverlayPkgNameForApp(String appPkgName) {
+ AppTheme theme = getThemeFor(appPkgName);
+ return theme.mOverlayPkgName;
+ }
+
+ public String getIconPackPkgName() {
+ AppTheme theme = getDefaultTheme();
+ return theme.mIconPkgName;
+ }
+
+ public String getIconPackPkgNameForApp(String appPkgName) {
+ AppTheme theme = getThemeFor(appPkgName);
+ return theme.mIconPkgName;
+ }
+
+ public String getFontPkgName() {
+ AppTheme defaultTheme = getDefaultTheme();
+ return defaultTheme.mFontPkgName;
+ }
+
+ public String getFontPkgNameForApp(String appPkgName) {
+ AppTheme theme = getThemeFor(appPkgName);
+ return theme.mFontPkgName;
+ }
+
+ private AppTheme getThemeFor(String pkgName) {
+ AppTheme theme = mThemes.get(pkgName);
+ if (theme == null) theme = getDefaultTheme();
+ return theme;
+ }
+
+ private AppTheme getDefaultTheme() {
+ AppTheme theme = mThemes.get(KEY_DEFAULT_PKG);
+ if (theme == null) theme = mSystemAppTheme;
+ return theme;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (object == this) {
+ return true;
+ }
+ if (object instanceof ThemeConfig) {
+ ThemeConfig o = (ThemeConfig) object;
+
+ Map<String, AppTheme> currThemes = (mThemes == null) ?
+ new HashMap<String, AppTheme>() : mThemes;
+ Map<String, AppTheme> newThemes = (o.mThemes == null) ?
+ new HashMap<String, AppTheme>() : o.mThemes;
+
+ return (currThemes.equals(newThemes));
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ if (mThemes != null) {
+ result.append("themes:");
+ result.append(mThemes);
+ }
+ return result.toString();
+ }
+
+ public String toJson() {
+ return JsonSerializer.toJson(this);
+ }
+
+ public static ThemeConfig fromJson(String json) {
+ return JsonSerializer.fromJson(json);
+ }
+
+ /**
+ * Represents the theme that the device booted into. This is used to
+ * simulate a "default" configuration based on the user's last known
+ * preference until the theme is switched at runtime.
+ */
+ public static ThemeConfig getBootTheme(ContentResolver resolver) {
+ ThemeConfig bootTheme = mSystemConfig;
+ try {
+ String json = Settings.Secure.getString(resolver,
+ Configuration.THEME_PKG_CONFIGURATION_PERSISTENCE_PROPERTY);
+ bootTheme = ThemeConfig.fromJson(json);
+
+ // Handle upgrade Case: Previously the theme configuration was in separate fields
+ if (bootTheme == null) {
+ String overlayPkgName = Settings.Secure.getString(resolver,
+ Configuration.THEME_PACKAGE_NAME_PERSISTENCE_PROPERTY);
+ String iconPackPkgName = Settings.Secure.getString(resolver,
+ Configuration.THEME_ICONPACK_PACKAGE_NAME_PERSISTENCE_PROPERTY);
+ String fontPkgName = Settings.Secure.getString(resolver,
+ Configuration.THEME_FONT_PACKAGE_NAME_PERSISTENCE_PROPERTY);
+
+ Builder builder = new Builder();
+ builder.defaultOverlay(overlayPkgName);
+ builder.defaultIcon(iconPackPkgName);
+ builder.defaultFont(fontPkgName);
+ bootTheme = builder.build();
+ }
+ } catch (SecurityException e) {
+ Log.e(TAG, "Could not get boot theme", e);
+ }
+ return bootTheme;
+ }
+
+ /**
+ * Represents the system framework theme, perceived by the system as there
+ * being no theme applied.
+ */
+ public static ThemeConfig getSystemTheme() {
+ return mSystemConfig;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ String json = JsonSerializer.toJson(this);
+ dest.writeString(json);
+ }
+
+ public static final Parcelable.Creator<ThemeConfig> CREATOR =
+ new Parcelable.Creator<ThemeConfig>() {
+ public ThemeConfig createFromParcel(Parcel source) {
+ String json = source.readString();
+ return JsonSerializer.fromJson(json);
+ }
+
+ public ThemeConfig[] newArray(int size) {
+ return new ThemeConfig[size];
+ }
+ };
+
+ @Override
+ public int compareTo(ThemeConfig o) {
+ if (o == null) return -1;
+ int n = 0;
+ n = mThemes.equals(o.mThemes) ? 0 : 1;
+ return n;
+ }
+
+ public Object clone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException e) {
+ Log.d(TAG, "clone not supported", e);
+ return null;
+ }
+ }
+
+ public static class AppTheme implements Cloneable, Comparable<AppTheme> {
+ // If any field is modified or added here be sure to change the serializer accordingly
+ String mOverlayPkgName;
+ String mIconPkgName;
+ String mFontPkgName;
+
+ public AppTheme(String overlayPkgName, String iconPkgName, String fontPkgName) {
+ mOverlayPkgName = overlayPkgName;
+ mIconPkgName = iconPkgName;
+ mFontPkgName = fontPkgName;
+ }
+
+ public String getIconPackPkgName() {
+ return mIconPkgName;
+ }
+
+ public String getOverlayPkgName() {
+ return mOverlayPkgName;
+ }
+
+ public String getFontPackPkgName() {
+ return mFontPkgName;
+ }
+
+ @Override
+ public synchronized int hashCode() {
+ int hash = 17;
+ hash = 31 * hash + (mOverlayPkgName == null ? 0 : mOverlayPkgName.hashCode());
+ hash = 31 * hash + (mIconPkgName == null ? 0 : mIconPkgName.hashCode());
+ hash = 31 * hash + (mFontPkgName == null ? 0 : mIconPkgName.hashCode());
+ return hash;
+ }
+
+ @Override
+ public int compareTo(AppTheme o) {
+ if (o == null) return -1;
+ int n = 0;
+ n = mIconPkgName.compareTo(o.mIconPkgName);
+ if (n != 0) return n;
+ n = mFontPkgName.compareTo(o.mFontPkgName);
+ if (n != 0) return n;
+ n = mOverlayPkgName.equals(o.mOverlayPkgName) ? 0 : 1;
+ return n;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (object == this) {
+ return true;
+ }
+ if (object instanceof AppTheme) {
+ AppTheme o = (AppTheme) object;
+ String currentOverlayPkgName = (mOverlayPkgName == null)? "" : mOverlayPkgName;
+ String newOverlayPkgName = (o.mOverlayPkgName == null)? "" : o.mOverlayPkgName;
+ String currentIconPkgName = (mIconPkgName == null)? "" : mIconPkgName;
+ String newIconPkgName = (o.mIconPkgName == null)? "" : o.mIconPkgName;
+ String currentFontPkgName = (mFontPkgName == null)? "" : mFontPkgName;
+ String newFontPkgName = (o.mFontPkgName == null)? "" : o.mFontPkgName;
+
+
+ return (currentIconPkgName.equals(newIconPkgName) &&
+ currentFontPkgName.equals(newFontPkgName) &&
+ currentOverlayPkgName.equals(newOverlayPkgName));
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ if (mOverlayPkgName != null) {
+ result.append("overlay:");
+ result.append(mOverlayPkgName);
+ }
+
+ if (!TextUtils.isEmpty(mIconPkgName)) {
+ result.append(", iconPack:");
+ result.append(mIconPkgName);
+ }
+
+ if (!TextUtils.isEmpty(mFontPkgName)) {
+ result.append(", fontPkg:");
+ result.append(mFontPkgName);
+ }
+ return result.toString();
+ }
+ }
+
+
+ public static class Builder {
+ private HashMap<String, String> mOverlays = new HashMap<String, String>();
+ private HashMap<String, String> mIcons = new HashMap<String, String>();
+ private HashMap<String, String> mFonts = new HashMap<String, String>();
+
+ public Builder() {}
+
+ public Builder(ThemeConfig theme) {
+ for(Map.Entry<String, AppTheme> entry : theme.mThemes.entrySet()) {
+ String key = entry.getKey();
+ AppTheme appTheme = entry.getValue();
+ mFonts.put(key, appTheme.getFontPackPkgName());
+ mIcons.put(key, appTheme.getIconPackPkgName());
+ mOverlays.put(key, appTheme.getOverlayPkgName());
+ }
+ }
+
+ /**
+ * For uniquely theming a specific app. ex. "Dialer gets red theme,
+ * Calculator gets blue theme"
+ */
+ public Builder defaultOverlay(String themePkgName) {
+ if (themePkgName != null) {
+ mOverlays.put(KEY_DEFAULT_PKG, themePkgName);
+ } else {
+ mOverlays.remove(KEY_DEFAULT_PKG);
+ }
+ return this;
+ }
+
+ public Builder defaultFont(String themePkgName) {
+ if (themePkgName != null) {
+ mFonts.put(KEY_DEFAULT_PKG, themePkgName);
+ } else {
+ mFonts.remove(KEY_DEFAULT_PKG);
+ }
+ return this;
+ }
+
+ public Builder defaultIcon(String themePkgName) {
+ if (themePkgName != null) {
+ mIcons.put(KEY_DEFAULT_PKG, themePkgName);
+ } else {
+ mIcons.remove(KEY_DEFAULT_PKG);
+ }
+ return this;
+ }
+
+ public Builder icon(String appPkgName, String themePkgName) {
+ if (themePkgName != null) {
+ mIcons.put(appPkgName, themePkgName);
+ } else {
+ mIcons.remove(appPkgName);
+ }
+ return this;
+ }
+
+ public Builder overlay(String appPkgName, String themePkgName) {
+ if (themePkgName != null) {
+ mOverlays.put(appPkgName, themePkgName);
+ } else {
+ mOverlays.remove(appPkgName);
+ }
+ return this;
+ }
+
+ public Builder font(String appPkgName, String themePkgName) {
+ if (themePkgName != null) {
+ mFonts.put(appPkgName, themePkgName);
+ } else {
+ mFonts.remove(appPkgName);
+ }
+ return this;
+ }
+
+ public ThemeConfig build() {
+ HashSet<String> appPkgSet = new HashSet<String>();
+ appPkgSet.addAll(mOverlays.keySet());
+ appPkgSet.addAll(mIcons.keySet());
+ appPkgSet.addAll(mFonts.keySet());
+
+ HashMap<String, AppTheme> appThemes = new HashMap<String, AppTheme>();
+ for(String appPkgName : appPkgSet) {
+ String icon = mIcons.get(appPkgName);
+ String overlay = mOverlays.get(appPkgName);
+ String font = mFonts.get(appPkgName);
+
+ AppTheme appTheme = new AppTheme(overlay, icon, font);
+ appThemes.put(appPkgName, appTheme);
+ }
+ return new ThemeConfig(appThemes);
+ }
+ }
+
+
+ public static class JsonSerializer {
+ private static final String NAME_OVERLAY_PKG = "mOverlayPkgName";
+ private static final String NAME_ICON_PKG = "mIconPkgName";
+ private static final String NAME_FONT_PKG = "mFontPkgName";
+
+ public static String toJson(ThemeConfig theme) {
+ String json = null;
+ Writer writer = null;
+ JsonWriter jsonWriter = null;
+ try {
+ writer = new StringWriter();
+ jsonWriter = new JsonWriter(writer);
+ writeTheme(jsonWriter, theme);
+ json = writer.toString();
+ } catch(IOException e) {
+ Log.e(TAG, "Could not write theme mapping", e);
+ } finally {
+ closeQuietly(writer);
+ closeQuietly(jsonWriter);
+ }
+ return json;
+ }
+
+ private static void writeTheme(JsonWriter writer, ThemeConfig theme)
+ throws IOException {
+ writer.beginObject();
+ for(Map.Entry<String, AppTheme> entry : theme.mThemes.entrySet()) {
+ String appPkgName = entry.getKey();
+ AppTheme appTheme = entry.getValue();
+ writer.name(appPkgName);
+ writeAppTheme(writer, appTheme);
+ }
+ writer.endObject();
+ }
+
+ private static void writeAppTheme(JsonWriter writer, AppTheme appTheme) throws IOException {
+ writer.beginObject();
+ writer.name(NAME_OVERLAY_PKG).value(appTheme.mOverlayPkgName);
+ writer.name(NAME_ICON_PKG).value(appTheme.mIconPkgName);
+ writer.name(NAME_FONT_PKG).value(appTheme.mFontPkgName);
+ writer.endObject();
+ }
+
+ public static ThemeConfig fromJson(String json) {
+ if (json == null) return null;
+ HashMap<String, AppTheme> map = new HashMap<String, AppTheme>();
+ StringReader reader = null;
+ JsonReader jsonReader = null;
+ try {
+ reader = new StringReader(json);
+ jsonReader = new JsonReader(reader);
+ jsonReader.beginObject();
+ while (jsonReader.hasNext()) {
+ String appPkgName = jsonReader.nextName();
+ AppTheme appTheme = readAppTheme(jsonReader);
+ map.put(appPkgName, appTheme);
+ }
+ jsonReader.endObject();
+ } catch(Exception e) {
+ Log.e(TAG, "Could not parse ThemeConfig from: " + json, e);
+ } finally {
+ closeQuietly(reader);
+ closeQuietly(jsonReader);
+ }
+ return new ThemeConfig(map);
+ }
+
+ private static AppTheme readAppTheme(JsonReader reader) throws IOException {
+ String overlay = null;
+ String icon = null;
+ String font = null;
+
+ reader.beginObject();
+ while(reader.hasNext()) {
+ String name = reader.nextName();
+ if (NAME_OVERLAY_PKG.equals(name) && reader.peek() != JsonToken.NULL) {
+ overlay = reader.nextString();
+ } else if (NAME_ICON_PKG.equals(name) && reader.peek() != JsonToken.NULL) {
+ icon = reader.nextString();
+ } else if (NAME_FONT_PKG.equals(name) && reader.peek() != JsonToken.NULL) {
+ font = reader.nextString();
+ } else {
+ reader.skipValue();
+ }
+ }
+ reader.endObject();
+
+ return new AppTheme(overlay, icon, font);
+ }
+
+ private static void closeQuietly(Reader reader) {
+ try {
+ if (reader != null) reader.close();
+ } catch(IOException e) {
+ }
+ }
+
+ private static void closeQuietly(JsonReader reader) {
+ try {
+ if (reader != null) reader.close();
+ } catch(IOException e) {
+ }
+ }
+
+ private static void closeQuietly(Writer writer) {
+ try {
+ if (writer != null) writer.close();
+ } catch(IOException e) {
+ }
+ }
+
+ private static void closeQuietly(JsonWriter writer) {
+ try {
+ if (writer != null) writer.close();
+ } catch(IOException e) {
+ }
+ }
+ }
+
+ public static class SystemConfig extends ThemeConfig {
+ public SystemConfig() {
+ super(new HashMap<String, AppTheme>());
+ }
+ }
+
+ public static class SystemAppTheme extends AppTheme {
+ public SystemAppTheme() {
+ super(SYSTEM_DEFAULT, SYSTEM_DEFAULT, SYSTEM_DEFAULT);
+ }
+
+ @Override
+ public String toString() {
+ return "No Theme Applied (Holo)";
+ }
+ }
+}
diff --git a/core/java/android/content/res/ThemeManager.java b/core/java/android/content/res/ThemeManager.java
new file mode 100644
index 00000000000..a9d2fcc942a
--- /dev/null
+++ b/core/java/android/content/res/ThemeManager.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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 android.content.res;
+
+import android.content.Context;
+import android.content.pm.ThemeUtils;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * {@hide}
+ */
+public class ThemeManager {
+ private static final String TAG = ThemeManager.class.getName();
+ private Context mContext;
+ private IThemeService mService;
+ private Handler mHandler;
+
+ private Set<ThemeChangeListener> mChangeListeners =
+ new HashSet<ThemeChangeListener>();
+
+ private Set<ThemeProcessingListener> mProcessingListeners =
+ new HashSet<ThemeProcessingListener>();
+
+ public ThemeManager(Context context, IThemeService service) {
+ mContext = context;
+ mService = service;
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+
+ private final IThemeChangeListener mThemeChangeListener = new IThemeChangeListener.Stub() {
+ @Override
+ public void onProgress(final int progress) throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mChangeListeners) {
+ List<ThemeChangeListener> listenersToRemove = new ArrayList
+ <ThemeChangeListener>();
+ for (ThemeChangeListener listener : mChangeListeners) {
+ try {
+ listener.onProgress(progress);
+ } catch (Throwable e) {
+ Log.w(TAG, "Unable to update theme change progress", e);
+ listenersToRemove.add(listener);
+ }
+ }
+ if (listenersToRemove.size() > 0) {
+ for (ThemeChangeListener listener : listenersToRemove) {
+ mChangeListeners.remove(listener);
+ }
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onFinish(final boolean isSuccess) throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mChangeListeners) {
+ List<ThemeChangeListener> listenersToRemove = new ArrayList
+ <ThemeChangeListener>();
+ for (ThemeChangeListener listener : mChangeListeners) {
+ try {
+ listener.onFinish(isSuccess);
+ } catch (Throwable e) {
+ Log.w(TAG, "Unable to update theme change listener", e);
+ listenersToRemove.add(listener);
+ }
+ }
+ if (listenersToRemove.size() > 0) {
+ for (ThemeChangeListener listener : listenersToRemove) {
+ mChangeListeners.remove(listener);
+ }
+ }
+ }
+ }
+ });
+ }
+ };
+
+ private final IThemeProcessingListener mThemeProcessingListener =
+ new IThemeProcessingListener.Stub() {
+ @Override
+ public void onFinishedProcessing(final String pkgName) throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mProcessingListeners) {
+ List<ThemeProcessingListener> listenersToRemove = new ArrayList
+ <ThemeProcessingListener>();
+ for (ThemeProcessingListener listener : mProcessingListeners) {
+ try {
+ listener.onFinishedProcessing(pkgName);
+ } catch (Throwable e) {
+ Log.w(TAG, "Unable to update theme change progress", e);
+ listenersToRemove.add(listener);
+ }
+ }
+ if (listenersToRemove.size() > 0) {
+ for (ThemeProcessingListener listener : listenersToRemove) {
+ mProcessingListeners.remove(listener);
+ }
+ }
+ }
+ }
+ });
+ }
+ };
+
+
+ public void addClient(ThemeChangeListener listener) {
+ synchronized (mChangeListeners) {
+ if (mChangeListeners.contains(listener)) {
+ throw new IllegalArgumentException("Client was already added ");
+ }
+ if (mChangeListeners.size() == 0) {
+ try {
+ mService.requestThemeChangeUpdates(mThemeChangeListener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to register listener", e);
+ }
+ }
+ mChangeListeners.add(listener);
+ }
+ }
+
+ public void removeClient(ThemeChangeListener listener) {
+ synchronized (mChangeListeners) {
+ mChangeListeners.remove(listener);
+ if (mChangeListeners.size() == 0) {
+ try {
+ mService.removeUpdates(mThemeChangeListener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to remove listener", e);
+ }
+ }
+ }
+ }
+
+ public void onClientPaused(ThemeChangeListener listener) {
+ removeClient(listener);
+ }
+
+ public void onClientResumed(ThemeChangeListener listener) {
+ addClient(listener);
+ }
+
+ public void onClientDestroyed(ThemeChangeListener listener) {
+ removeClient(listener);
+ }
+
+ /**
+ * Register a ThemeProcessingListener to be notified when a theme is done being processed.
+ * @param listener ThemeChangeListener to register
+ */
+ public void registerProcessingListener(ThemeProcessingListener listener) {
+ synchronized (mProcessingListeners) {
+ if (mProcessingListeners.contains(listener)) {
+ throw new IllegalArgumentException("Listener was already added ");
+ }
+ if (mProcessingListeners.size() == 0) {
+ try {
+ mService.registerThemeProcessingListener(mThemeProcessingListener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to register listener", e);
+ }
+ }
+ mProcessingListeners.add(listener);
+ }
+ }
+
+ /**
+ * Unregister a ThemeChangeListener.
+ * @param listener ThemeChangeListener to unregister
+ */
+ public void unregisterProcessingListener(ThemeChangeListener listener) {
+ synchronized (mProcessingListeners) {
+ mProcessingListeners.remove(listener);
+ if (mProcessingListeners.size() == 0) {
+ try {
+ mService.unregisterThemeProcessingListener(mThemeProcessingListener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to remove listener", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Convenience method. Applies the entire theme.
+ */
+ public void requestThemeChange(String pkgName) {
+ //List<String> components = ThemeUtils.getSupportedComponents(mContext, pkgName);
+ //requestThemeChange(pkgName, components);
+ }
+
+ public void requestThemeChange(String pkgName, List<String> components) {
+ Map<String, String> componentMap = new HashMap<String, String>(components.size());
+ for (String component : components) {
+ componentMap.put(component, pkgName);
+ }
+ requestThemeChange(componentMap);
+ }
+
+ public void requestThemeChange(Map<String, String> componentMap) {
+ try {
+ mService.requestThemeChange(componentMap);
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+ }
+
+ public void applyDefaultTheme() {
+ try {
+ mService.applyDefaultTheme();
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+ }
+
+ public boolean isThemeApplying() {
+ try {
+ return mService.isThemeApplying();
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+
+ return false;
+ }
+
+ public boolean isThemeBeingProcessed(String themePkgName) {
+ try {
+ return mService.isThemeBeingProcessed(themePkgName);
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+ return false;
+ }
+
+ public int getProgress() {
+ try {
+ return mService.getProgress();
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+ return -1;
+ }
+
+ public boolean processThemeResources(String themePkgName) {
+ try {
+ return mService.processThemeResources(themePkgName);
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+ return false;
+ }
+
+ private void logThemeServiceException(Exception e) {
+ Log.w(TAG, "Unable to access ThemeService", e);
+ }
+
+ public interface ThemeChangeListener {
+ void onProgress(int progress);
+ void onFinish(boolean isSuccess);
+ }
+
+ public interface ThemeProcessingListener {
+ void onFinishedProcessing(String pkgName);
+ }
+}
+
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 3ada9bb002e..855ae8c19ec 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -486,11 +486,12 @@ public class Process {
String abi,
String instructionSet,
String appDataDir,
+ boolean refreshTheme,
String[] zygoteArgs) {
try {
return startViaZygote(processClass, niceName, uid, gid, gids,
debugFlags, mountExternal, targetSdkVersion, seInfo,
- abi, instructionSet, appDataDir, zygoteArgs);
+ abi, instructionSet, appDataDir, refreshTheme, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
"Starting VM process through Zygote failed");
@@ -609,6 +610,7 @@ public class Process {
String abi,
String instructionSet,
String appDataDir,
+ boolean refreshTheme,
String[] extraArgs)
throws ZygoteStartFailedEx {
synchronized(Process.class) {
@@ -639,6 +641,9 @@ public class Process {
} else if (mountExternal == Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL) {
argsForZygote.add("--mount-external-multiuser-all");
}
+ if (refreshTheme) {
+ argsForZygote.add("--refresh_theme");
+ }
argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
//TODO optionally enable debuger
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 91053d52549..1b8db12721a 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5216,6 +5216,35 @@ public final class Settings {
public static final String PROTECTED_COMPONENTS = "protected_components";
/**
+ * Default theme to use. If empty, use holo.
+ * @hide
+ */
+ public static final String DEFAULT_THEME_PACKAGE = "default_theme_package";
+
+ /**
+ * A '|' delimited list of theme components to apply from the default theme on first boot.
+ * Components can be one or more of the "mods_XXXXXXX" found in
+ * {@link ThemesContract$ThemesColumns}. Leaving this field blank assumes all components
+ * will be applied.
+ *
+ * ex: mods_icons|mods_overlays|mods_homescreen
+ *
+ * @hide
+ */
+ public static final String DEFAULT_THEME_COMPONENTS = "default_theme_components";
+
+ /**
+ * This will be set to the system's current theme API version when ThemeService starts.
+ * It is useful for when an upgrade from one version of CM to another occurs.
+ * For example, after a user upgrades from CM11 to CM12, the value of this field
+ * might be 19. ThemeService would then change the value to 21. This is useful
+ * when an API change breaks a theme. Themeservice can identify old themes and
+ * unapply them from the system.
+ * @hide
+ */
+ public static final String THEME_PREV_BOOT_API_LEVEL = "theme_prev_boot_api_level";
+
+ /**
* This are the settings to be backed up.
*
* NOTE: Settings are backed up and restored in the order they appear
diff --git a/core/java/android/provider/ThemesContract.java b/core/java/android/provider/ThemesContract.java
new file mode 100644
index 00000000000..33fb09df512
--- /dev/null
+++ b/core/java/android/provider/ThemesContract.java
@@ -0,0 +1,565 @@
+package android.provider;
+
+import android.net.Uri;
+
+/**
+ * @hide
+ */
+public class ThemesContract {
+ public static final String AUTHORITY = "com.cyanogenmod.themes";
+ public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
+
+ public static class ThemesColumns {
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "themes");
+
+ /**
+ * The unique ID for a row.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String _ID = "_id";
+
+ /**
+ * The user visible title.
+ * <P>Type: TEXT</P>
+ */
+ public static final String TITLE = "title";
+
+ /**
+ * Unique text to identify the apk pkg. ie "com.foo.bar"
+ * <P>Type: TEXT</P>
+ */
+ public static final String PKG_NAME = "pkg_name";
+
+ /**
+ * A 32 bit RRGGBB color representative of the themes color scheme
+ * <P>Type: INTEGER</P>
+ */
+ public static final String PRIMARY_COLOR = "primary_color";
+
+ /**
+ * A 2nd 32 bit RRGGBB color representative of the themes color scheme
+ * <P>Type: INTEGER</P>
+ */
+ public static final String SECONDARY_COLOR = "secondary_color";
+
+ /**
+ * Name of the author of the theme
+ * <P>Type: TEXT</P>
+ */
+ public static final String AUTHOR = "author";
+
+ /**
+ * The time that this row was created on its originating client (msecs
+ * since the epoch).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String DATE_CREATED = "created";
+
+ /**
+ * URI to an image that shows the homescreen with the theme applied
+ * since the epoch).
+ * <P>Type: TEXT</P>
+ */
+ public static final String HOMESCREEN_URI = "homescreen_uri";
+
+ /**
+ * URI to an image that shows the lockscreen with theme applied
+ * <P>Type: TEXT</P>
+ */
+ public static final String LOCKSCREEN_URI = "lockscreen_uri";
+
+ /**
+ * URI to an image that shows the style (aka skin) with theme applied
+ * <P>Type: TEXT</P>
+ */
+ public static final String STYLE_URI = "style_uri";
+
+ /**
+ * TODO: Figure structure for actual animation instead of static
+ * URI to an image of the boot_anim.
+ * <P>Type: TEXT</P>
+ */
+ public static final String BOOT_ANIM_URI = "bootanim_uri";
+
+ /**
+ * URI to an image of the status bar for this theme.
+ * <P>Type: TEXT</P>
+ */
+ public static final String STATUSBAR_URI = "status_uri";
+
+ /**
+ * URI to an image of the fonts in this theme.
+ * <P>Type: TEXT</P>
+ */
+ public static final String FONT_URI = "font_uri";
+
+ /**
+ * URI to an image of the fonts in this theme.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ICON_URI = "icon_uri";
+
+ /**
+ * URI to an image of the fonts in this theme.
+ * <P>Type: TEXT</P>
+ */
+ public static final String OVERLAYS_URI = "overlays_uri";
+
+ /**
+ * 1 if theme modifies the launcher/homescreen else 0
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_LAUNCHER = "mods_homescreen";
+
+ /**
+ * 1 if theme modifies the lockscreen else 0
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_LOCKSCREEN = "mods_lockscreen";
+
+ /**
+ * 1 if theme modifies icons else 0
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_ICONS = "mods_icons";
+
+ /**
+ * 1 if theme modifies fonts
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_FONTS = "mods_fonts";
+
+ /**
+ * 1 if theme modifies boot animation
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_BOOT_ANIM = "mods_bootanim";
+
+ /**
+ * 1 if theme modifies notifications
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_NOTIFICATIONS = "mods_notifications";
+
+ /**
+ * 1 if theme modifies alarm sounds
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_ALARMS = "mods_alarms";
+
+ /**
+ * 1 if theme modifies ringtones
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_RINGTONES = "mods_ringtones";
+
+ /**
+ * 1 if theme has overlays
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_OVERLAYS = "mods_overlays";
+
+ /**
+ * 1 if theme has an overlay for SystemUI/StatusBar
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_STATUS_BAR = "mods_status_bar";
+
+ /**
+ * 1 if theme has an overlay for SystemUI/NavBar
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_NAVIGATION_BAR = "mods_navigation_bar";
+
+ /**
+ * URI to the theme's wallpaper. We should support multiple wallpaper
+ * but for now we will just have 1.
+ * <P>Type: TEXT</P>
+ */
+ public static final String WALLPAPER_URI = "wallpaper_uri";
+
+ /**
+ * 1 if this row should actually be presented as a theme to the user.
+ * For example if a "theme" only modifies one component (ex icons) then
+ * we do not present it to the user under the themes table.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String PRESENT_AS_THEME = "present_as_theme";
+
+ /**
+ * 1 if this theme is a legacy theme.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String IS_LEGACY_THEME = "is_legacy_theme";
+
+ /**
+ * 1 if this theme is the system default theme.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String IS_DEFAULT_THEME = "is_default_theme";
+
+ /**
+ * 1 if this theme is a legacy iconpack. A legacy icon pack is an APK that was written
+ * for Trebuchet or a 3rd party launcher.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String IS_LEGACY_ICONPACK = "is_legacy_iconpack";
+
+ /**
+ * install/update time in millisecs. When the row is inserted this column
+ * is populated by the PackageInfo. It is used for syncing to PM
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String LAST_UPDATE_TIME = "updateTime";
+
+ /**
+ * install time in millisecs. When the row is inserted this column
+ * is populated by the PackageInfo.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String INSTALL_TIME = "install_time";
+
+ /**
+ * The target API this theme supports
+ * is populated by the PackageInfo.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String TARGET_API = "target_api";
+ }
+
+ /**
+ * Key-value table which assigns a component (ex wallpaper) to a theme's package
+ */
+ public static class MixnMatchColumns {
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "mixnmatch");
+
+ /**
+ * The unique key for a row. See the KEY_* constants
+ * for valid examples
+ * <P>Type: TEXT</P>
+ */
+ public static final String COL_KEY = "key";
+
+ /**
+ * The package name that corresponds to a given component.
+ * <P>Type: String</P>
+ */
+ public static final String COL_VALUE = "value";
+
+ /**
+ * Valid keys
+ */
+ public static final String KEY_HOMESCREEN = "mixnmatch_homescreen";
+ public static final String KEY_LOCKSCREEN = "mixnmatch_lockscreen";
+ public static final String KEY_ICONS = "mixnmatch_icons";
+ public static final String KEY_STATUS_BAR = "mixnmatch_status_bar";
+ public static final String KEY_BOOT_ANIM = "mixnmatch_boot_anim";
+ public static final String KEY_FONT = "mixnmatch_font";
+ public static final String KEY_ALARM = "mixnmatch_alarm";
+ public static final String KEY_NOTIFICATIONS = "mixnmatch_notifications";
+ public static final String KEY_RINGTONE = "mixnmatch_ringtone";
+ public static final String KEY_OVERLAYS = "mixnmatch_overlays";
+ public static final String KEY_NAVIGATION_BAR = "mixnmatch_navigation_bar";
+
+ public static final String[] ROWS = { KEY_HOMESCREEN,
+ KEY_LOCKSCREEN,
+ KEY_ICONS,
+ KEY_STATUS_BAR,
+ KEY_BOOT_ANIM,
+ KEY_FONT,
+ KEY_NOTIFICATIONS,
+ KEY_RINGTONE,
+ KEY_ALARM,
+ KEY_OVERLAYS,
+ KEY_NAVIGATION_BAR
+ };
+
+ /**
+ * For a given key value in the MixNMatch table, return the column
+ * associated with it in the Themes Table. This is useful for URI based
+ * elements like wallpaper where the caller wishes to determine the
+ * wallpaper URI.
+ */
+ public static String componentToImageColName(String component) {
+ if (component.equals(MixnMatchColumns.KEY_HOMESCREEN)) {
+ return ThemesColumns.HOMESCREEN_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_LOCKSCREEN)) {
+ return ThemesColumns.LOCKSCREEN_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_BOOT_ANIM)) {
+ return ThemesColumns.BOOT_ANIM_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_FONT)) {
+ return ThemesColumns.FONT_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_ICONS)) {
+ return ThemesColumns.ICON_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_STATUS_BAR)) {
+ return ThemesColumns.STATUSBAR_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_NOTIFICATIONS)) {
+ throw new IllegalArgumentException("Notifications mixnmatch component does not have a related column");
+ } else if (component.equals(MixnMatchColumns.KEY_RINGTONE)) {
+ throw new IllegalArgumentException("Ringtone mixnmatch component does not have a related column");
+ } else if (component.equals(MixnMatchColumns.KEY_OVERLAYS)) {
+ return ThemesColumns.OVERLAYS_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_STATUS_BAR)) {
+ throw new IllegalArgumentException(
+ "Status bar mixnmatch component does not have a related column");
+ } else if (component.equals(MixnMatchColumns.KEY_NAVIGATION_BAR)) {
+ throw new IllegalArgumentException(
+ "Navigation bar mixnmatch component does not have a related column");
+ }
+ return null;
+ }
+
+ /**
+ * A component in the themes table (IE "mods_wallpaper") has an
+ * equivalent key in mixnmatch table
+ */
+ public static String componentToMixNMatchKey(String component) {
+ if (component.equals(ThemesColumns.MODIFIES_LAUNCHER)) {
+ return MixnMatchColumns.KEY_HOMESCREEN;
+ } else if (component.equals(ThemesColumns.MODIFIES_ICONS)) {
+ return MixnMatchColumns.KEY_ICONS;
+ } else if (component.equals(ThemesColumns.MODIFIES_LOCKSCREEN)) {
+ return MixnMatchColumns.KEY_LOCKSCREEN;
+ } else if (component.equals(ThemesColumns.MODIFIES_FONTS)) {
+ return MixnMatchColumns.KEY_FONT;
+ } else if (component.equals(ThemesColumns.MODIFIES_BOOT_ANIM)) {
+ return MixnMatchColumns.KEY_BOOT_ANIM;
+ } else if (component.equals(ThemesColumns.MODIFIES_ALARMS)) {
+ return MixnMatchColumns.KEY_ALARM;
+ } else if (component.equals(ThemesColumns.MODIFIES_NOTIFICATIONS)) {
+ return MixnMatchColumns.KEY_NOTIFICATIONS;
+ } else if (component.equals(ThemesColumns.MODIFIES_RINGTONES)) {
+ return MixnMatchColumns.KEY_RINGTONE;
+ } else if (component.equals(ThemesColumns.MODIFIES_OVERLAYS)) {
+ return MixnMatchColumns.KEY_OVERLAYS;
+ } else if (component.equals(ThemesColumns.MODIFIES_STATUS_BAR)) {
+ return MixnMatchColumns.KEY_STATUS_BAR;
+ } else if (component.equals(ThemesColumns.MODIFIES_NAVIGATION_BAR)) {
+ return MixnMatchColumns.KEY_NAVIGATION_BAR;
+ }
+ return null;
+ }
+
+ /**
+ * A mixnmatch key in has an
+ * equivalent value in the themes table
+ */
+ public static String mixNMatchKeyToComponent(String mixnmatchKey) {
+ if (mixnmatchKey.equals(MixnMatchColumns.KEY_HOMESCREEN)) {
+ return ThemesColumns.MODIFIES_LAUNCHER;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_ICONS)) {
+ return ThemesColumns.MODIFIES_ICONS;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_LOCKSCREEN)) {
+ return ThemesColumns.MODIFIES_LOCKSCREEN;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_FONT)) {
+ return ThemesColumns.MODIFIES_FONTS;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_BOOT_ANIM)) {
+ return ThemesColumns.MODIFIES_BOOT_ANIM;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_ALARM)) {
+ return ThemesColumns.MODIFIES_ALARMS;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_NOTIFICATIONS)) {
+ return ThemesColumns.MODIFIES_NOTIFICATIONS;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_RINGTONE)) {
+ return ThemesColumns.MODIFIES_RINGTONES;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_OVERLAYS)) {
+ return ThemesColumns.MODIFIES_OVERLAYS;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_STATUS_BAR)) {
+ return ThemesColumns.MODIFIES_STATUS_BAR;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_NAVIGATION_BAR)) {
+ return ThemesColumns.MODIFIES_NAVIGATION_BAR;
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Table containing cached preview blobs for a given theme
+ */
+ public static class PreviewColumns {
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "previews");
+
+ /**
+ * Uri for retrieving the previews for the currently applied components.
+ * Querying the themes provider using this URI will return a cursor with a single row
+ * containing all the previews for the components that are currently applied.
+ */
+ public static final Uri APPLIED_URI = Uri.withAppendedPath(AUTHORITY_URI,
+ "applied_previews");
+
+ /**
+ * The unique ID for a row.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String _ID = "_id";
+
+ /**
+ * The unique ID for the theme these previews belong to.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String THEME_ID = "theme_id";
+
+ /**
+ * Cached image of the themed status bar background.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String STATUSBAR_BACKGROUND = "statusbar_background";
+
+ /**
+ * Cached image of the themed bluetooth status icon.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String STATUSBAR_BLUETOOTH_ICON = "statusbar_bluetooth_icon";
+
+ /**
+ * Cached image of the themed wifi status icon.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String STATUSBAR_WIFI_ICON = "statusbar_wifi_icon";
+
+ /**
+ * Cached image of the themed cellular signal status icon.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String STATUSBAR_SIGNAL_ICON = "statusbar_signal_icon";
+
+ /**
+ * Cached image of the themed battery using portrait style.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String STATUSBAR_BATTERY_PORTRAIT = "statusbar_battery_portrait";
+
+ /**
+ * Cached image of the themed battery using landscape style.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String STATUSBAR_BATTERY_LANDSCAPE = "statusbar_battery_landscape";
+
+ /**
+ * Cached image of the themed battery using circle style.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String STATUSBAR_BATTERY_CIRCLE = "statusbar_battery_circle";
+
+ /**
+ * The themed margin value between the wifi and rssi signal icons.
+ * <P>Type: INTEGER (int)</P>
+ */
+ public static final String STATUSBAR_WIFI_COMBO_MARGIN_END = "wifi_combo_margin_end";
+
+ /**
+ * The themed color used for clock text in the status bar.
+ * <P>Type: INTEGER (int)</P>
+ */
+ public static final String STATUSBAR_CLOCK_TEXT_COLOR = "statusbar_clock_text_color";
+
+ /**
+ * Cached image of the themed navigation bar background.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String NAVBAR_BACKGROUND = "navbar_background";
+
+ /**
+ * Cached image of the themed back button.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String NAVBAR_BACK_BUTTON = "navbar_back_button";
+
+ /**
+ * Cached image of the themed home button.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String NAVBAR_HOME_BUTTON = "navbar_home_button";
+
+ /**
+ * Cached image of the themed recents button.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String NAVBAR_RECENT_BUTTON = "navbar_recent_button";
+
+ /**
+ * Cached image of the 1/4 icons
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String ICON_PREVIEW_1 = "icon_preview_1";
+
+ /**
+ * Cached image of the 2/4 icons
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String ICON_PREVIEW_2 = "icon_preview_2";
+
+ /**
+ * Cached image of the 3/4 icons
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String ICON_PREVIEW_3 = "icon_preview_3";
+
+ /**
+ * Cached image of the 4/4 icons
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String ICON_PREVIEW_4 = "icon_preview_4";
+
+ /**
+ * Cached preview of UI controls representing the theme's style
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String STYLE_PREVIEW = "style_preview";
+
+ /**
+ * Cached thumbnail preview of UI controls representing the theme's style
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String STYLE_THUMBNAIL = "style_thumbnail";
+
+ /**
+ * Cached thumbnail of the theme's boot animation
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String BOOTANIMATION_THUMBNAIL = "bootanimation_thumbnail";
+
+ /**
+ * Cached thumbnail of the theme's wallpaper
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String WALLPAPER_THUMBNAIL = "wallpaper_thumbnail";
+
+ /**
+ * Cached preview of the theme's wallpaper which is larger than the thumbnail
+ * but smaller than the full sized wallpaper.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String WALLPAPER_PREVIEW = "wallpaper_preview";
+
+ /**
+ * Cached thumbnail of the theme's lockscreen wallpaper
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String LOCK_WALLPAPER_THUMBNAIL = "lock_wallpaper_thumbnail";
+
+ /**
+ * Cached preview of the theme's lockscreen wallpaper which is larger than the thumbnail
+ * but smaller than the full sized lockscreen wallpaper.
+ * <P>Type: BLOB (bitmap)</P>
+ */
+ public static final String LOCK_WALLPAPER_PREVIEW = "lock_wallpaper_preview";
+ }
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 6aa86c7a0be..d5afdf8f421 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -125,7 +125,7 @@ interface IWindowManager
boolean inKeyguardRestrictedInputMode();
void dismissKeyguard();
void keyguardGoingAway(boolean disableWindowAnimations,
- boolean keyguardGoingToNotificationShade);
+ boolean keyguardGoingToNotificationShade, boolean keyguardShowingMedia);
void closeSystemDialogs(String reason);
@@ -223,6 +223,16 @@ interface IWindowManager
int maxHeight, boolean force565);
/**
+ * Get the current x offset for the wallpaper
+ */
+ int getLastWallpaperX();
+
+ /**
+ * Get the current y offset for the wallpaper
+ */
+ int getLastWallpaperY();
+
+ /**
* Called by the status bar to notify Views of changes to System UI visiblity.
*/
oneway void statusBarVisibilityChanged(int visibility);
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 037ed281be1..789f8ad8b64 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -182,6 +182,16 @@ interface IWindowSession {
*/
void setWallpaperDisplayOffset(IBinder windowToken, int x, int y);
+ /**
+ * Get the current x offset for the wallpaper
+ */
+ int getLastWallpaperX();
+
+ /**
+ * Get the current y offset for the wallpaper
+ */
+ int getLastWallpaperY();
+
Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
int z, in Bundle extras, boolean sync);
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 673f075e6c1..a1e0dc775fd 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -758,7 +758,8 @@ public interface WindowManagerPolicy {
* Create and return an animation to let the wallpaper disappear after being shown on a force
* hiding window.
*/
- public Animation createForceHideWallpaperExitAnimation(boolean goingToNotificationShade);
+ public Animation createForceHideWallpaperExitAnimation(boolean goingToNotificationShade,
+ boolean keyguardShowingMedia);
/**
* Called from the input reader thread before a key is enqueued.
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 7cb3c378ace..2c0fca1bc9e 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -2516,6 +2516,12 @@ public class RemoteViews implements Parcelable, Filter {
/** @hide */
public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
+ return apply(context, parent, handler, null);
+ }
+
+ /** @hide */
+ public View apply(Context context, ViewGroup parent, OnClickHandler handler,
+ String themePackageName) {
RemoteViews rvToApply = getRemoteViewsToApply(context);
View result;
@@ -2523,7 +2529,7 @@ public class RemoteViews implements Parcelable, Filter {
// user. So build a context that loads resources from that user but
// still returns the current users userId so settings like data / time formats
// are loaded without requiring cross user persmissions.
- final Context contextForResources = getContextForResources(context);
+ final Context contextForResources = getContextForResources(context, themePackageName);
Context inflationContext = new ContextWrapper(context) {
@Override
public Resources getResources() {
@@ -2589,7 +2595,7 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- private Context getContextForResources(Context context) {
+ private Context getContextForResources(Context context, String themePackageName) {
if (mApplication != null) {
if (context.getUserId() == UserHandle.getUserId(mApplication.uid)
&& context.getPackageName().equals(mApplication.packageName)) {
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 2ef8a20f7d9..272788d362f 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -16,6 +16,7 @@
package com.android.internal.os;
+import android.graphics.Typeface;
import android.net.Credentials;
import android.net.LocalSocket;
import android.os.Process;
@@ -214,6 +215,10 @@ class ZygoteConnection {
ZygoteInit.setCloseOnExec(serverPipeFd, true);
}
+ if (parsedArgs.refreshTheme) {
+ Typeface.recreateDefaults();
+ }
+
/**
* In order to avoid leaking descriptors to the Zygote child,
* the native code must close the two Zygote socket descriptors
@@ -410,6 +415,9 @@ class ZygoteConnection {
*/
String appDataDir;
+ /** from --refresh_theme */
+ boolean refreshTheme;
+
/**
* Constructs instance and parses args
* @param args zygote command-line args
@@ -569,6 +577,8 @@ class ZygoteConnection {
instructionSet = arg.substring(arg.indexOf('=') + 1);
} else if (arg.startsWith("--app-data-dir=")) {
appDataDir = arg.substring(arg.indexOf('=') + 1);
+ } else if (arg.equals("--refresh_theme")) {
+ refreshTheme = true;
} else {
break;
}
diff --git a/core/java/com/android/internal/util/cm/ImageUtils.java b/core/java/com/android/internal/util/cm/ImageUtils.java
new file mode 100644
index 00000000000..d7803840fe9
--- /dev/null
+++ b/core/java/com/android/internal/util/cm/ImageUtils.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2013-2014 The CyanogenMod 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.internal.util.cm;
+
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.pm.ThemeUtils;
+import android.content.res.AssetManager;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Point;
+import android.net.Uri;
+import android.provider.ThemesContract;
+import android.provider.ThemesContract.ThemesColumns;
+import android.text.TextUtils;
+import android.util.Log;
+import android.webkit.URLUtil;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import libcore.io.IoUtils;
+
+public class ImageUtils {
+ private static final String TAG = ImageUtils.class.getSimpleName();
+
+ private static final String ASSET_URI_PREFIX = "file:///android_asset/";
+ private static final int DEFAULT_IMG_QUALITY = 100;
+
+ /**
+ * Gets the Width and Height of the image
+ *
+ * @param inputStream The input stream of the image
+ *
+ * @return A point structure that holds the Width and Height (x and y)/*"
+ */
+ public static Point getImageDimension(InputStream inputStream) {
+ if (inputStream == null) {
+ throw new IllegalArgumentException("'inputStream' cannot be null!");
+ }
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(inputStream, null, options);
+ Point point = new Point(options.outWidth,options.outHeight);
+ return point;
+ }
+
+ /**
+ * Crops the input image and returns a new InputStream of the cropped area
+ *
+ * @param inputStream The input stream of the image
+ * @param imageWidth Width of the input image
+ * @param imageHeight Height of the input image
+ * @param inputStream Desired Width
+ * @param inputStream Desired Width
+ *
+ * @return a new InputStream of the cropped area/*"
+ */
+ public static InputStream cropImage(InputStream inputStream, int imageWidth, int imageHeight,
+ int outWidth, int outHeight) throws IllegalArgumentException {
+ if (inputStream == null){
+ throw new IllegalArgumentException("inputStream cannot be null");
+ }
+
+ if (imageWidth <= 0 || imageHeight <= 0) {
+ throw new IllegalArgumentException(
+ String.format("imageWidth and imageHeight must be > 0: imageWidth=%d" +
+ " imageHeight=%d", imageWidth, imageHeight));
+ }
+
+ if (outWidth <= 0 || outHeight <= 0) {
+ throw new IllegalArgumentException(
+ String.format("outWidth and outHeight must be > 0: outWidth=%d" +
+ " outHeight=%d", imageWidth, outHeight));
+ }
+
+ int scaleDownSampleSize = Math.min(imageWidth / outWidth, imageHeight / outHeight);
+ if (scaleDownSampleSize > 0) {
+ imageWidth /= scaleDownSampleSize;
+ imageHeight /= scaleDownSampleSize;
+ } else {
+ float ratio = (float) outWidth / outHeight;
+ if (imageWidth < imageHeight * ratio) {
+ outWidth = imageWidth;
+ outHeight = (int) (outWidth / ratio);
+ } else {
+ outHeight = imageHeight;
+ outWidth = (int) (outHeight * ratio);
+ }
+ }
+ int left = (imageWidth - outWidth) / 2;
+ int top = (imageHeight - outHeight) / 2;
+ InputStream compressed = null;
+ try {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ if (scaleDownSampleSize > 1) {
+ options.inSampleSize = scaleDownSampleSize;
+ }
+ Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);
+ if (bitmap == null) {
+ return null;
+ }
+ Bitmap cropped = Bitmap.createBitmap(bitmap, left, top, outWidth, outHeight);
+ ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
+ if (cropped.compress(Bitmap.CompressFormat.PNG, DEFAULT_IMG_QUALITY, tmpOut)) {
+ byte[] outByteArray = tmpOut.toByteArray();
+ compressed = new ByteArrayInputStream(outByteArray);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception " + e);
+ }
+ return compressed;
+ }
+
+ /**
+ * Crops the lock screen image and returns a new InputStream of the cropped area
+ *
+ * @param pkgName Name of the theme package
+ * @param context The context
+ *
+ * @return a new InputStream of the cropped image/*"
+ */
+ public static InputStream getCroppedKeyguardStream(String pkgName, Context context)
+ throws IllegalArgumentException {
+ if (TextUtils.isEmpty(pkgName)) {
+ throw new IllegalArgumentException("'pkgName' cannot be null or empty!");
+ }
+ if (context == null) {
+ throw new IllegalArgumentException("'context' cannot be null!");
+ }
+
+ InputStream cropped = null;
+ InputStream stream = null;
+ try {
+ stream = getOriginalKeyguardStream(pkgName, context);
+ if (stream == null) {
+ return null;
+ }
+ Point point = getImageDimension(stream);
+ IoUtils.closeQuietly(stream);
+ if (point == null || point.x == 0 || point.y == 0) {
+ return null;
+ }
+ WallpaperManager wm = WallpaperManager.getInstance(context);
+ int outWidth = wm.getDesiredMinimumWidth();
+ int outHeight = wm.getDesiredMinimumHeight();
+ stream = getOriginalKeyguardStream(pkgName, context);
+ if (stream == null) {
+ return null;
+ }
+ cropped = cropImage(stream, point.x, point.y, outWidth, outHeight);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception " + e);
+ } finally {
+ IoUtils.closeQuietly(stream);
+ }
+ return cropped;
+ }
+
+ /**
+ * Crops the wallpaper image and returns a new InputStream of the cropped area
+ *
+ * @param pkgName Name of the theme package
+ * @param context The context
+ *
+ * @return a new InputStream of the cropped image/*"
+ */
+ public static InputStream getCroppedWallpaperStream(String pkgName, Context context) {
+ if (TextUtils.isEmpty(pkgName)) {
+ throw new IllegalArgumentException("'pkgName' cannot be null or empty!");
+ }
+ if (context == null) {
+ throw new IllegalArgumentException("'context' cannot be null!");
+ }
+
+ InputStream cropped = null;
+ InputStream stream = null;
+ try {
+ stream = getOriginalWallpaperStream(pkgName, context);
+ if (stream == null) {
+ return null;
+ }
+ Point point = getImageDimension(stream);
+ IoUtils.closeQuietly(stream);
+ if (point == null || point.x == 0 || point.y == 0) {
+ return null;
+ }
+ WallpaperManager wm = WallpaperManager.getInstance(context);
+ int outWidth = wm.getDesiredMinimumWidth();
+ int outHeight = wm.getDesiredMinimumHeight();
+ stream = getOriginalWallpaperStream(pkgName, context);
+ if (stream == null) {
+ return null;
+ }
+ cropped = cropImage(stream, point.x, point.y, outWidth, outHeight);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception " + e);
+ } finally {
+ IoUtils.closeQuietly(stream);
+ }
+ return cropped;
+ }
+
+ private static InputStream getOriginalKeyguardStream(String pkgName, Context context) {
+ if (TextUtils.isEmpty(pkgName) || context == null) {
+ return null;
+ }
+
+ InputStream inputStream = null;
+ try {
+ //Get input WP stream from the theme
+ Context themeCtx = context.createPackageContext(pkgName,
+ Context.CONTEXT_IGNORE_SECURITY);
+ AssetManager assetManager = themeCtx.getAssets();
+ String wpPath = ThemeUtils.getLockscreenWallpaperPath(assetManager);
+ if (wpPath == null) {
+ Log.w(TAG, "Not setting lockscreen wp because wallpaper file was not found.");
+ } else {
+ inputStream = ThemeUtils.getInputStreamFromAsset(themeCtx,
+ ASSET_URI_PREFIX + wpPath);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "There was an error setting lockscreen wp for pkg " + pkgName, e);
+ }
+ return inputStream;
+ }
+
+ private static InputStream getOriginalWallpaperStream(String pkgName, Context context) {
+ if (TextUtils.isEmpty(pkgName) || context == null) {
+ return null;
+ }
+
+ InputStream inputStream = null;
+ String selection = ThemesContract.ThemesColumns.PKG_NAME + "= ?";
+ String[] selectionArgs = {pkgName};
+ Cursor c = context.getContentResolver().query(ThemesColumns.CONTENT_URI,
+ null, selection,
+ selectionArgs, null);
+ if (c == null || c.getCount() < 1) {
+ if (c != null) c.close();
+ return null;
+ } else {
+ c.moveToFirst();
+ }
+
+ try {
+ Context themeContext = context.createPackageContext(pkgName,
+ Context.CONTEXT_IGNORE_SECURITY);
+ boolean isLegacyTheme = c.getInt(
+ c.getColumnIndex(ThemesColumns.IS_LEGACY_THEME)) == 1;
+ String wallpaper = c.getString(
+ c.getColumnIndex(ThemesColumns.WALLPAPER_URI));
+ if (wallpaper != null) {
+ if (URLUtil.isAssetUrl(wallpaper)) {
+ inputStream = ThemeUtils.getInputStreamFromAsset(themeContext, wallpaper);
+ } else {
+ inputStream = context.getContentResolver().openInputStream(
+ Uri.parse(wallpaper));
+ }
+ } else {
+ // try and get the wallpaper directly from the apk if the URI was null
+ Context themeCtx = context.createPackageContext(pkgName,
+ Context.CONTEXT_IGNORE_SECURITY);
+ AssetManager assetManager = themeCtx.getAssets();
+ String wpPath = ThemeUtils.getWallpaperPath(assetManager);
+ if (wpPath == null) {
+ Log.e(TAG, "Not setting wp because wallpaper file was not found.");
+ } else {
+ inputStream = ThemeUtils.getInputStreamFromAsset(themeCtx,
+ ASSET_URI_PREFIX + wpPath);
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "getWallpaperStream: " + e);
+ } finally {
+ c.close();
+ }
+
+ return inputStream;
+ }
+}
+
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 0dd35ea8e76..386718b233d 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -518,24 +518,138 @@ static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz
}
static jint android_content_AssetManager_addOverlayPath(JNIEnv* env, jobject clazz,
- jstring idmapPath)
+ jstring idmapPath,
+ jstring packagePath,
+ jstring resApkPath, jstring targetPkgPath,
+ jstring prefixPath)
{
ScopedUtfChars idmapPath8(env, idmapPath);
if (idmapPath8.c_str() == NULL) {
return 0;
}
+ ScopedUtfChars packagePath8(env, packagePath);
+ if (packagePath8.c_str() == NULL) {
+ return 0;
+ }
+
+ ScopedUtfChars resApkPath8(env, resApkPath);
+ if (resApkPath8.c_str() == NULL) {
+ return 0;
+ }
+
+ ScopedUtfChars prefixPath8(env, prefixPath);
+ if (prefixPath8.c_str() == NULL) {
+ return 0;
+ }
+
+ ScopedUtfChars targetPkgPath8(env, targetPkgPath);
+ if (targetPkgPath8.c_str() == NULL) {
+ return 0;
+ }
+
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return 0;
+ }
+
+ int32_t cookie;
+ bool res = am->addOverlayPath(
+ String8(idmapPath8.c_str()),
+ String8(packagePath8.c_str()),
+ &cookie,
+ String8(resApkPath8.c_str()),
+ String8(targetPkgPath8.c_str()),
+ String8(prefixPath8.c_str()));
+
+ return (res) ? (jint)cookie : 0;
+}
+
+static jint android_content_AssetManager_addCommonOverlayPath(JNIEnv* env, jobject clazz,
+ jstring packagePath,
+ jstring resApkPath, jstring prefixPath)
+{
+ ScopedUtfChars packagePath8(env, packagePath);
+ if (packagePath8.c_str() == NULL) {
+ return 0;
+ }
+
+ ScopedUtfChars resApkPath8(env, resApkPath);
+ if (resApkPath8.c_str() == NULL) {
+ return 0;
+ }
+
+ ScopedUtfChars prefixPath8(env, prefixPath);
+ if (prefixPath8.c_str() == NULL) {
+ return 0;
+ }
+
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return 0;
+ }
+
+ int32_t cookie;
+ bool res = am->addCommonOverlayPath(String8(packagePath8.c_str()), &cookie,
+ String8(resApkPath8.c_str()),
+ String8(prefixPath8.c_str()));
+
+ return (res) ? (jint)cookie : 0;
+}
+
+static jint android_content_AssetManager_addIconPath(JNIEnv* env, jobject clazz,
+ jstring packagePath,
+ jstring resApkPath, jstring prefixPath,
+ jint pkgIdOverride)
+{
+ ScopedUtfChars packagePath8(env, packagePath);
+ if (packagePath8.c_str() == NULL) {
+ return 0;
+ }
+
+ ScopedUtfChars resApkPath8(env, resApkPath);
+ if (resApkPath8.c_str() == NULL) {
+ return 0;
+ }
+
+ ScopedUtfChars prefixPath8(env, prefixPath);
+ if (prefixPath8.c_str() == NULL) {
+ return 0;
+ }
+
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return 0;
}
int32_t cookie;
- bool res = am->addOverlayPath(String8(idmapPath8.c_str()), &cookie);
+ bool res = am->addIconPath(String8(packagePath8.c_str()), &cookie,
+ String8(resApkPath8.c_str()),
+ String8(prefixPath8.c_str()), pkgIdOverride);
return (res) ? (jint)cookie : 0;
}
+static jboolean android_content_AssetManager_removeOverlayPath(JNIEnv* env, jobject clazz,
+ jstring packageName, jint cookie)
+{
+ if (packageName == NULL) {
+ jniThrowException(env, "java/lang/NullPointerException", "packageName");
+ return JNI_FALSE;
+ }
+
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return JNI_FALSE;
+ }
+
+ const char* name8 = env->GetStringUTFChars(packageName, NULL);
+ bool res = am->removeOverlayPath(String8(name8), cookie);
+ env->ReleaseStringUTFChars(packageName, name8);
+
+ return res;
+}
+
static jboolean android_content_AssetManager_isUpToDate(JNIEnv* env, jobject clazz)
{
AssetManager* am = assetManagerForJavaObject(env, clazz);
@@ -1077,7 +1191,7 @@ static jboolean android_content_AssetManager_resolveAttrs(JNIEnv* env, jobject c
const ResTable::bag_entry* defStyleEnt = NULL;
uint32_t defStyleTypeSetFlags = 0;
ssize_t bagOff = defStyleRes != 0
- ? res.getBagLocked(defStyleRes, &defStyleEnt, &defStyleTypeSetFlags) : -1;
+ ? res.getBagLocked(defStyleRes, &defStyleEnt, &defStyleTypeSetFlags, true) : -1;
defStyleTypeSetFlags |= defStyleBagTypeSetFlags;
const ResTable::bag_entry* endDefStyleEnt = defStyleEnt +
(bagOff >= 0 ? bagOff : 0);;
@@ -1286,7 +1400,7 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla
const ResTable::bag_entry* defStyleEnt = NULL;
uint32_t defStyleTypeSetFlags = 0;
ssize_t bagOff = defStyleRes != 0
- ? res.getBagLocked(defStyleRes, &defStyleEnt, &defStyleTypeSetFlags) : -1;
+ ? res.getBagLocked(defStyleRes, &defStyleEnt, &defStyleTypeSetFlags, true) : -1;
defStyleTypeSetFlags |= defStyleBagTypeSetFlags;
const ResTable::bag_entry* endDefStyleEnt = defStyleEnt +
(bagOff >= 0 ? bagOff : 0);
@@ -1294,7 +1408,7 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla
// Retrieve the style class bag, if requested.
const ResTable::bag_entry* styleEnt = NULL;
uint32_t styleTypeSetFlags = 0;
- bagOff = style != 0 ? res.getBagLocked(style, &styleEnt, &styleTypeSetFlags) : -1;
+ bagOff = style != 0 ? res.getBagLocked(style, &styleEnt, &styleTypeSetFlags, true) : -1;
styleTypeSetFlags |= styleBagTypeSetFlags;
const ResTable::bag_entry* endStyleEnt = styleEnt +
(bagOff >= 0 ? bagOff : 0);
@@ -1631,7 +1745,7 @@ static jint android_content_AssetManager_retrieveArray(JNIEnv* env, jobject claz
const ResTable::bag_entry* arrayEnt = NULL;
uint32_t arrayTypeSetFlags = 0;
- ssize_t bagOff = res.getBagLocked(id, &arrayEnt, &arrayTypeSetFlags);
+ ssize_t bagOff = res.getBagLocked(id, &arrayEnt, &arrayTypeSetFlags, true);
const ResTable::bag_entry* endArrayEnt = arrayEnt +
(bagOff >= 0 ? bagOff : 0);
@@ -1968,6 +2082,50 @@ static jint android_content_AssetManager_getGlobalAssetManagerCount(JNIEnv* env,
return AssetManager::getGlobalCount();
}
+static jint android_content_AssetManager_getBasePackageCount(JNIEnv* env, jobject clazz)
+{
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return JNI_FALSE;
+ }
+
+ return am->getResources().getBasePackageCount();
+}
+
+static jstring android_content_AssetManager_getBasePackageName(JNIEnv* env, jobject clazz,
+ jint index)
+{
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return JNI_FALSE;
+ }
+
+ String16 packageName(am->getBasePackageName(index));
+ return env->NewString((const jchar*)packageName.string(), packageName.size());
+}
+
+static jstring android_content_AssetManager_getBaseResourcePackageName(JNIEnv* env, jobject clazz,
+ jint index)
+{
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return JNI_FALSE;
+ }
+
+ String16 packageName(am->getResources().getBasePackageName(index));
+ return env->NewString((const jchar*)packageName.string(), packageName.size());
+}
+
+static jint android_content_AssetManager_getBasePackageId(JNIEnv* env, jobject clazz, jint index)
+{
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return JNI_FALSE;
+ }
+
+ return am->getResources().getBasePackageId(index);
+}
+
// ----------------------------------------------------------------------------
/*
@@ -2001,8 +2159,22 @@ static JNINativeMethod gAssetManagerMethods[] = {
(void*) android_content_AssetManager_getAssetRemainingLength },
{ "addAssetPathNative", "(Ljava/lang/String;)I",
(void*) android_content_AssetManager_addAssetPath },
- { "addOverlayPathNative", "(Ljava/lang/String;)I",
+ { "addOverlayPathNative", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
(void*) android_content_AssetManager_addOverlayPath },
+ { "addCommonOverlayPathNative", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
+ (void*) android_content_AssetManager_addCommonOverlayPath },
+ { "addIconPathNative", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)I",
+ (void*) android_content_AssetManager_addIconPath },
+ { "removeOverlayPathNative", "(Ljava/lang/String;I)Z",
+ (void*) android_content_AssetManager_removeOverlayPath },
+ { "getBasePackageCount", "()I",
+ (void*) android_content_AssetManager_getBasePackageCount },
+ { "getBasePackageName", "(I)Ljava/lang/String;",
+ (void*) android_content_AssetManager_getBasePackageName },
+ { "getBaseResourcePackageName", "(I)Ljava/lang/String;",
+ (void*) android_content_AssetManager_getBaseResourcePackageName },
+ { "getBasePackageId", "(I)I",
+ (void*) android_content_AssetManager_getBasePackageId },
{ "isUpToDate", "()Z",
(void*) android_content_AssetManager_isUpToDate },
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 9069fbb8d15..ce22eccd649 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -291,6 +291,8 @@
<protected-broadcast android:name="android.nfc.handover.intent.action.TRANSFER_PROGRESS" />
<protected-broadcast android:name="android.nfc.handover.intent.action.TRANSFER_DONE" />
+ <protected-broadcast android:name="org.cyanogenmod.intent.action.THEME_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.THEME_RESOURCES_CACHED" />
<!-- ====================================== -->
<!-- Permissions for things that cost money -->
@@ -1539,6 +1541,14 @@
android:label="@string/permlab_setWallpaper"
android:description="@string/permdesc_setWallpaper" />
+ <!-- Allows applications to set the keyguard wallpaper
+ @hide -->
+ <permission android:name="android.permission.SET_KEYGUARD_WALLPAPER"
+ android:permissionGroup="android.permission-group.WALLPAPER"
+ android:protectionLevel="normal"
+ android:label="@string/permlab_setKeyguardWallpaper"
+ android:description="@string/permdesc_setKeyguardWallpaper" />
+
<!-- Allows applications to set the wallpaper hints -->
<permission android:name="android.permission.SET_WALLPAPER_HINTS"
android:permissionGroup="android.permission-group.WALLPAPER"
@@ -2877,6 +2887,30 @@
android:label="@string/permlab_changePhoneBlacklist"
android:description="@string/permdesc_changePhoneBlacklist" />
+ <!-- Allows an application to use the Theme Service
+ @hide -->
+ <permission android:name="android.permission.ACCESS_THEME_MANAGER"
+ android:label="@string/permlab_accessThemeService"
+ android:description="@string/permdesc_accessThemeService"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to read the current theme configuration and
+ get information about the various themes currently installed
+ @hide -->
+ <permission android:name="android.permission.READ_THEMES"
+ android:label="@string/permlab_readThemes"
+ android:description="@string/permdesc_readThemesDesc"
+ android:protectionLevel="normal" />
+
+ <!-- Allows an application to write the current theme configuration and
+ write information about the various themes currently installed.
+ Changing themes should be done through the service ACCESS_THEME_MANAGER
+ @hide -->
+ <permission android:name="android.permission.WRITE_THEMES"
+ android:label="@string/permlab_writeThemes"
+ android:description="@string/permdesc_writeThemesDesc"
+ android:protectionLevel="system|signature" />
+
<!-- The system process is explicitly the only one allowed to launch the
confirmation UI for full backup/restore -->
<uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
@@ -3074,6 +3108,18 @@
</intent-filter>
</receiver>
+ <receiver android:name="com.android.server.AppsFailureReceiver" >
+ <intent-filter>
+ <action android:name="com.tmobile.intent.action.APP_FAILURE" />
+ <action android:name="com.tmobile.intent.action.APP_FAILURE_RESET" />
+ <action android:name="android.intent.action.PACKAGE_ADDED" />
+ <action android:name="android.intent.action.PACKAGE_REMOVED" />
+ <action android:name="org.cyanogenmod.intent.action.THEME_CHANGED" />
+ <category android:name="com.tmobile.intent.category.THEME_PACKAGE_INSTALL_STATE_CHANGE" />
+ <data android:scheme="package" />
+ </intent-filter>
+ </receiver>
+
<service android:name="com.android.internal.os.storage.ExternalStorageFormatter"
android:permission="android.permission.MASTER_CLEAR"
android:exported="true" />
diff --git a/core/res/res/anim/lock_screen_wallpaper_exit_noop.xml b/core/res/res/anim/lock_screen_wallpaper_exit_noop.xml
new file mode 100644
index 00000000000..4cc5c70aae3
--- /dev/null
+++ b/core/res/res/anim/lock_screen_wallpaper_exit_noop.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The CyanogenMod 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false" android:startOffset="100">
+ <alpha
+ android:fromAlpha="0.0" android:toAlpha="0.0"
+ android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_linear_in"
+ android:duration="150"/>
+
+ <!-- Empty animation so the animation has same duration as lock_screen_behind_enter animation
+ -->
+ <translate android:fromYDelta="0" android:toYDelta="0"
+ android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+ android:interpolator="@interpolator/linear"
+ android:duration="300" />
+</set> \ No newline at end of file
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 10c2518ec19..73887a49d7e 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -745,6 +745,8 @@
<flag name="smallestScreenSize" value="0x0800" />
<!-- The layout direction has changed. For example going from LTR to RTL. -->
<flag name="layoutDirection" value="0x2000" />
+ <!-- Theme has changed -->
+ <flag name="themeChange" value="0x8000" />
<!-- The font scaling factor has changed, that is the user has
selected a new global font size. -->
<flag name="fontScale" value="0x40000000" />
diff --git a/core/res/res/values/cm_strings.xml b/core/res/res/values/cm_strings.xml
index 9ba01c5b90a..a2d16c0fa65 100755
--- a/core/res/res/values/cm_strings.xml
+++ b/core/res/res/values/cm_strings.xml
@@ -49,6 +49,23 @@
<!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permgroupdesc_security">Permissions related to device security information.</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_accessThemeService">access theme service</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_accessThemeService">Allows an app to access the theme service. Should never be needed for normal apps.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_readThemes">read your theme info</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_readThemesDesc">Allows the app to read your themes and
+ determine which theme you have applied.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_writeThemes">modify your themes</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_writeThemesDesc">Allows the app to insert new themes and
+ modify which theme you have applied.</string>
+
<!-- [CHAR LIMIT=NONE] Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_readPhoneBlacklist">read phone blacklist</string>
<!-- [CHAR LIMIT=NONE] Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -58,4 +75,14 @@
<string name="permlab_changePhoneBlacklist">change phone blacklist</string>
<!-- [CHAR LIMIT=NONE] Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_changePhoneBlacklist">Allows an app to change the phone numbers that are blocked for incoming calls or messages.</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want the application to do this. -->
+ <string name="permlab_setKeyguardWallpaper">set keyguard wallpaper</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_setKeyguardWallpaper">Allows an app to change the lock screen wallpaper.</string>
+
+ <!-- Theme installation error notification -->
+ <string name="theme_install_error_title">Failed to install theme</string>
+ <string name="theme_install_error_message">%s failed to install</string>
+ <string name="theme_reset_notification_title">Theme reset</string>
+ <string name="theme_reset_notification_body">System theme restored due to multiple app crashes.</string>
</resources>
diff --git a/core/res/res/values/cm_symbols.xml b/core/res/res/values/cm_symbols.xml
new file mode 100644
index 00000000000..1e5c2003cd4
--- /dev/null
+++ b/core/res/res/values/cm_symbols.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2012, 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>
+ <!-- We don't want to publish private symbols in android.R as part of the
+ SDK. Instead, put them here. -->
+ <private-symbols package="com.android.internal" />
+
+ <!-- Theme install failure notification -->
+ <java-symbol type="string" name="theme_install_error_title" />
+ <java-symbol type="string" name="theme_install_error_message" />
+
+ <!-- Theme reset notification -->
+ <java-symbol type="string" name="theme_reset_notification_title" />
+ <java-symbol type="string" name="theme_reset_notification_body" />
+</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f135a75a2cf..348874b6935 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1870,6 +1870,7 @@
<java-symbol type="anim" name="lock_screen_behind_enter_wallpaper" />
<java-symbol type="anim" name="lock_screen_behind_enter_fade_in" />
<java-symbol type="anim" name="lock_screen_wallpaper_exit" />
+ <java-symbol type="anim" name="lock_screen_wallpaper_exit_noop" />
<java-symbol type="bool" name="config_alwaysUseCdmaRssi" />
<java-symbol type="dimen" name="status_bar_icon_size" />
diff --git a/graphics/java/android/graphics/FontListConverter.java b/graphics/java/android/graphics/FontListConverter.java
new file mode 100644
index 00000000000..c675c88c820
--- /dev/null
+++ b/graphics/java/android/graphics/FontListConverter.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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 android.graphics;
+
+import android.graphics.FontListParser;
+import android.graphics.FontListParser.Alias;
+import android.graphics.FontListParser.Font;
+import android.graphics.LegacyFontListParser.Family;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map.Entry;
+
+/**
+ * Converts a list of Family to FontListParser.Config
+ * {@hide}
+ */
+public class FontListConverter {
+ // These array values were determined by the order
+ // of fonts in a fileset. The order is:
+ // "Normal, Bold, Italic, BoldItalic"
+ // Additionally the weight values in L's fonts.xml
+ // are used to determine a generic weight value for each type
+ // e.g The 2nd entry in a fileset is the bold font.
+ protected static final int[] WEIGHTS = { 400, 700, 400, 700 };
+ protected static boolean[] ITALICS = { false, false, true, true };
+ protected static final int DEFAULT_WEIGHT = WEIGHTS[0];
+
+ // Arbitrarily chosen list based on L's fonts.xml.
+ // There could be more out there, but we can't use a generic pattern of "fontName-styleName"
+ // as "sans-serif" would be translated as a font called "sans" with the style "serif".
+ public static final String[] STYLES = {
+ "thin",
+ "light",
+ "medium",
+ "black"
+ };
+
+ // Maps a "normal" family to a list of similar families differing by style
+ // Example: "sans-serif" -> { sans-serif-thin, sans-serif-light, sans-serif-medium }
+ private HashMap<Family, List<Family>> mFamilyVariants = new HashMap<Family, List<Family>>();
+ private List<Family> mFamilies =
+ new ArrayList<Family >();
+ private String mFontDir;
+
+
+ public FontListConverter(List<Family> families, String fontDir) {
+ mFamilies.addAll(families);
+ mFontDir = fontDir;
+ findFamilyVariants();
+ }
+
+ public FontListConverter(Family family, String fontDir) {
+ mFamilies.add(family);
+ mFontDir = fontDir;
+ findFamilyVariants();
+ }
+
+ private void findFamilyVariants() {
+ for(Family family : mFamilies) {
+ if (isNormalStyle(family)) {
+ List<Family> variants = findVariants(family, mFamilies);
+ mFamilyVariants.put(family, variants);
+ }
+ }
+ }
+
+ private List<Family> findVariants(Family normalFamily, List<Family> legacyFamilies) {
+ List<Family> variants = new ArrayList<Family>();
+
+ String normalFamilyName = normalFamily.getName();
+
+ for(Family family : legacyFamilies) {
+ String name = family.getName();
+
+ if (name.startsWith(normalFamilyName) && !isNormalStyle(family)) {
+ variants.add(family);
+ }
+ }
+ return variants;
+ }
+
+ public FontListParser.Config convert() {
+ FontListParser.Config config = new FontListParser.Config();
+ config.families.addAll(convertFamilies());
+ config.aliases.addAll(createAliases());
+ return config;
+ }
+
+ /**
+ * A "normal" style is just standard font,
+ * eg Roboto is normal. Roboto-Thin is styled.
+ */
+ protected boolean isNormalStyle(Family family) {
+ String name = family.getName();
+ if (name == null) return false;
+
+ for(String style : STYLES) {
+ if (name.endsWith('-' + style)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ protected List<FontListParser.Family> convertFamilies() {
+ List<FontListParser.Family> convertedFamilies = new ArrayList<FontListParser.Family>();
+
+ // Only convert normal families. Each normal family will add in its variants
+ for(Family family : mFamilyVariants.keySet()) {
+ convertedFamilies.add(convertFamily(family));
+ }
+
+ return convertedFamilies;
+ }
+
+ protected FontListParser.Family convertFamily(Family legacyFamily) {
+ List<String> nameset = legacyFamily.nameset;
+ List<String> fileset = legacyFamily.fileset;
+
+ // Arbitrarily choose the first entry in the nameset to be the name
+ String name = nameset.isEmpty() ? null : nameset.get(0);
+
+ List<Font> fonts = convertFonts(fileset);
+
+ // Add fonts from other variants
+ for(Family variantFamily : mFamilyVariants.get(legacyFamily)) {
+ fonts.addAll(convertFonts(variantFamily.fileset));
+ }
+
+ return new FontListParser.Family(name, fonts, null, null);
+ }
+
+ protected List<FontListParser.Font> convertFonts(List<String> fileset) {
+ List<Font> fonts = new ArrayList<Font>();
+
+ for(int i=0; i < fileset.size(); i++) {
+ String fullpath = mFontDir + File.separatorChar + fileset.get(i);
+ // fileset should only be 4 entries, but if
+ // its more we can just assign a default.
+ int weight = i < WEIGHTS.length ? WEIGHTS[i] : DEFAULT_WEIGHT;
+ boolean isItalic = i < ITALICS.length ? ITALICS[i] : false;
+
+ Font font = new Font(fullpath, weight, isItalic);
+ fonts.add(font);
+ }
+
+ return fonts;
+ }
+
+ protected List<Alias> createAliases() {
+ List<Alias> aliases = new ArrayList<Alias>();
+
+ for(Family family : mFamilyVariants.keySet()) {
+ // Get any aliases that might be from a normal family's nameset.
+ // eg sans-serif, arial, helvetica, tahoma etc.
+ if (isNormalStyle(family)) {
+ aliases.addAll(adaptNamesetAliases(family.nameset));
+ }
+ }
+
+ aliases.addAll(getAliasesForRelatedFamilies());
+
+ return aliases;
+ }
+
+ private List<Alias> getAliasesForRelatedFamilies() {
+ List<Alias> aliases = new ArrayList<Alias>();
+
+ for(Entry<Family, List<Family>> entry : mFamilyVariants.entrySet()) {
+ String toName = entry.getKey().nameset.get(0);
+ List<Family> relatedFamilies = entry.getValue();
+ for(Family relatedFamily : relatedFamilies) {
+ aliases.addAll(adaptNamesetAliases(relatedFamily.nameset, toName));
+ }
+ }
+ return aliases;
+ }
+
+ private List<Alias> adaptNamesetAliases(List<String> nameset, String toName) {
+ List<Alias> aliases = new ArrayList<Alias>();
+ for(String name : nameset) {
+ Alias alias = new Alias();
+ alias.name = name;
+ alias.toName = toName;
+ aliases.add(alias);
+ }
+ return aliases;
+ }
+
+ private List<Alias> adaptNamesetAliases(List<String> nameset) {
+ List<Alias> aliases = new ArrayList<Alias>();
+ if (nameset.size() < 2) return aliases; // An alias requires a name and toName
+
+ String toName = nameset.get(0);
+ for(int i = 1; i < nameset.size(); i++) {
+ Alias alias = new Alias();
+ alias.name = nameset.get(i);
+ alias.toName = toName;
+ aliases.add(alias);
+ }
+
+ return aliases;
+ }
+}
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 97081f9c77f..1ca464d6f3d 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -21,6 +21,8 @@ import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
@@ -74,25 +76,64 @@ public class FontListParser {
}
/* Parse fallback list (no names) */
- public static Config parse(InputStream in) throws XmlPullParserException, IOException {
+ public static Config parse(File configFilename, File fontDir)
+ throws XmlPullParserException, IOException {
+ FileInputStream in = new FileInputStream(configFilename);
+ if (isLegacyFormat(configFilename)) {
+ return parseLegacyFormat(in, fontDir.getAbsolutePath());
+ } else {
+ return parseNormalFormat(in, fontDir.getAbsolutePath());
+ }
+ }
+
+ private static boolean isLegacyFormat(File configFilename)
+ throws XmlPullParserException, IOException {
+ FileInputStream in = new FileInputStream(configFilename);
+ boolean isLegacy = false;
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in, null);
+ parser.nextTag();
+ parser.require(XmlPullParser.START_TAG, null, "familyset");
+ String version = parser.getAttributeValue(null, "version");
+ isLegacy = version == null;
+ } finally {
+ in.close();
+ }
+ return isLegacy;
+ }
+
+ public static Config parseLegacyFormat(InputStream in, String dirName)
+ throws XmlPullParserException, IOException {
+ try {
+ List<LegacyFontListParser.Family> legacyFamilies = LegacyFontListParser.parse(in);
+ FontListConverter converter = new FontListConverter(legacyFamilies, dirName);
+ return converter.convert();
+ } finally {
+ in.close();
+ }
+ }
+
+ public static Config parseNormalFormat(InputStream in, String dirName)
+ throws XmlPullParserException, IOException {
try {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(in, null);
parser.nextTag();
- return readFamilies(parser);
+ return readFamilies(parser, dirName);
} finally {
in.close();
}
}
- private static Config readFamilies(XmlPullParser parser)
+ private static Config readFamilies(XmlPullParser parser, String dirPath)
throws XmlPullParserException, IOException {
Config config = new Config();
parser.require(XmlPullParser.START_TAG, null, "familyset");
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
if (parser.getName().equals("family")) {
- config.families.add(readFamily(parser));
+ config.families.add(readFamily(parser, dirPath));
} else if (parser.getName().equals("alias")) {
config.aliases.add(readAlias(parser));
} else {
@@ -102,7 +143,7 @@ public class FontListParser {
return config;
}
- private static Family readFamily(XmlPullParser parser)
+ private static Family readFamily(XmlPullParser parser, String dirPath)
throws XmlPullParserException, IOException {
String name = parser.getAttributeValue(null, "name");
String lang = parser.getAttributeValue(null, "lang");
@@ -116,7 +157,7 @@ public class FontListParser {
int weight = weightStr == null ? 400 : Integer.parseInt(weightStr);
boolean isItalic = "italic".equals(parser.getAttributeValue(null, "style"));
String filename = parser.nextText();
- String fullFilename = "/system/fonts/" + filename;
+ String fullFilename = dirPath + File.separatorChar + filename;
fonts.add(new Font(fullFilename, weight, isItalic));
} else {
skip(parser);
diff --git a/graphics/java/android/graphics/LegacyFontListParser.java b/graphics/java/android/graphics/LegacyFontListParser.java
new file mode 100644
index 00000000000..22923cb63f8
--- /dev/null
+++ b/graphics/java/android/graphics/LegacyFontListParser.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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 android.graphics;
+
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Parses an XML font config. Example:
+ *
+ *<familyset>
+ *
+ * <family>
+ * <nameset>
+ * <name>sans-serif</name>
+ * <name>arial</name>
+ * </nameset>
+ * <fileset>
+ * <file>Roboto-Regular.ttf</file>
+ * <file>Roboto-Bold.ttf</file>
+ * <file>Roboto-Italic.ttf</file>
+ * <file>Roboto-BoldItalic.ttf</file>
+ * </fileset>
+ * </family>
+ * <family>
+ * ...
+ * </family>
+ *</familyset>
+ * @hide
+ */
+public class LegacyFontListParser {
+ public static class Family {
+ public List<String> nameset = new ArrayList<String>();
+ public List<String> fileset = new ArrayList<String>();
+
+ public String getName() {
+ if (nameset != null && !nameset.isEmpty()) {
+ return nameset.get(0);
+ }
+ return null;
+ }
+ }
+
+ public static List<Family> parse(InputStream in)
+ throws XmlPullParserException, IOException {
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in, null);
+ parser.nextTag();
+ return readFamilySet(parser);
+ } finally {
+ in.close();
+ }
+ }
+
+ private static List<Family> readFamilySet(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ List<Family> families = new ArrayList<Family>();
+ parser.require(XmlPullParser.START_TAG, null, "familyset");
+
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
+ }
+ String name = parser.getName();
+
+ // Starts by looking for the entry tag
+ if (name.equals("family")) {
+ Family family = readFamily(parser);
+ families.add(family);
+ }
+ }
+ return families;
+ }
+
+ private static Family readFamily(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ Family family = new Family();
+ parser.require(XmlPullParser.START_TAG, null, "family");
+
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
+ }
+ String name = parser.getName();
+ if (name.equals("nameset")) {
+ List<String> nameset = readNameset(parser);
+ family.nameset = nameset;
+ } else if (name.equals("fileset")) {
+ List<String> fileset = readFileset(parser);
+ family.fileset = fileset;
+ } else {
+ skip(parser);
+ }
+ }
+ return family;
+ }
+
+ private static List<String> readNameset(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ List<String> names = new ArrayList<String>();
+ parser.require(XmlPullParser.START_TAG, null, "nameset");
+
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
+ }
+ String tagname = parser.getName();
+ if (tagname.equals("name")) {
+ String name = readText(parser);
+ names.add(name);
+ } else {
+ skip(parser);
+ }
+ }
+ return names;
+ }
+
+ private static List<String> readFileset(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ List<String> files = new ArrayList<String>();
+ parser.require(XmlPullParser.START_TAG, null, "fileset");
+
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
+ }
+ String name = parser.getName();
+ if (name.equals("file")) {
+ String file = readText(parser);
+ files.add(file);
+ } else {
+ skip(parser);
+ }
+ }
+ return files;
+ }
+
+ // For the tags title and summary, extracts their text values.
+ private static String readText(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ String result = "";
+ if (parser.next() == XmlPullParser.TEXT) {
+ result = parser.getText();
+ parser.nextTag();
+ }
+ return result;
+ }
+
+ private static void skip(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ throw new IllegalStateException();
+ }
+ int depth = 1;
+ while (depth != 0) {
+ switch (parser.next()) {
+ case XmlPullParser.END_TAG:
+ depth--;
+ break;
+ case XmlPullParser.START_TAG:
+ depth++;
+ break;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index db42314447f..68c9c0bd9c0 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -25,7 +25,6 @@ import android.util.SparseArray;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
@@ -68,6 +67,8 @@ public class Typeface {
static final String FONTS_CONFIG = "fonts.xml";
+ static final String SANS_SERIF_FAMILY_NAME = "sans-serif";
+
/**
* @hide
*/
@@ -81,6 +82,13 @@ public class Typeface {
private int mStyle = 0;
+ // Typefaces that we can garbage collect when changing fonts, and so we don't break public APIs
+ private static Typeface DEFAULT_INTERNAL;
+ private static Typeface DEFAULT_BOLD_INTERNAL;
+ private static Typeface SANS_SERIF_INTERNAL;
+ private static Typeface SERIF_INTERNAL;
+ private static Typeface MONOSPACE_INTERNAL;
+
private static void setDefault(Typeface t) {
sDefaultTypeface = t;
nativeSetDefault(t.native_instance);
@@ -228,7 +236,10 @@ public class Typeface {
for (int i = 0; i < families.length; i++) {
ptrArray[i] = families[i].mNativePtr;
}
- return new Typeface(nativeCreateFromArray(ptrArray));
+
+
+ Typeface typeface = new Typeface(nativeCreateFromArray(ptrArray));
+ return typeface;
}
/**
@@ -267,6 +278,73 @@ public class Typeface {
return fontFamily;
}
+ /**
+ * Adds the family from src with the name familyName as a fallback font in dst
+ * @param src Source font config
+ * @param dst Destination font config
+ * @param familyName Name of family to add as a fallback
+ */
+ private static void addFallbackFontsForFamilyName(FontListParser.Config src,
+ FontListParser.Config dst, String familyName) {
+ for (Family srcFamily : src.families) {
+ if (familyName.equals(srcFamily.name)) {
+ // set the name to null so that it will be added as a fallback
+ srcFamily.name = null;
+ dst.families.add(srcFamily);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Adds any font families in src that do not exist in dst
+ * @param src Source font config
+ * @param dst Destination font config
+ */
+ private static void addMissingFontFamilies(FontListParser.Config src,
+ FontListParser.Config dst) {
+ final int N = dst.families.size();
+ // add missing families
+ for (Family srcFamily : src.families) {
+ boolean addFamily = true;
+ for (int i = 0; i < N && addFamily; i++) {
+ final Family dstFamily = dst.families.get(i);
+ final String dstFamilyName = dstFamily.name;
+ if (dstFamilyName != null && dstFamilyName.equals(srcFamily.name)) {
+ addFamily = false;
+ break;
+ }
+ }
+ if (addFamily) {
+ dst.families.add(srcFamily);
+ }
+ }
+ }
+
+ /**
+ * Adds any aliases in src that do not exist in dst
+ * @param src Source font config
+ * @param dst Destination font config
+ */
+ private static void addMissingFontAliases(FontListParser.Config src,
+ FontListParser.Config dst) {
+ final int N = dst.aliases.size();
+ // add missing aliases
+ for (FontListParser.Alias alias : src.aliases) {
+ boolean addAlias = true;
+ for (int i = 0; i < N && addAlias; i++) {
+ final String dstAliasName = dst.aliases.get(i).name;
+ if (dstAliasName != null && dstAliasName.equals(alias.name)) {
+ addAlias = false;
+ break;
+ }
+ }
+ if (addAlias) {
+ dst.aliases.add(alias);
+ }
+ }
+ }
+
/*
* (non-Javadoc)
*
@@ -275,10 +353,35 @@ public class Typeface {
private static void init() {
// Load font config and initialize Minikin state
File systemFontConfigLocation = getSystemFontConfigLocation();
- File configFilename = new File(systemFontConfigLocation, FONTS_CONFIG);
+ File themeFontConfigLocation = getThemeFontConfigLocation();
+
+ File systemConfigFile = new File(systemFontConfigLocation, FONTS_CONFIG);
+ File themeConfigFile = new File(themeFontConfigLocation, FONTS_CONFIG);
+ File configFile = null;
+ File fontDir;
+
+ if (themeConfigFile.exists()) {
+ configFile = themeConfigFile;
+ fontDir = getThemeFontDirLocation();
+ } else {
+ configFile = systemConfigFile;
+ fontDir = getSystemFontDirLocation();
+ }
+
try {
- FileInputStream fontsIn = new FileInputStream(configFilename);
- FontListParser.Config fontConfig = FontListParser.parse(fontsIn);
+ FontListParser.Config fontConfig = FontListParser.parse(configFile, fontDir);
+ FontListParser.Config systemFontConfig = null;
+
+ // If the fonts are coming from a theme, we will need to make sure that we include
+ // any font families from the system fonts that the theme did not include.
+ // NOTE: All the system font families without names ALWAYS get added.
+ if (configFile == themeConfigFile) {
+ systemFontConfig = FontListParser.parse(systemConfigFile,
+ getSystemFontDirLocation());
+ addMissingFontFamilies(systemFontConfig, fontConfig);
+ addMissingFontAliases(systemFontConfig, fontConfig);
+ addFallbackFontsForFamilyName(systemFontConfig, fontConfig, SANS_SERIF_FAMILY_NAME);
+ }
List<FontFamily> familyList = new ArrayList<FontFamily>();
// Note that the default typeface is always present in the fallback list;
@@ -289,6 +392,7 @@ public class Typeface {
familyList.add(makeFamilyFromParsed(f));
}
}
+
sFallbackFonts = familyList.toArray(new FontFamily[familyList.size()]);
setDefault(Typeface.createFromFamilies(sFallbackFonts));
@@ -324,22 +428,53 @@ public class Typeface {
Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e);
// TODO: normal in non-Minikin case, remove or make error when Minikin-only
} catch (FileNotFoundException e) {
- Log.e(TAG, "Error opening " + configFilename);
+ Log.e(TAG, "Error opening " + configFile);
} catch (IOException e) {
- Log.e(TAG, "Error reading " + configFilename);
+ Log.e(TAG, "Error reading " + configFile);
} catch (XmlPullParserException e) {
- Log.e(TAG, "XML parse exception for " + configFilename);
+ Log.e(TAG, "XML parse exception for " + configFile);
}
}
+ /**
+ * Clears caches in java and skia.
+ * Skia will then reparse font config
+ * @hide
+ */
+ public static void recreateDefaults() {
+ sTypefaceCache.clear();
+ sSystemFontMap.clear();
+ init();
+
+ DEFAULT_INTERNAL = create((String) null, 0);
+ DEFAULT_BOLD_INTERNAL = create((String) null, Typeface.BOLD);
+ SANS_SERIF_INTERNAL = create("sans-serif", 0);
+ SERIF_INTERNAL = create("serif", 0);
+ MONOSPACE_INTERNAL = create("monospace", 0);
+
+ DEFAULT.native_instance = DEFAULT_INTERNAL.native_instance;
+ DEFAULT_BOLD.native_instance = DEFAULT_BOLD_INTERNAL.native_instance;
+ SANS_SERIF.native_instance = SANS_SERIF_INTERNAL.native_instance;
+ SERIF.native_instance = SERIF_INTERNAL.native_instance;
+ MONOSPACE.native_instance = MONOSPACE_INTERNAL.native_instance;
+ sDefaults[2] = create((String) null, Typeface.ITALIC);
+ sDefaults[3] = create((String) null, Typeface.BOLD_ITALIC);
+ }
+
static {
init();
// Set up defaults and typefaces exposed in public API
- DEFAULT = create((String) null, 0);
- DEFAULT_BOLD = create((String) null, Typeface.BOLD);
- SANS_SERIF = create("sans-serif", 0);
- SERIF = create("serif", 0);
- MONOSPACE = create("monospace", 0);
+ DEFAULT_INTERNAL = create((String) null, 0);
+ DEFAULT_BOLD_INTERNAL = create((String) null, Typeface.BOLD);
+ SANS_SERIF_INTERNAL = create("sans-serif", 0);
+ SERIF_INTERNAL = create("serif", 0);
+ MONOSPACE_INTERNAL = create("monospace", 0);
+
+ DEFAULT = new Typeface(DEFAULT_INTERNAL.native_instance);
+ DEFAULT_BOLD = new Typeface(DEFAULT_BOLD_INTERNAL.native_instance);
+ SANS_SERIF = new Typeface(SANS_SERIF_INTERNAL.native_instance);
+ SERIF = new Typeface(SERIF_INTERNAL.native_instance);
+ MONOSPACE = new Typeface(MONOSPACE_INTERNAL.native_instance);
sDefaults = new Typeface[] {
DEFAULT,
@@ -354,6 +489,18 @@ public class Typeface {
return new File("/system/etc/");
}
+ private static File getSystemFontDirLocation() {
+ return new File("/system/fonts/");
+ }
+
+ private static File getThemeFontConfigLocation() {
+ return new File("/data/system/theme/fonts/");
+ }
+
+ private static File getThemeFontDirLocation() {
+ return new File("/data/system/theme/fonts/");
+ }
+
@Override
protected void finalize() throws Throwable {
try {
diff --git a/graphics/tests/localtests/Android.mk b/graphics/tests/localtests/Android.mk
new file mode 100644
index 00000000000..0b95ecd5645
--- /dev/null
+++ b/graphics/tests/localtests/Android.mk
@@ -0,0 +1,18 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE := FrameworksGraphicsHostTests
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+#LOCAL_PACKAGE_NAME := FrameworksGraphicsTests
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+#####################################
+
+
diff --git a/graphics/tests/localtests/src/android/graphics/FontListConverterTest.java b/graphics/tests/localtests/src/android/graphics/FontListConverterTest.java
new file mode 100644
index 00000000000..60c90533c98
--- /dev/null
+++ b/graphics/tests/localtests/src/android/graphics/FontListConverterTest.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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 android.graphics;
+
+import android.graphics.FontListParser.Alias;
+import android.graphics.LegacyFontListParser.Family;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+public class FontListConverterTest extends TestCase {
+ // VALID nameset includes the default name first and
+ // and some other 'aliases' with it.
+ private static final String[] VALID_NAMESET = {
+ "sans-serif",
+ "arial",
+ "helvetica",
+ "tahoma",
+ "verdana"
+ };
+
+ // The correct fileset will have 4 files in
+ // order by type (regular, bold, italic, bolditalic)
+ private static final String[] VALID_FILESET = {
+ "Roboto-Regular.ttf",
+ "Roboto-Bold.ttf",
+ "Roboto-Italic.ttf",
+ "Roboto-BoldItalic.ttf"
+ };
+
+ // The legacy fontlist format considered thin, light, and black styles
+ // each as part of their own familysets. The new format does not, so we need
+ // to provide a test case to adapt this. Note: "condensed" is still considered
+ // to be its own familyset. So we must be careful
+ private static final String[] VALID_ADDITIONAL_STYLE_NAMESET = {
+ "sans-serif-thin"
+ };
+
+ private static final String[] VALID_ADDITIONAL_STYLE_FILESET = {
+ "Roboto-Thin.ttf",
+ "Roboto-ThinItalic.ttf"
+ };
+
+ // thin, light, and black styles are part of the same family but a Roboto "condensed"
+ // or Roboto "slab" would be considered part of a different family. Since the legacy
+ // format would already consider these as a different family, we just have to make sure
+ // they don't get brought back into a common family like thin/light/black
+ private static final String[] VALID_RELATED_FAMILY_NAMESET = {
+ "sans-serif-condensed"
+ };
+
+ private static final String[] VALID_RELATED_FAMILY_FILESET = {
+ "RobotoCondensed-Regular.ttf",
+ "RobotoCondensed-Bold.ttf",
+ "RobotoCondensed-Italic.ttf",
+ "RobotoCondensed-BoldItalic.ttf"
+ };
+
+ // Some typefaces will only have one style.
+ private static final String[] VALID_SINGLE_STYLE_FAMIlY_NAMESET = {
+ "monospace"
+ };
+ private static final String[] VALID_SINGLE_STYLE_FAMIlY_FILESET = {
+ "DroidSansMono.ttf"
+ };
+
+ final String VALID_PATH = "/valid/path/";
+
+ private Family sValidFamily; // eg "sans-serif"
+ private Family sValidAdditionalStyleFamily; // eg "sans-serif-light"
+ private Family sValidRelatedFamily; // eg "sans-serif-condensed"
+ private Family mValidSingleStyleFamily; // eg "monospace" which only uses DroidSansMono.ttf
+
+ protected void setUp() {
+ sValidFamily = new Family();
+ sValidFamily.nameset = new ArrayList<String>(Arrays.asList(VALID_NAMESET));
+ sValidFamily.fileset = new ArrayList<String>(Arrays.asList(VALID_FILESET));
+
+ sValidAdditionalStyleFamily = new Family();
+ sValidAdditionalStyleFamily.nameset =
+ new ArrayList<String>(Arrays.asList(VALID_ADDITIONAL_STYLE_NAMESET));
+ sValidAdditionalStyleFamily.fileset =
+ new ArrayList<String>(Arrays.asList(VALID_ADDITIONAL_STYLE_FILESET));
+
+ sValidRelatedFamily = new Family();
+ sValidRelatedFamily.nameset =
+ new ArrayList<String>(Arrays.asList(VALID_RELATED_FAMILY_NAMESET));
+ sValidRelatedFamily.fileset =
+ new ArrayList<String>(Arrays.asList(VALID_RELATED_FAMILY_FILESET));
+
+ mValidSingleStyleFamily = new Family();
+ }
+
+ @SmallTest
+ public void testValidAdaptedFamilyShouldHaveNameOfNamesetsFirstElement() {
+ FontListConverter adapter = new FontListConverter(sValidFamily, VALID_PATH);
+ FontListParser.Family convertedFamily = adapter.convertFamily(sValidFamily);
+ assertEquals(VALID_NAMESET[0], convertedFamily.name);
+ }
+
+ @SmallTest
+ public void testValidAdaptedFamilyShouldHaveFonts() {
+ FontListConverter adapter = new FontListConverter(sValidFamily, VALID_PATH);
+ FontListParser.Family convertedFamily = adapter.convertFamily(sValidFamily);
+ List<FontListParser.Font> fonts = convertedFamily.fonts;
+ assertEquals(VALID_FILESET.length, fonts.size());
+ }
+
+ @SmallTest
+ public void testValidAdaptedFontsShouldHaveCorrectProperties() {
+ FontListConverter adapter = new FontListConverter(sValidFamily, VALID_PATH);
+ List<FontListParser.Font> fonts = adapter.convertFonts(Arrays.asList(VALID_FILESET));
+
+ assertEquals(VALID_FILESET.length, fonts.size());
+ for(int i=0; i < fonts.size(); i++) {
+ FontListParser.Font font = fonts.get(i);
+ assertEquals(VALID_PATH + VALID_FILESET[i], font.fontName);
+ assertEquals("shouldBeItalic", shouldBeItalic(i), font.isItalic);
+ assertEquals(FontListConverter.WEIGHTS[i], font.weight);
+ }
+ }
+
+ @SmallTest
+ public void testExtraNamesetsShouldConvertToAliases() {
+ List<Family> families = new ArrayList<Family>();
+ families.add(sValidFamily);
+
+ FontListConverter adapter = new FontListConverter(sValidFamily, VALID_PATH);
+ List<FontListParser.Alias> aliases = adapter.createAliases();
+
+ // Be sure the aliases point to the first name in the nameset
+ for(int i = 0; i < aliases.size(); i++) {
+ FontListParser.Alias alias = aliases.get(i);
+ assertEquals(VALID_NAMESET[0], alias.toName);
+ }
+
+ // Be sure the extra namesets are in the alias list
+ for(int i = 1; i < VALID_NAMESET.length; i++) {
+ assertTrue("hasAliasWithName", hasAliasWithName(aliases, VALID_NAMESET[i]));
+ }
+ }
+
+ /**
+ * The legacy format treats thin, light, and black fonts to be different families
+ * The new format treats these as all part of the original
+ * eg sans-serif and sans-serif-thin become one family
+ */
+ @SmallTest
+ public void testAdditionalStylesShouldConvertToSameFamily() {
+ List<Family> families = new ArrayList<Family>();
+ families.add(sValidFamily); //eg "sans-serif"
+ families.add(sValidAdditionalStyleFamily); //eg "sans-serif-light"
+
+ FontListConverter adapter = new FontListConverter(families, VALID_PATH);
+ List<FontListParser.Family> convertedFamilies = adapter.convertFamilies();
+
+ // We started with two similiar families, and now should have one
+ assertEquals(1, convertedFamilies.size());
+
+ // The name of the family should be the base name, no style modifiers
+ // ie "sans-serif" not "sans-serif-light"
+ FontListParser.Family convertedFamily = convertedFamilies.get(0);
+ assertEquals(sValidFamily.nameset.get(0), convertedFamily.name);
+
+ // Verify all the fonts from both families exist now in the converted Family
+ List<String> combinedFileSet = new ArrayList<String>();
+ combinedFileSet.addAll(sValidFamily.fileset);
+ combinedFileSet.addAll(sValidAdditionalStyleFamily.fileset);
+ for(String filename : combinedFileSet) {
+ String fontName = VALID_PATH + filename;
+ assertTrue("hasFontWithName", hasFontWithName(convertedFamily, fontName));
+ }
+ }
+
+ /**
+ * When two families combine, the "varied" family (ie light, light, black) should
+ * have their namesets converted to aliases.
+ * IE sans-serif-light should point to sans-serif because the light family
+ * gets merged to sans-serif
+ */
+ @SmallTest
+ public void testAdditionalStylesNamesShouldBecomeAliases() {
+ List<Family> families = new ArrayList<Family>();
+ families.add(sValidFamily); //eg "sans-serif"
+ families.add(sValidAdditionalStyleFamily); //eg "sans-serif-light"
+
+ FontListConverter adapter = new FontListConverter(families, VALID_PATH);
+ List<Alias> aliases = adapter.createAliases();
+
+ // Subtract 1 from the total length since VALID_NAMESET[0] will be the family name
+ int expectedSize = VALID_NAMESET.length + VALID_ADDITIONAL_STYLE_NAMESET.length - 1;
+ assertEquals(expectedSize, aliases.size());
+
+ // All aliases should point at the base family
+ for(Alias alias : aliases) {
+ assertEquals(VALID_NAMESET[0], alias.toName);
+ }
+
+ // There should be an alias for every name in the merged in family
+ for(String name : VALID_ADDITIONAL_STYLE_NAMESET) {
+ assertTrue("hasAliasWithName", hasAliasWithName(aliases, name));
+ }
+ }
+
+ /**
+ * sans-serif-condensed should not get merged in with sans-serif
+ */
+ @SmallTest
+ public void testSimiliarFontsShouldKeepSameFamily() {
+ List<Family> families = new ArrayList<Family>();
+ families.add(sValidFamily); //eg "sans-serif"
+ families.add(sValidRelatedFamily); //eg "sans-serif-condensed"
+
+ FontListConverter adapter = new FontListConverter(families, VALID_PATH);
+ List<FontListParser.Family> convertedFamilies = adapter.convertFamilies();
+ FontListParser.Family convertedValidFamily =
+ getFontListFamilyWithName(convertedFamilies, VALID_NAMESET[0]);
+ FontListParser.Family convertedRelatedFamily =
+ getFontListFamilyWithName(convertedFamilies, VALID_RELATED_FAMILY_NAMESET[0]);
+
+
+ // Valid family should only have its own fonts. Will fail if these were merged
+ for(String filename : sValidFamily.fileset) {
+ String fontName = VALID_PATH + filename;
+ assertTrue("hasFontWithName", hasFontWithName(convertedValidFamily, fontName));
+ assertFalse("hasFontWIthName", hasFontWithName(convertedRelatedFamily, fontName));
+ }
+
+ // Related family should also only have have its own fonts. Will fail if these were merged
+ for(String filename : sValidRelatedFamily.fileset) {
+ String fontName = VALID_PATH + filename;
+ assertTrue("hasFontWithName", hasFontWithName(convertedRelatedFamily, fontName));
+ assertFalse("hasFontWIthName", hasFontWithName(convertedValidFamily, fontName));
+ }
+ }
+
+ private static boolean hasAliasWithName(List<Alias> aliases, String name) {
+ for (Alias alias : aliases) if (name.equals(alias.name)) return true;
+ return false;
+ }
+
+ private static boolean hasFontWithName(FontListParser.Family family, String name) {
+ for (FontListParser.Font font : family.fonts) {
+ if(font.fontName != null && font.fontName.equals(name)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static FontListParser.Family getFontListFamilyWithName(
+ List<FontListParser.Family> families, String name) {
+ for(FontListParser.Family family : families) {
+ if (name.equals(family.name)) return family;
+ }
+ return null;
+ }
+
+ private boolean shouldBeItalic(int index) {
+ // Since the fileset format is regular, bold, italic, bolditalic, anything >= 2 is italic
+ return index >= 2;
+ }
+}
diff --git a/graphics/tests/localtests/src/android/graphics/TypefaceTestSuite.java b/graphics/tests/localtests/src/android/graphics/TypefaceTestSuite.java
new file mode 100644
index 00000000000..9ef8421dbc0
--- /dev/null
+++ b/graphics/tests/localtests/src/android/graphics/TypefaceTestSuite.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2008 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 android.graphics;
+
+import junit.framework.TestSuite;
+
+public class TypefaceTestSuite {
+ public static TestSuite suite() {
+ TestSuite suite = new TestSuite(TypefaceTestSuite.class.getName());
+ suite.addTestSuite(FontListConverterTest.class);
+ return suite;
+ }
+}
diff --git a/include/androidfw/AssetManager.h b/include/androidfw/AssetManager.h
index cfe8a99490a..a3f22ec5a74 100644
--- a/include/androidfw/AssetManager.h
+++ b/include/androidfw/AssetManager.h
@@ -75,6 +75,7 @@ public:
static const char* TARGET_PACKAGE_NAME;
static const char* TARGET_APK_PATH;
static const char* IDMAP_DIR;
+ static const char* APK_EXTENSION;
typedef enum CacheMode {
CACHE_UNKNOWN = 0,
@@ -100,7 +101,14 @@ public:
* newly-added asset source.
*/
bool addAssetPath(const String8& path, int32_t* cookie);
- bool addOverlayPath(const String8& path, int32_t* cookie);
+ bool addOverlayPath(const String8& idmapPath, const String8& overlayApkpath, int32_t* cookie,
+ const String8& resApkPath, const String8& targetPkgPath,
+ const String8& prefixPath);
+ bool addCommonOverlayPath(const String8& path, int32_t* cookie,
+ const String8& resApkPath, const String8& prefixPath);
+ bool addIconPath(const String8& path, int32_t* cookie,
+ const String8& resApkPath, const String8& prefixPath, uint32_t pkgIdOverride);
+ bool removeOverlayPath(const String8& path, int32_t cookie);
/*
* Convenience for adding the standard system assets. Uses the
@@ -230,8 +238,12 @@ public:
* Generate idmap data to translate resources IDs between a package and a
* corresponding overlay package.
*/
- bool createIdmap(const char* targetApkPath, const char* overlayApkPath,
- uint32_t targetCrc, uint32_t overlayCrc, uint32_t** outData, size_t* outSize);
+ bool createIdmap(const char* targetApkPath, const char* overlayApkPath, const char* cache_path,
+ uint32_t targetCrc, uint32_t overlayCrc,
+ time_t targetMtime, time_t overlayMtime,
+ uint32_t** outData, size_t* outSize);
+
+ String8 getBasePackageName(uint32_t index);
private:
struct asset_path
@@ -239,11 +251,18 @@ private:
String8 path;
FileType type;
String8 idmap;
+ String8 prefixPath;
+ String8 resfilePath;
+ String8 resApkPath;
+ uint32_t pkgIdOverride;
+ asset_path() : pkgIdOverride(0) {}
};
Asset* openInPathLocked(const char* fileName, AccessMode mode,
const asset_path& path);
Asset* openNonAssetInPathLocked(const char* fileName, AccessMode mode,
+ const asset_path& path, bool usePrefix = true);
+ Asset* openNonAssetInExactPathLocked(const char* fileName, AccessMode mode,
const asset_path& path);
Asset* openInLocaleVendorLocked(const char* fileName, AccessMode mode,
const asset_path& path, const char* locale, const char* vendor);
@@ -254,6 +273,7 @@ private:
const String8& dirName, const String8& fileName);
ZipFileRO* getZipFileLocked(const asset_path& path);
+ ZipFileRO* getZipFileLocked(const String8& path);
Asset* openAssetFromFileLocked(const String8& fileName, AccessMode mode);
Asset* openAssetFromZipLocked(const ZipFileRO* pZipFile,
const ZipEntryRO entry, AccessMode mode, const String8& entryName);
@@ -285,6 +305,10 @@ private:
void addSystemOverlays(const char* pathOverlaysList, const String8& targetPackagePath,
ResTable* sharedRes, size_t offset) const;
+ String8 getPkgName(const char *apkPath);
+
+ String8 getOverlayResPath(const char* cachePath);
+
class SharedZip : public RefBase {
public:
static sp<SharedZip> get(const String8& path, bool createIfNotPresent = true);
@@ -374,6 +398,9 @@ private:
mutable ResTable* mResources;
ResTable_config* mConfig;
+ String8 mBasePackageName;
+ uint32_t mBasePackageIndex;
+
/*
* Cached data for "loose" files. This lets us avoid poking at the
* filesystem when searching for loose assets. Each entry is the
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h
index ac5eca08a4f..dd3af3b7be2 100644
--- a/include/androidfw/ResourceTypes.h
+++ b/include/androidfw/ResourceTypes.h
@@ -1495,10 +1495,11 @@ public:
status_t add(const void* data, size_t size, const int32_t cookie=-1, bool copyData=false);
status_t add(const void* data, size_t size, const void* idmapData, size_t idmapDataSize,
- const int32_t cookie=-1, bool copyData=false);
+ const int32_t cookie=-1, bool copyData=false, const uint32_t pkgIdOverride=0);
status_t add(Asset* asset, const int32_t cookie=-1, bool copyData=false);
- status_t add(Asset* asset, Asset* idmapAsset, const int32_t cookie=-1, bool copyData=false);
+ status_t add(Asset* asset, Asset* idmapAsset, const int32_t cookie=-1, bool copyData=false,
+ const uint32_t pkgIdOverride=0);
status_t add(ResTable* src);
status_t addEmpty(const int32_t cookie);
@@ -1585,7 +1586,7 @@ public:
void lock() const;
ssize_t getBagLocked(uint32_t resID, const bag_entry** outBag,
- uint32_t* outTypeSpecFlags=NULL) const;
+ uint32_t* outTypeSpecFlags=NULL, bool performMapping=true) const;
void unlock() const;
@@ -1770,11 +1771,12 @@ public:
// NO_ERROR; the caller should not free outData.
status_t createIdmap(const ResTable& overlay,
uint32_t targetCrc, uint32_t overlayCrc,
+ time_t targetMtime, time_t overlayMtime,
const char* targetPath, const char* overlayPath,
void** outData, size_t* outSize) const;
enum {
- IDMAP_HEADER_SIZE_BYTES = 4 * sizeof(uint32_t) + 2 * 256,
+ IDMAP_HEADER_SIZE_BYTES = 6 * sizeof(uint32_t) + 2 * 256,
};
// Retrieve idmap meta-data.
@@ -1786,6 +1788,8 @@ public:
uint32_t* pTargetCrc, uint32_t* pOverlayCrc,
String8* pTargetPath, String8* pOverlayPath);
+ void removeAssetsByCookie(const String8 &packageName, int32_t cookie);
+
void print(bool inclValues) const;
static String8 normalizeForOutput(const char* input);
@@ -1799,17 +1803,23 @@ private:
typedef Vector<Type*> TypeList;
status_t addInternal(const void* data, size_t size, const void* idmapData, size_t idmapDataSize,
- const int32_t cookie, bool copyData);
+ const int32_t cookie, bool copyData, const uint32_t pkgIdOverride);
ssize_t getResourcePackageIndex(uint32_t resID) const;
status_t getEntry(
const PackageGroup* packageGroup, int typeIndex, int entryIndex,
const ResTable_config* config,
- Entry* outEntry) const;
+ Entry* outEntry, const bool performMapping=true) const;
status_t parsePackage(
- const ResTable_package* const pkg, const Header* const header);
+ const ResTable_package* const pkg, const Header* const header,
+ const uint32_t pkgIdOverride);
+
+ bool isResTypeAllowed(const char* type) const;
+ bool isDynamicPackageId(const uint32_t pkgId) const;
+ bool isProtectedAttr(uint32_t resID) const;
+ status_t removeIdmappedTypesFromPackageGroup(PackageGroup* packageGroup) const;
void print_value(const Package* pkg, const Res_value& value) const;
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index 7b467194ed3..8c79a105d8f 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2006 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -75,6 +76,7 @@ static const char* kAppZipName = NULL; //"classes.jar";
static const char* kSystemAssets = "framework/framework-res.apk";
static const char* kResourceCache = "resource-cache";
static const char* kAndroidManifest = "AndroidManifest.xml";
+static const int kComposedIconAsset = 128;
static const char* kExcludeExtension = ".EXCLUDE";
@@ -88,9 +90,10 @@ const char* AssetManager::OVERLAY_DIR = "/vendor/overlay";
const char* AssetManager::TARGET_PACKAGE_NAME = "android";
const char* AssetManager::TARGET_APK_PATH = "/system/framework/framework-res.apk";
const char* AssetManager::IDMAP_DIR = "/data/resource-cache";
+const char* AssetManager::APK_EXTENSION = ".apk";
namespace {
- String8 idmapPathForPackagePath(const String8& pkgPath)
+ String8 idmapPathForPackagePath(const String8& pkgPath, const String8& targetPkgPath)
{
const char* root = getenv("ANDROID_DATA");
LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_DATA not set");
@@ -98,7 +101,7 @@ namespace {
path.appendPath(kResourceCache);
char buf[256]; // 256 chars should be enough for anyone...
- strncpy(buf, pkgPath.string(), 255);
+ strncpy(buf, targetPkgPath.string(), 255);
buf[255] = '\0';
char* filename = buf;
while (*filename && *filename == '/') {
@@ -112,6 +115,22 @@ namespace {
++p;
}
path.appendPath(filename);
+ path.append("@");
+
+ strncpy(buf, pkgPath.string(), 255);
+ buf[255] = '\0';
+ filename = buf;
+ while (*filename && *filename == '/') {
+ ++filename;
+ }
+ p = filename;
+ while (*p) {
+ if (*p == '/') {
+ *p = '@';
+ }
+ ++p;
+ }
+ path.append(filename);
path.append("@idmap");
return path;
@@ -134,6 +153,13 @@ namespace {
return newStr;
}
+
+ static String8 flatten_path(const char *path)
+ {
+ String16 tmp(path);
+ tmp.replaceAll('/', '@');
+ return String8(tmp);
+ }
}
/*
@@ -150,7 +176,8 @@ int32_t AssetManager::getGlobalCount()
AssetManager::AssetManager(CacheMode cacheMode)
: mLocale(NULL), mVendor(NULL),
mResources(NULL), mConfig(new ResTable_config),
- mCacheMode(cacheMode), mCacheValid(false)
+ mCacheMode(cacheMode), mCacheValid(false),
+ mBasePackageIndex(-1)
{
int count = android_atomic_inc(&gCount)+1;
//ALOGI("Creating AssetManager %p #%d\n", this, count);
@@ -239,12 +266,34 @@ bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
return true;
}
-bool AssetManager::addOverlayPath(const String8& packagePath, int32_t* cookie)
+/**
+ * packagePath: Path to the APK that contains our overlay
+ * cookie: Set by this method. The caller can use this cookie to refer to the asset path that has
+ * been added.
+ * resApkPath: Path to the overlay's processed and cached resources.
+ * targetPkgPath: Path to the APK we are trying to overlay
+ * prefixPath: This is the base path internal to the overlay APK.
+ * For example, if we have theme "com.redtheme.apk"
+ * with a launcher overlay then this theme will have a structure like this:
+ * assets/
+ * com.android.launcher/
+ * res/
+ * drawable/
+ * foo.png
+ * Our resources.arsc will reference foo.png's path as "res/drawable/foo.png"
+ * so we need "assets/com.android.launcher/" as a prefix
+ */
+bool AssetManager::addOverlayPath(const String8& idmapPath, const String8& overlayPackagePath,
+ int32_t* cookie, const String8& resApkPath, const String8& targetPkgPath,
+ const String8& prefixPath)
{
- const String8 idmapPath = idmapPathForPackagePath(packagePath);
-
AutoMutex _l(mLock);
+ ALOGV("overlayApkPath: %s, idmap Path: %s, resApkPath %s, targetPkgPath: %s",
+ overlayPackagePath.string(), idmapPath.string(),
+ resApkPath.string(),
+ targetPkgPath.string());
+
for (size_t i = 0; i < mAssetPaths.size(); ++i) {
if (mAssetPaths[i].idmap == idmapPath) {
*cookie = static_cast<int32_t>(i + 1);
@@ -268,9 +317,9 @@ bool AssetManager::addOverlayPath(const String8& packagePath, int32_t* cookie)
}
delete idmap;
- if (overlayPath != packagePath) {
+ if (overlayPath != overlayPackagePath) {
ALOGW("idmap file %s inconcistent: expected path %s does not match actual path %s\n",
- idmapPath.string(), packagePath.string(), overlayPath.string());
+ idmapPath.string(), overlayPackagePath.string(), overlayPath.string());
return false;
}
if (access(targetPath.string(), R_OK) != 0) {
@@ -290,10 +339,12 @@ bool AssetManager::addOverlayPath(const String8& packagePath, int32_t* cookie)
oap.path = overlayPath;
oap.type = ::getFileType(overlayPath.string());
oap.idmap = idmapPath;
+ oap.resApkPath = resApkPath;
#if 0
ALOGD("Overlay added: targetPath=%s overlayPath=%s idmapPath=%s\n",
targetPath.string(), overlayPath.string(), idmapPath.string());
#endif
+ oap.prefixPath = prefixPath; //ex: assets/com.foo.bar
mAssetPaths.add(oap);
*cookie = static_cast<int32_t>(mAssetPaths.size());
@@ -305,18 +356,181 @@ bool AssetManager::addOverlayPath(const String8& packagePath, int32_t* cookie)
return true;
}
+bool AssetManager::addCommonOverlayPath(const String8& themePackagePath, int32_t* cookie,
+ const String8& resApkPath, const String8& prefixPath)
+{
+ AutoMutex _l(mLock);
+
+ ALOGV("targetApkPath: %s, resApkPath %s, prefixPath %s",
+ themePackagePath.string(), resApkPath.string(), prefixPath.string());
+
+ // Skip if we have it already.
+ for (size_t i = 0; i < mAssetPaths.size(); ++i) {
+ if (mAssetPaths[i].path == themePackagePath && mAssetPaths[i].resApkPath == resApkPath) {
+ *cookie = static_cast<int32_t>(i + 1);
+ return true;
+ }
+ }
+
+ if (access(themePackagePath.string(), R_OK) != 0) {
+ ALOGW("failed to access file %s: %s\n", themePackagePath.string(), strerror(errno));
+ return false;
+ }
+
+ if (access(resApkPath.string(), R_OK) != 0) {
+ ALOGW("failed to access file %s: %s\n", resApkPath.string(), strerror(errno));
+ return false;
+ }
+
+ asset_path oap;
+ oap.path = themePackagePath;
+ oap.type = ::getFileType(themePackagePath.string());
+ oap.resApkPath = resApkPath;
+ oap.prefixPath = prefixPath;
+ mAssetPaths.add(oap);
+ *cookie = static_cast<int32_t>(mAssetPaths.size());
+
+ if (mResources != NULL) {
+ size_t index = mAssetPaths.size() - 1;
+ appendPathToResTable(oap, &index);
+ }
+
+ return true;
+}
+
+/*
+ * packagePath: Path to the APK that contains our icon assets
+ * cookie: Set by this method. The caller can use this cookie to refer to the added asset path.
+ * resApkPath: Path to the icon APK's processed and cached resources.
+ * prefixPath: This is the base path internal to the icon APK.
+ For example, if we have theme "com.redtheme.apk"
+ * assets/
+ * icons/
+ * res/
+ * drawable/
+ * foo.png
+ * Our restable will reference foo.png's path as "res/drawable/foo.png"
+ * so we need "assets/com.android.launcher/" as a prefix
+ * pkgIdOverride: The package id we want to give. This will overridet the id in the res table.
+ * This is necessary for legacy icon packs because they are compiled with the
+ * standard 7F package id.
+*/
+bool AssetManager::addIconPath(const String8& packagePath, int32_t* cookie,
+ const String8& resApkPath, const String8& prefixPath,
+ uint32_t pkgIdOverride)
+{
+ AutoMutex _l(mLock);
+
+ ALOGV("package path: %s, resApkPath %s, prefixPath %s",
+ packagePath.string(),
+ resApkPath.string(), prefixPath.string());
+
+ // Skip if we have it already.
+ for (size_t i = 0; i < mAssetPaths.size(); i++) {
+ if (mAssetPaths[i].path == packagePath && mAssetPaths[i].resApkPath == resApkPath) {
+ *cookie = static_cast<int32_t>(i + 1);
+ return true;
+ }
+ }
+
+ asset_path oap;
+ oap.path = packagePath;
+ oap.type = ::getFileType(packagePath.string());
+ oap.resApkPath = resApkPath;
+ oap.prefixPath = prefixPath;
+ oap.pkgIdOverride = pkgIdOverride;
+ mAssetPaths.add(oap);
+ *cookie = static_cast<int32_t>(mAssetPaths.size());
+
+ if (mResources != NULL) {
+ size_t index = mAssetPaths.size() - 1;
+ appendPathToResTable(oap, &index);
+ }
+
+ return true;
+}
+
+String8 AssetManager::getPkgName(const char *apkPath) {
+ String8 pkgName;
+
+ asset_path ap;
+ ap.type = kFileTypeRegular;
+ ap.path = String8(apkPath);
+
+ ResXMLTree tree;
+
+ Asset* manifestAsset = openNonAssetInPathLocked(kAndroidManifest, Asset::ACCESS_BUFFER, ap);
+ tree.setTo(manifestAsset->getBuffer(true),
+ manifestAsset->getLength());
+ tree.restart();
+
+ int depth = 0;
+ size_t len;
+ ResXMLTree::event_code_t code;
+ while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code != ResXMLTree::START_TAG) {
+ continue;
+ }
+ String8 tag(tree.getElementName(&len));
+ if (tag != "manifest") break; //Manifest does not start with <manifest>
+ Res_value value;
+ size_t len;
+ ssize_t idx = tree.indexOfAttribute(NULL, "package");
+ const uint16_t* str = tree.getAttributeStringValue(idx, &len);
+ pkgName = (str ? String8(str, len) : String8());
+
+ }
+
+ manifestAsset->close();
+ return pkgName;
+ }
+
+/**
+ * Returns the base package name as defined in the AndroidManifest.xml
+ */
+String8 AssetManager::getBasePackageName(uint32_t index)
+{
+ if (index >= mAssetPaths.size()) return String8::empty();
+
+ if (mBasePackageName.isEmpty() || mBasePackageIndex != index) {
+ mBasePackageName = getPkgName(mAssetPaths[index].path.string());
+ mBasePackageIndex = index;
+ }
+ return mBasePackageName;
+}
+
+String8 AssetManager::getOverlayResPath(const char* cachePath)
+{
+ String8 resPath(cachePath);
+ resPath.append("/");
+ resPath.append("resources");
+ resPath.append(AssetManager::APK_EXTENSION);
+ return resPath;
+}
+
bool AssetManager::createIdmap(const char* targetApkPath, const char* overlayApkPath,
- uint32_t targetCrc, uint32_t overlayCrc, uint32_t** outData, size_t* outSize)
+ const char *cache_path, uint32_t targetCrc, uint32_t overlayCrc,
+ time_t targetMtime, time_t overlayMtime,
+ uint32_t** outData, size_t* outSize)
{
AutoMutex _l(mLock);
const String8 paths[2] = { String8(targetApkPath), String8(overlayApkPath) };
ResTable tables[2];
+ //Our overlay APK might use an external restable
+ String8 resPath = getOverlayResPath(cache_path);
+
for (int i = 0; i < 2; ++i) {
asset_path ap;
ap.type = kFileTypeRegular;
ap.path = paths[i];
- Asset* ass = openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap);
+ Asset* ass;
+ if (i == 1 && access(resPath.string(), R_OK) != -1) {
+ ap.path = resPath;
+ ass = openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap);
+ } else {
+ ass = openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap);
+ }
if (ass == NULL) {
ALOGW("failed to find resources.arsc in %s\n", ap.path.string());
return false;
@@ -324,7 +538,7 @@ bool AssetManager::createIdmap(const char* targetApkPath, const char* overlayApk
tables[i].add(ass);
}
- return tables[0].createIdmap(tables[1], targetCrc, overlayCrc,
+ return tables[0].createIdmap(tables[1], targetCrc, overlayCrc, targetMtime, overlayMtime,
targetApkPath, overlayApkPath, (void**)outData, outSize) == NO_ERROR;
}
@@ -514,6 +728,10 @@ Asset* AssetManager::open(const char* fileName, AccessMode mode)
i--;
ALOGV("Looking for asset '%s' in '%s'\n",
assetName.string(), mAssetPaths.itemAt(i).path.string());
+
+ // Skip theme/icon attached assets
+ if (mAssetPaths.itemAt(i).prefixPath.length() > 0) continue;
+
Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode, mAssetPaths.itemAt(i));
if (pAsset != NULL) {
return pAsset != kExcludedAsset ? pAsset : NULL;
@@ -547,6 +765,10 @@ Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode, int32_t
while (i > 0) {
i--;
ALOGV("Looking for non-asset '%s' in '%s'\n", fileName, mAssetPaths.itemAt(i).path.string());
+
+ // Skip theme/icon attached assets
+ if (mAssetPaths.itemAt(i).prefixPath.length() > 0) continue;
+
Asset* pAsset = openNonAssetInPathLocked(
fileName, mode, mAssetPaths.itemAt(i));
if (pAsset != NULL) {
@@ -577,6 +799,16 @@ Asset* AssetManager::openNonAsset(const int32_t cookie, const char* fileName, Ac
if (pAsset != NULL) {
return pAsset != kExcludedAsset ? pAsset : NULL;
}
+ } else if ((size_t)cookie == kComposedIconAsset) {
+ asset_path ap;
+ String8 path(fileName);
+ ap.type = kFileTypeDirectory;
+ ap.path = path.getPathDir();
+ Asset* pAsset = openNonAssetInPathLocked(
+ path.getPathLeaf().string(), mode, ap);
+ if (pAsset != NULL) {
+ return pAsset;
+ }
}
return NULL;
@@ -614,7 +846,11 @@ bool AssetManager::appendPathToResTable(const asset_path& ap, size_t* entryIdx)
MY_TRACE_BEGIN(ap.path.string());
Asset* idmap = openIdmapLocked(ap);
ALOGV("Looking for resource asset in '%s'\n", ap.path.string());
- if (ap.type != kFileTypeDirectory) {
+ if (!ap.resApkPath.isEmpty()) {
+ // Avoid using prefix path in this case since the compiled resApk will simply have resources.arsc in it
+ ass = const_cast<AssetManager*>(this)->openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap, false);
+ shared = false;
+ } else if (ap.type != kFileTypeDirectory) {
if (*entryIdx == 0) {
// The first item is typically the framework resources,
// which we want to avoid parsing every time.
@@ -646,7 +882,7 @@ bool AssetManager::appendPathToResTable(const asset_path& ap, size_t* entryIdx)
// can quickly copy it out for others.
ALOGV("Creating shared resources for %s", ap.path.string());
sharedRes = new ResTable();
- sharedRes->add(ass, idmap, *entryIdx + 1, false);
+ sharedRes->add(ass, idmap, *entryIdx + 1, false, ap.pkgIdOverride);
#ifdef HAVE_ANDROID_OS
const char* data = getenv("ANDROID_DATA");
LOG_ALWAYS_FATAL_IF(data == NULL, "ANDROID_DATA not set");
@@ -675,7 +911,7 @@ bool AssetManager::appendPathToResTable(const asset_path& ap, size_t* entryIdx)
mResources->add(sharedRes);
} else {
ALOGV("Parsing resources for %s", ap.path.string());
- mResources->add(ass, idmap, *entryIdx + 1, !shared);
+ mResources->add(ass, idmap, *entryIdx + 1, !shared, ap.pkgIdOverride);
}
onlyEmptyResources = false;
@@ -800,7 +1036,7 @@ void AssetManager::addSystemOverlays(const char* pathOverlaysList,
if (oass != NULL) {
Asset* oidmap = openIdmapLocked(oap);
offset++;
- sharedRes->add(oass, oidmap, offset + 1, false);
+ sharedRes->add(oass, oidmap, offset + 1, false, oap.pkgIdOverride);
const_cast<AssetManager*>(this)->mAssetPaths.add(oap);
const_cast<AssetManager*>(this)->mZipSet.addOverlay(targetPackagePath, oap);
}
@@ -808,6 +1044,29 @@ void AssetManager::addSystemOverlays(const char* pathOverlaysList,
fclose(fin);
}
+bool AssetManager::removeOverlayPath(const String8& packageName, int32_t cookie)
+{
+ AutoMutex _l(mLock);
+
+ const size_t which = ((size_t)cookie)-1;
+ if (which >= mAssetPaths.size()) {
+ ALOGE("cookie was larger than paths size");
+ return false;
+ }
+
+ mAssetPaths.removeAt(which);
+
+ ResTable* rt = mResources;
+ if (rt == NULL) {
+ ALOGE("Unable to remove overlayPath, ResTable must not be NULL");
+ return false;
+ }
+
+ rt->removeAssetsByCookie(packageName, cookie);
+
+ return true;
+}
+
const ResTable& AssetManager::getResources(bool required) const
{
const ResTable* rt = getResTable(required);
@@ -842,18 +1101,28 @@ void AssetManager::getLocales(Vector<String8>* locales) const
* Open a non-asset file as if it were an asset, searching for it in the
* specified app.
*
+ *
* Pass in a NULL values for "appName" if the common app directory should
* be used.
*/
Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode mode,
- const asset_path& ap)
+ const asset_path& ap, bool usePrefix)
{
Asset* pAsset = NULL;
+ // Append asset_path prefix if needed
+ const char* fileNameFinal = fileName;
+ String8 fileNameWithPrefix;
+ if (usePrefix && ap.prefixPath.length() > 0) {
+ fileNameWithPrefix.append(ap.prefixPath);
+ fileNameWithPrefix.append(fileName);
+ fileNameFinal = fileNameWithPrefix.string();
+ }
+
/* look at the filesystem on disk */
if (ap.type == kFileTypeDirectory) {
String8 path(ap.path);
- path.appendPath(fileName);
+ path.appendPath(fileNameFinal);
pAsset = openAssetFromFileLocked(path, mode);
@@ -871,24 +1140,45 @@ Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode m
/* look inside the zip file */
} else {
String8 path(fileName);
+ const char* zipPath;
/* check the appropriate Zip file */
- ZipFileRO* pZip = getZipFileLocked(ap);
- if (pZip != NULL) {
- //printf("GOT zip, checking NA '%s'\n", (const char*) path);
- ZipEntryRO entry = pZip->findEntryByName(path.string());
- if (entry != NULL) {
- //printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon);
- pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
- pZip->releaseEntry(entry);
+ ZipFileRO* pZip;
+ ZipEntryRO entry;
+
+ if (!ap.resApkPath.isEmpty()) {
+ pZip = getZipFileLocked(ap.resApkPath);
+ if (pZip != NULL) {
+ //printf("GOT zip, checking NA '%s'\n", (const char*) path);
+ entry = pZip->findEntryByName(path.string());
+ if (entry != NULL) {
+ //printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon);
+ pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
+ zipPath = ap.resApkPath.string();
+ }
+ }
+ }
+
+ if (pAsset == NULL) {
+ path.setTo(fileNameFinal);
+ pZip = getZipFileLocked(ap);
+ if (pZip != NULL) {
+ //printf("GOT zip, checking NA '%s'\n", (const char*) path);
+ ZipEntryRO entry = pZip->findEntryByName(path.string());
+ if (entry != NULL) {
+ //printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon);
+ pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
+ pZip->releaseEntry(entry);
+ zipPath = ap.path.string();
+ }
}
}
if (pAsset != NULL) {
/* create a "source" name, for debug/display */
pAsset->setAssetSource(
- createZipSourceNameLocked(ZipSet::getPathName(ap.path.string()), String8(""),
- String8(fileName)));
+ createZipSourceNameLocked(ZipSet::getPathName(zipPath), String8(""),
+ String8(path.string())));
}
}
@@ -1100,9 +1390,14 @@ String8 AssetManager::createPathNameLocked(const asset_path& ap, const char* roo
*/
ZipFileRO* AssetManager::getZipFileLocked(const asset_path& ap)
{
+ return getZipFileLocked(ap.path);
+}
+
+ZipFileRO* AssetManager::getZipFileLocked(const String8& path)
+{
ALOGV("getZipFileLocked() in %p\n", this);
- return mZipSet.getZip(ap.path);
+ return mZipSet.getZip(path);
}
/*
@@ -1226,6 +1521,10 @@ AssetDir* AssetManager::openDir(const char* dirName)
while (i > 0) {
i--;
const asset_path& ap = mAssetPaths.itemAt(i);
+
+ // Skip theme/icon attached assets
+ if (ap.prefixPath.length() > 0) continue;
+
if (ap.type == kFileTypeRegular) {
ALOGV("Adding directory %s from zip %s", dirName, ap.path.string());
scanAndMergeZipLocked(pMergedInfo, ap, kAssetsRoot, dirName);
@@ -1767,6 +2066,10 @@ void AssetManager::fncScanLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
while (i > 0) {
i--;
const asset_path& ap = mAssetPaths.itemAt(i);
+
+ // Skip theme/icon attached assets
+ if (ap.prefixPath.length() > 0) continue;
+
fncScanAndMergeDirLocked(pMergedInfo, ap, NULL, NULL, dirName);
if (mLocale != NULL)
fncScanAndMergeDirLocked(pMergedInfo, ap, mLocale, NULL, dirName);
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 3dc880a0fa1..8078ef709ef 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -71,6 +71,13 @@ namespace android {
#define APP_PACKAGE_ID 0x7f
#define SYS_PACKAGE_ID 0x01
+#define OVERLAY_APP_PACKAGE_ID 0x61
+#define OVERLAY_SYS_PACKAGE_ID 0x60
+#define OVERLAY_COMMON_PACKAGE_ID 0x5f
+
+// Define attributes from android.R.attr to protect from theme changes
+#define ATTR_WINDOW_NO_TITLE 0x01010056 // windowNoTitle
+#define ATTR_WINDOW_ACTION_BAR 0x010102cd // windowActionBar
// Standard C isspace() is only required to look at the low byte of its input, so
// produces incorrect results for UTF-16 characters. For safety's sake, assume that
@@ -723,7 +730,7 @@ const uint16_t* ResStringPool::stringAt(size_t idx, size_t* u16len) const
mHeader->stringCount*sizeof(char16_t**)));
#else
// We do not want to be in this case when actually running Android.
- ALOGW("CREATING STRING CACHE OF %d bytes",
+ ALOGV("CREATING STRING CACHE OF %d bytes",
mHeader->stringCount*sizeof(char16_t**));
#endif
mCache = (char16_t**)calloc(mHeader->stringCount, sizeof(char16_t**));
@@ -2860,6 +2867,8 @@ struct ResTable::Entry {
StringPoolRef typeStr;
StringPoolRef keyStr;
+
+ bool isFromOverlay;
};
struct ResTable::Type
@@ -2879,7 +2888,8 @@ struct ResTable::Type
struct ResTable::Package
{
Package(ResTable* _owner, const Header* _header, const ResTable_package* _package)
- : owner(_owner), header(_header), package(_package), typeIdOffset(0) {
+ : owner(_owner), header(_header), package(_package), typeIdOffset(0),
+ pkgIdOverride(0) {
if (dtohs(package->header.headerSize) == sizeof(package)) {
// The package structure is the same size as the definition.
// This means it contains the typeIdOffset field.
@@ -2895,6 +2905,7 @@ struct ResTable::Package
ResStringPool keyStrings;
size_t typeIdOffset;
+ uint32_t pkgIdOverride;
};
// A group of objects describing a particular resource package.
@@ -2909,6 +2920,7 @@ struct ResTable::PackageGroup
, largestTypeId(0)
, bags(NULL)
, dynamicRefTable(static_cast<uint8_t>(_id))
+ , overlayPackage(NULL)
{ }
~PackageGroup() {
@@ -2992,6 +3004,8 @@ struct ResTable::PackageGroup
// by having these tables in a per-package scope rather than
// per-package-group.
DynamicRefTable dynamicRefTable;
+
+ Package* overlayPackage;
};
struct ResTable::bag_set
@@ -3285,7 +3299,7 @@ ResTable::ResTable(const void* data, size_t size, const int32_t cookie, bool cop
{
memset(&mParams, 0, sizeof(mParams));
memset(mPackageMap, 0, sizeof(mPackageMap));
- addInternal(data, size, NULL, 0, cookie, copyData);
+ addInternal(data, size, NULL, 0, cookie, copyData, 0);
LOG_FATAL_IF(mError != NO_ERROR, "Error parsing resource table");
//ALOGI("Creating ResTable %p\n", this);
}
@@ -3302,12 +3316,12 @@ inline ssize_t ResTable::getResourcePackageIndex(uint32_t resID) const
}
status_t ResTable::add(const void* data, size_t size, const int32_t cookie, bool copyData) {
- return addInternal(data, size, NULL, 0, cookie, copyData);
+ return addInternal(data, size, NULL, 0, cookie, copyData, 0);
}
status_t ResTable::add(const void* data, size_t size, const void* idmapData, size_t idmapDataSize,
- const int32_t cookie, bool copyData) {
- return addInternal(data, size, idmapData, idmapDataSize, cookie, copyData);
+ const int32_t cookie, bool copyData, const uint32_t pkgIdOverride) {
+ return addInternal(data, size, idmapData, idmapDataSize, cookie, copyData, pkgIdOverride);
}
status_t ResTable::add(Asset* asset, const int32_t cookie, bool copyData) {
@@ -3317,10 +3331,11 @@ status_t ResTable::add(Asset* asset, const int32_t cookie, bool copyData) {
return UNKNOWN_ERROR;
}
- return addInternal(data, static_cast<size_t>(asset->getLength()), NULL, 0, cookie, copyData);
+ return addInternal(data, static_cast<size_t>(asset->getLength()), NULL, 0, cookie, copyData, 0);
}
-status_t ResTable::add(Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData) {
+status_t ResTable::add(Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData,
+ const uint32_t pkgIdOverride) {
const void* data = asset->getBuffer(true);
if (data == NULL) {
ALOGW("Unable to get buffer of resource asset file");
@@ -3339,7 +3354,7 @@ status_t ResTable::add(Asset* asset, Asset* idmapAsset, const int32_t cookie, bo
}
return addInternal(data, static_cast<size_t>(asset->getLength()),
- idmapData, idmapSize, cookie, copyData);
+ idmapData, idmapSize, cookie, copyData, pkgIdOverride);
}
status_t ResTable::add(ResTable* src)
@@ -3393,7 +3408,7 @@ status_t ResTable::addEmpty(const int32_t cookie) {
}
status_t ResTable::addInternal(const void* data, size_t dataSize, const void* idmapData, size_t idmapDataSize,
- const int32_t cookie, bool copyData)
+ const int32_t cookie, bool copyData, const uint32_t pkgIdOverride)
{
if (!data) {
return NO_ERROR;
@@ -3489,7 +3504,11 @@ status_t ResTable::addInternal(const void* data, size_t dataSize, const void* id
return (mError=BAD_TYPE);
}
- if (parsePackage((ResTable_package*)chunk, header) != NO_ERROR) {
+ // Warning: If the pkg id will be overriden and there is more than one package in the
+ // resource table then the caller should make sure there are enough unique ids above
+ // pkgIdOverride.
+ uint32_t idOverride = (pkgIdOverride == 0) ? 0 : pkgIdOverride + curPackage;
+ if (parsePackage((ResTable_package*)chunk, header, idOverride) != NO_ERROR) {
return mError;
}
curPackage++;
@@ -3767,8 +3786,27 @@ void ResTable::unlock() const
mLock.unlock();
}
+// Protected attributes are not permitted to be themed. If a theme
+// does try to change a protected attribute it will be overriden
+// by the app's original value.
+const static uint32_t PROTECTED_ATTRS[] = {
+ ATTR_WINDOW_NO_TITLE,
+ ATTR_WINDOW_ACTION_BAR
+};
+
+bool ResTable::isProtectedAttr(uint32_t resID) const
+{
+ int length = sizeof(PROTECTED_ATTRS) / sizeof(PROTECTED_ATTRS[0]);
+ for(int i=0; i < length; i++) {
+ if (PROTECTED_ATTRS[i] == resID) {
+ return true;
+ }
+ }
+ return false;
+}
+
ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
- uint32_t* outTypeSpecFlags) const
+ uint32_t* outTypeSpecFlags, bool performMapping) const
{
if (mError != NO_ERROR) {
return mError;
@@ -3808,7 +3846,7 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
}
// First see if we've already computed this bag...
- if (grp->bags) {
+ if (grp->bags && performMapping) {
bag_set** typeSet = grp->bags->get(t);
if (typeSet) {
bag_set* set = typeSet[e];
@@ -3848,7 +3886,7 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
// Now collect all bag attributes
Entry entry;
- status_t err = getEntry(grp, t, e, &mParams, &entry);
+ status_t err = getEntry(grp, t, e, &mParams, &entry, performMapping);
if (err != NO_ERROR) {
return err;
}
@@ -3887,7 +3925,8 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
const bag_entry* parentBag;
uint32_t parentTypeSpecFlags = 0;
- const ssize_t NP = getBagLocked(resolvedParent, &parentBag, &parentTypeSpecFlags);
+ const ssize_t NP = getBagLocked(resolvedParent, &parentBag, &parentTypeSpecFlags,
+ resolvedParent != resID);
const size_t NT = ((NP >= 0) ? NP : 0) + N;
set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*NT);
if (set == NULL) {
@@ -4010,6 +4049,78 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
set->numAttrs = curEntry;
}
+ if (entry.isFromOverlay) {
+ const bag_entry* originalBag;
+ uint32_t originalTypeSpecFlags = 0;
+ const ssize_t NO = getBagLocked(resID, &originalBag,
+ &originalTypeSpecFlags, false);
+ if (NO <= 0) {
+ ALOGW("Failed to retrieve original bag for 0x%08x", resID);
+ }
+
+ // Now merge in the original attributes...
+ bag_entry* entries = (bag_entry*)(set+1);
+ size_t curEntry = 0;
+ uint32_t pos = 0;
+ for (int i = 0; i < NO; i++) {
+ TABLE_NOISY(printf("Now at %d\n", i));
+ const uint32_t newName = originalBag[i].map.name.ident;
+ bool isInside;
+ uint32_t oldName = 0;
+ curEntry = 0;
+
+ while ((isInside=(curEntry < set->numAttrs))
+ && (oldName=entries[curEntry].map.name.ident) < newName) {
+ curEntry++;
+ }
+
+ if ((!isInside) || oldName != newName) {
+ // This is a new attribute... figure out what to do with it.
+ // Need to alloc more memory...
+ size_t prevEntry = curEntry;
+ curEntry = set->availAttrs;
+ set->availAttrs++;
+ const size_t newAvail = set->availAttrs;
+ set = (bag_set*)realloc(set,
+ sizeof(bag_set)
+ + sizeof(bag_entry)*newAvail);
+ if (set == NULL) {
+ return NO_MEMORY;
+ }
+ entries = (bag_entry*)(set+1);
+ TABLE_NOISY(printf("Reallocated set %p, entries=%p, avail=%d\n",
+ set, entries, set->availAttrs));
+ if (isInside) {
+ // Going in the middle, need to make space.
+ memmove(entries+prevEntry+1, entries+prevEntry,
+ sizeof(bag_entry)*(set->numAttrs-prevEntry));
+ }
+ TABLE_NOISY(printf("#%d: Inserting new attribute: 0x%08x\n",
+ curEntry, newName));
+
+ bag_entry* cur = entries+curEntry;
+
+ cur->stringBlock = originalBag[i].stringBlock;
+ cur->map.name.ident = originalBag[i].map.name.ident;
+ cur->map.value = originalBag[i].map.value;
+ set->typeSpecFlags |= originalTypeSpecFlags;
+ set->numAttrs = set->availAttrs;
+ TABLE_NOISY(printf("Setting entry #%d %p: block=%d, name=0x%08x, type=%d, \
+ data=0x%08x\n",
+ curEntry, cur, cur->stringBlock, cur->map.name.ident,
+ cur->map.value.dataType, cur->map.value.data));
+ } else if (isProtectedAttr(newName)) {
+ // The attribute exists in both the original and the new theme bags,
+ // furthermore it is an attribute we don't wish themers to theme, so
+ // give our current themed bag the same value as the original
+ bag_entry* cur = entries+curEntry;
+ cur->stringBlock = originalBag[i].stringBlock;
+ cur->map.name.ident = originalBag[i].map.name.ident;
+ cur->map.value = originalBag[i].map.value;
+ }
+ }
+ }
+
// And this is it...
typeSet[e] = set;
if (set) {
@@ -4692,7 +4803,7 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString,
}
uint32_t packageId = Res_GETPACKAGE(rid) + 1;
- if (packageId != APP_PACKAGE_ID && packageId != SYS_PACKAGE_ID) {
+ if (isDynamicPackageId(packageId)) {
outValue->dataType = Res_value::TYPE_DYNAMIC_REFERENCE;
}
outValue->data = rid;
@@ -4711,7 +4822,7 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString,
outValue->data = rid;
outValue->dataType = Res_value::TYPE_DYNAMIC_REFERENCE;
return true;
- } else if (packageId == APP_PACKAGE_ID || packageId == SYS_PACKAGE_ID) {
+ } else if (!isDynamicPackageId(packageId)) {
// We accept packageId's generated as 0x01 in order to support
// building the android system resources
outValue->data = rid;
@@ -5438,7 +5549,8 @@ bool ResTable::getResourceFlags(uint32_t resID, uint32_t* outFlags) const {
status_t ResTable::getEntry(
const PackageGroup* packageGroup, int typeIndex, int entryIndex,
const ResTable_config* config,
- Entry* outEntry) const
+ Entry* outEntry,
+ const bool performMapping) const
{
const TypeList& typeList = packageGroup->types[typeIndex];
if (typeList.isEmpty()) {
@@ -5454,6 +5566,8 @@ status_t ResTable::getEntry(
ResTable_config bestConfig;
memset(&bestConfig, 0, sizeof(bestConfig));
+ bool currentTypeIsOverlay = false;
+
// Iterate over the Types of each package.
const size_t typeCount = typeList.size();
for (size_t i = 0; i < typeCount; i++) {
@@ -5461,11 +5575,11 @@ status_t ResTable::getEntry(
int realEntryIndex = entryIndex;
int realTypeIndex = typeIndex;
- bool currentTypeIsOverlay = false;
+ currentTypeIsOverlay = false;
// Runtime overlay packages provide a mapping of app resource
// ID to package resource ID.
- if (typeSpec->idmapEntries.hasEntries()) {
+ if (performMapping && typeSpec->idmapEntries.hasEntries()) {
uint16_t overlayEntryIndex;
if (typeSpec->idmapEntries.lookup(entryIndex, &overlayEntryIndex) != NO_ERROR) {
// No such mapping exists
@@ -5523,7 +5637,7 @@ status_t ResTable::getEntry(
// Check if this one is less specific than the last found. If so,
// we will skip it. We check starting with things we most care
// about to those we least care about.
- if (!thisConfig.isBetterThan(bestConfig, config)) {
+ if (!currentTypeIsOverlay && !thisConfig.isBetterThan(bestConfig, config)) {
if (!currentTypeIsOverlay || thisConfig.compare(bestConfig) != 0) {
continue;
}
@@ -5574,12 +5688,13 @@ status_t ResTable::getEntry(
outEntry->package = bestPackage;
outEntry->typeStr = StringPoolRef(&bestPackage->typeStrings, actualTypeIndex - bestPackage->typeIdOffset);
outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, dtohl(entry->key.index));
+ outEntry->isFromOverlay = currentTypeIsOverlay;
}
return NO_ERROR;
}
status_t ResTable::parsePackage(const ResTable_package* const pkg,
- const Header* const header)
+ const Header* const header, const uint32_t pkgIdOverride)
{
const uint8_t* base = (const uint8_t*)pkg;
status_t err = validate_chunk(&pkg->header, sizeof(*pkg) - sizeof(pkg->typeIdOffset),
@@ -5613,15 +5728,14 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
uint32_t id = dtohl(pkg->id);
KeyedVector<uint8_t, IdmapEntries> idmapEntries;
+ uint8_t targetPackageId = 0;
if (header->resourceIDMap != NULL) {
- uint8_t targetPackageId = 0;
status_t err = parseIdmap(header->resourceIDMap, header->resourceIDMapSize, &targetPackageId, &idmapEntries);
if (err != NO_ERROR) {
ALOGW("Overlay is broken");
return (mError=err);
}
- id = targetPackageId;
}
if (id >= 256) {
@@ -5632,11 +5746,17 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
id = mNextPackageId++;
}
+ if (pkgIdOverride != 0) {
+ ALOGV("Overriding pkg id %d with %d", id, pkgIdOverride);
+ id = pkgIdOverride;
+ }
+
PackageGroup* group = NULL;
Package* package = new Package(this, header, pkg);
if (package == NULL) {
return (mError=NO_MEMORY);
}
+ package->pkgIdOverride = pkgIdOverride;
err = package->typeStrings.setTo(base+dtohl(pkg->typeStrings),
header->dataEnd-(base+dtohl(pkg->typeStrings)));
@@ -5691,6 +5811,15 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
return (mError=err);
}
+ // Get the target group if this is an overlay
+ PackageGroup* targetGroup = NULL;
+ if (header->resourceIDMap != NULL) {
+ targetGroup = mPackageGroups.itemAt(mPackageMap[targetPackageId] - 1);
+ if (targetGroup != NULL) {
+ targetGroup->overlayPackage = package;
+ }
+ }
+
// Iterate through all chunks.
const ResChunk_header* chunk =
(const ResChunk_header*)(((const uint8_t*)pkg)
@@ -5736,15 +5865,11 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
if (newEntryCount > 0) {
uint8_t typeIndex = typeSpec->id - 1;
- ssize_t idmapIndex = idmapEntries.indexOfKey(typeSpec->id);
- if (idmapIndex >= 0) {
- typeIndex = idmapEntries[idmapIndex].targetTypeId() - 1;
- }
TypeList& typeList = group->types.editItemAt(typeIndex);
if (!typeList.isEmpty()) {
const Type* existingType = typeList[0];
- if (existingType->entryCount != newEntryCount && idmapIndex < 0) {
+ if (existingType->entryCount != newEntryCount) {
ALOGW("ResTable_typeSpec entry count inconsistent: given %d, previously %d",
(int) newEntryCount, (int) existingType->entryCount);
// We should normally abort here, but some legacy apps declare
@@ -5756,11 +5881,23 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
t->typeSpec = typeSpec;
t->typeSpecFlags = (const uint32_t*)(
((const uint8_t*)typeSpec) + dtohs(typeSpec->header.headerSize));
- if (idmapIndex >= 0) {
- t->idmapEntries = idmapEntries[idmapIndex];
- }
typeList.add(t);
group->largestTypeId = max(group->largestTypeId, typeSpec->id);
+
+ // Add this type spec to the targetGroup
+ if (targetGroup != NULL) {
+ ssize_t idmapIndex = idmapEntries.indexOfKey(typeSpec->id);
+ if (idmapIndex >= 0) {
+ typeIndex = idmapEntries[idmapIndex].targetTypeId() - 1;
+ TypeList& typeList = targetGroup->types.editItemAt(typeIndex);
+ Type* t = new Type(header, package, newEntryCount);
+ t->idmapEntries = idmapEntries[idmapIndex];
+ t->typeSpec = typeSpec;
+ t->typeSpecFlags = (const uint32_t*)(
+ ((const uint8_t*)typeSpec) + dtohs(typeSpec->header.headerSize));
+ typeList.add(t);
+ }
+ }
} else {
ALOGV("Skipping empty ResTable_typeSpec for type %d", typeSpec->id);
}
@@ -5803,10 +5940,6 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
if (newEntryCount > 0) {
uint8_t typeIndex = type->id - 1;
- ssize_t idmapIndex = idmapEntries.indexOfKey(type->id);
- if (idmapIndex >= 0) {
- typeIndex = idmapEntries[idmapIndex].targetTypeId() - 1;
- }
TypeList& typeList = group->types.editItemAt(typeIndex);
if (typeList.isEmpty()) {
@@ -5833,6 +5966,35 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
thisConfig.copyFromDtoH(type->config);
ALOGI("Adding config to type %d: %s\n",
type->id, thisConfig.toString().string()));
+
+ // Add this type to the targetGroup
+ if (targetGroup != NULL) {
+ ssize_t idmapIndex = idmapEntries.indexOfKey(type->id);
+ if (idmapIndex >= 0) {
+ typeIndex = idmapEntries[idmapIndex].targetTypeId() - 1;
+ TypeList& typeList = targetGroup->types.editItemAt(typeIndex);
+ if (typeList.isEmpty()) {
+ ALOGE("No TypeSpec for type %d", type->id);
+ return (mError=BAD_TYPE);
+ }
+ Type* t = typeList.editItemAt(typeList.size() - 1);
+ if (newEntryCount != t->entryCount) {
+ ALOGE("ResTable_type entry count inconsistent: given %d, previously %d",
+ (int)newEntryCount, (int)t->entryCount);
+ return (mError=BAD_TYPE);
+ }
+ if (t->package != package) {
+ ALOGE("No TypeSpec for type %d", type->id);
+ return (mError=BAD_TYPE);
+ }
+ t->configs.add(type);
+ TABLE_GETENTRY(
+ ResTable_config thisConfig;
+ thisConfig.copyFromDtoH(type->config);
+ ALOGI("Adding config to type %d: %s\n",
+ type->id, thisConfig.toString().string()));
+ }
+ }
} else {
ALOGV("Skipping empty ResTable_type for type %d", type->id);
}
@@ -5874,6 +6036,9 @@ DynamicRefTable::DynamicRefTable(uint8_t packageId)
// Reserved package ids
mLookupTable[APP_PACKAGE_ID] = APP_PACKAGE_ID;
mLookupTable[SYS_PACKAGE_ID] = SYS_PACKAGE_ID;
+ mLookupTable[OVERLAY_APP_PACKAGE_ID] = OVERLAY_APP_PACKAGE_ID;
+ mLookupTable[OVERLAY_SYS_PACKAGE_ID] = OVERLAY_SYS_PACKAGE_ID;
+ mLookupTable[OVERLAY_COMMON_PACKAGE_ID] = OVERLAY_COMMON_PACKAGE_ID;
}
status_t DynamicRefTable::load(const ResTable_lib_header* const header)
@@ -5950,7 +6115,8 @@ status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const {
uint32_t res = *resId;
size_t packageId = Res_GETPACKAGE(res) + 1;
- if (packageId == APP_PACKAGE_ID) {
+ if (packageId == APP_PACKAGE_ID || packageId == OVERLAY_APP_PACKAGE_ID ||
+ packageId == OVERLAY_SYS_PACKAGE_ID || packageId == OVERLAY_COMMON_PACKAGE_ID) {
// No lookup needs to be done, app package IDs are absolute.
return NO_ERROR;
}
@@ -6002,6 +6168,7 @@ struct IdmapTypeMap {
status_t ResTable::createIdmap(const ResTable& overlay,
uint32_t targetCrc, uint32_t overlayCrc,
+ time_t targetMtime, time_t overlayMtime,
const char* targetPath, const char* overlayPath,
void** outData, size_t* outSize) const
{
@@ -6032,84 +6199,111 @@ status_t ResTable::createIdmap(const ResTable& overlay,
// overlay packages are assumed to contain only one package group
const String16 overlayPackage(overlay.mPackageGroups[0]->packages[0]->package->name);
+ Package* pkg;
+ size_t typeCount;
+ uint32_t pkg_id;
+
+ const uint32_t groupCount = mPackageGroups.size();
+ for (int groupIdx = groupCount - 1; groupIdx >= 0; groupIdx--) {
+ bool foundRedirection = false;
+ pg = mPackageGroups[groupIdx];
+ pkg = pg->packages[0];
+ typeCount = pg->types.size();
+ pkg_id = pkg->package->id << 24;
+
+ for (size_t typeIndex = 0; typeIndex < typeCount; ++typeIndex) {
+ const TypeList& typeList = pg->types[typeIndex];
+ if (typeList.isEmpty()) {
+ continue;
+ }
- for (size_t typeIndex = 0; typeIndex < pg->types.size(); ++typeIndex) {
- const TypeList& typeList = pg->types[typeIndex];
- if (typeList.isEmpty()) {
- continue;
- }
-
- const Type* typeConfigs = typeList[0];
+ const Type* typeConfigs = typeList[0];
- IdmapTypeMap typeMap;
- typeMap.overlayTypeId = -1;
- typeMap.entryOffset = 0;
+ IdmapTypeMap typeMap;
+ typeMap.overlayTypeId = -1;
+ typeMap.entryOffset = 0;
- for (size_t entryIndex = 0; entryIndex < typeConfigs->entryCount; ++entryIndex) {
- uint32_t resID = Res_MAKEID(pg->id - 1, typeIndex, entryIndex);
- resource_name resName;
- if (!this->getResourceName(resID, false, &resName)) {
- if (typeMap.entryMap.isEmpty()) {
- typeMap.entryOffset++;
+ for (size_t entryIndex = 0; entryIndex < typeConfigs->entryCount; ++entryIndex) {
+ uint32_t resID = Res_MAKEID(pg->id - 1, typeIndex, entryIndex);
+ resource_name resName;
+ if (!this->getResourceName(resID, false, &resName)) {
+ if (typeMap.entryMap.isEmpty()) {
+ typeMap.entryOffset++;
+ }
+ continue;
}
- continue;
- }
- const String16 overlayType(resName.type, resName.typeLen);
- const String16 overlayName(resName.name, resName.nameLen);
- uint32_t overlayResID = overlay.identifierForName(overlayName.string(),
- overlayName.size(),
- overlayType.string(),
- overlayType.size(),
- overlayPackage.string(),
- overlayPackage.size());
- if (overlayResID == 0) {
- if (typeMap.entryMap.isEmpty()) {
- typeMap.entryOffset++;
+ // check if resource type is "allowed", if not continue
+ String8 type8;
+ if (resName.type8 != NULL) {
+ type8 = String8(resName.type8, resName.typeLen);
+ } else {
+ type8 = String8(resName.type, resName.typeLen);
+ }
+ if (!isResTypeAllowed(type8.string())) {
+ if (typeMap.entryMap.isEmpty()) {
+ typeMap.entryOffset++;
+ }
+ continue;
}
- continue;
- }
- if (typeMap.overlayTypeId == -1) {
- typeMap.overlayTypeId = Res_GETTYPE(overlayResID) + 1;
- }
+ const String16 overlayType(resName.type, resName.typeLen);
+ const String16 overlayName(resName.name, resName.nameLen);
+ uint32_t overlayResID = overlay.identifierForName(overlayName.string(),
+ overlayName.size(),
+ overlayType.string(),
+ overlayType.size(),
+ overlayPackage.string(),
+ overlayPackage.size());
+ if (overlayResID == 0) {
+ if (typeMap.entryMap.isEmpty()) {
+ typeMap.entryOffset++;
+ }
+ continue;
+ } else {
+ overlayResID = pkg_id | (0x00ffffff & overlayResID);
+ }
- if (Res_GETTYPE(overlayResID) + 1 != static_cast<size_t>(typeMap.overlayTypeId)) {
- ALOGE("idmap: can't mix type ids in entry map. Resource 0x%08x maps to 0x%08x"
- " but entries should map to resources of type %02x",
- resID, overlayResID, typeMap.overlayTypeId);
- return BAD_TYPE;
- }
+ if (typeMap.overlayTypeId == -1) {
+ typeMap.overlayTypeId = Res_GETTYPE(overlayResID) + 1;
+ }
- if (typeMap.entryOffset + typeMap.entryMap.size() < entryIndex) {
- // Resize to accomodate this entry and the 0's in between.
- const size_t oldSize = typeMap.entryMap.size();
- if (typeMap.entryMap.resize((entryIndex - typeMap.entryOffset) + 1) < 0) {
- return NO_MEMORY;
+ if (Res_GETTYPE(overlayResID) + 1 != static_cast<size_t>(typeMap.overlayTypeId)) {
+ ALOGE("idmap: can't mix type ids in entry map. Resource 0x%08x maps to 0x%08x"
+ " but entries should map to resources of type %02x",
+ resID, overlayResID, typeMap.overlayTypeId);
+ return BAD_TYPE;
}
- const size_t newSize = typeMap.entryMap.size();
- for (size_t i = oldSize; i < newSize; ++i) {
- // As this entry is not present in this idmap, so init the item as 0xffffffff.
- // Please refer to the function IdmapEntries.lookup().
- typeMap.entryMap.editItemAt(i) = 0xffffffff;
+
+ if (typeMap.entryOffset + typeMap.entryMap.size() < entryIndex) {
+ // Resize to accomodate this entry and the 0's in between.
+ const size_t oldSize = typeMap.entryMap.size();
+ if (typeMap.entryMap.resize((entryIndex - typeMap.entryOffset) + 1) < 0) {
+ return NO_MEMORY;
+ }
+ const size_t newSize = typeMap.entryMap.size();
+ for (size_t i = oldSize; i < newSize; ++i) {
+ // As this entry is not present in this idmap, so init the item as 0xffffffff.
+ // Please refer to the function IdmapEntries.lookup().
+ typeMap.entryMap.editItemAt(i) = 0xffffffff;
+ }
+ typeMap.entryMap.editTop() = Res_GETENTRY(overlayResID);
+ } else {
+ typeMap.entryMap.add(Res_GETENTRY(overlayResID));
}
- typeMap.entryMap.editTop() = Res_GETENTRY(overlayResID);
- } else {
- typeMap.entryMap.add(Res_GETENTRY(overlayResID));
}
- }
- if (!typeMap.entryMap.isEmpty()) {
- if (map.add(static_cast<uint8_t>(typeIndex), typeMap) < 0) {
- return NO_MEMORY;
+ if (!typeMap.entryMap.isEmpty()) {
+ if (map.add(static_cast<uint8_t>(typeIndex), typeMap) < 0) {
+ return NO_MEMORY;
+ }
+ *outSize += (4 * sizeof(uint16_t)) + (typeMap.entryMap.size() * sizeof(uint32_t));
}
- *outSize += (4 * sizeof(uint16_t)) + (typeMap.entryMap.size() * sizeof(uint32_t));
}
}
if (map.isEmpty()) {
ALOGW("idmap: no resources in overlay package present in base package");
- return UNKNOWN_ERROR;
}
if ((*outData = malloc(*outSize)) == NULL) {
@@ -6121,6 +6315,8 @@ status_t ResTable::createIdmap(const ResTable& overlay,
*data++ = htodl(IDMAP_CURRENT_VERSION);
*data++ = htodl(targetCrc);
*data++ = htodl(overlayCrc);
+ *data++ = htodl(targetMtime);
+ *data++ = htodl(overlayMtime);
const char* paths[] = { targetPath, overlayPath };
for (int j = 0; j < 2; ++j) {
char* p = (char*)data;
@@ -6177,14 +6373,122 @@ bool ResTable::getIdmapInfo(const void* idmap, size_t sizeBytes,
*pOverlayCrc = dtohl(map[3]);
}
if (pTargetPath) {
- pTargetPath->setTo(reinterpret_cast<const char*>(map + 4));
+ pTargetPath->setTo(reinterpret_cast<const char*>(map + 6));
}
if (pOverlayPath) {
- pOverlayPath->setTo(reinterpret_cast<const char*>(map + 4 + 256 / sizeof(uint32_t)));
+ pOverlayPath->setTo(reinterpret_cast<const char*>(map + 6 + 256 / sizeof(uint32_t)));
}
return true;
}
+void ResTable::removeAssetsByCookie(const String8 &packageName, int32_t cookie)
+{
+ mError = NO_ERROR;
+ size_t pgCount = mPackageGroups.size();
+ for (size_t pgIndex = 0; pgIndex < pgCount; pgIndex++) {
+ PackageGroup* pg = mPackageGroups[pgIndex];
+ size_t pkgCount = pg->packages.size();
+ ALOGV("Looking at pgIndex: %d, pkgCount: %d", pgIndex, pkgCount);
+ size_t index = pkgCount;
+ for (size_t pkgIndex = 0; pkgIndex < pkgCount; pkgIndex++) {
+ const Package* pkg = pg->packages[pkgIndex];
+ ALOGV("Examining pkg: %s cookie: %d", String8(pkg->package->name).string(),
+ pkg->header->cookie);
+ if (pkg->header->cookie == cookie) {
+ index = pkgIndex;
+ ALOGV("Delete Package %d id=%d name=%s\n",
+ (int32_t)pkgIndex, pkg->package->id,
+ String8(String16(pkg->package->name)).string());
+ break;
+ }
+ }
+ if (index < pkgCount) {
+ const Package* pkg = pg->packages[index];
+ ALOGV("Looking at pkg: %s", String8(pkg->package->name).string());
+ uint32_t id = dtohl(pkg->package->id);
+ if (pkg->pkgIdOverride != 0) {
+ id = pkg->pkgIdOverride;
+ }
+ if (id != 0 && id < 256 && pkgCount == 1) {
+ ALOGV("Settings id:%d to zero in mPackageMap", id);
+ mPackageMap[id] = 0;
+ }
+ // Check if this package is being reference in any other groups and remove it
+ size_t N = mPackageGroups.size();
+ for (int i = 0; i < N; i++) {
+ PackageGroup* grp = mPackageGroups.itemAt(i);
+ if (grp->overlayPackage == pkg) {
+ removeIdmappedTypesFromPackageGroup(grp);
+ grp->clearBagCache();
+ grp->overlayPackage = NULL;
+ }
+ }
+ if (pkgCount == 1) {
+ ALOGV("Delete Package Group %d id=%d packageCount=%d name=%s\n",
+ (int)pgIndex, pg->id, (int)pg->packages.size(),
+ String8(pg->name).string());
+ mPackageGroups.removeAt(pgIndex);
+ delete pg;
+ } else {
+ ALOGV("Delete package at %d", index);
+ pg->packages.removeAt(index);
+ delete pkg;
+ }
+ break;
+ } else {
+ ALOGV("idx > pkgCount");
+ }
+ }
+ ALOGV("Removing cookie %d for package %s", cookie, packageName.string());
+ size_t N = mHeaders.size();
+ for (size_t i = 0; i < N; i++) {
+ Header* header = mHeaders[i];
+ if (header->cookie == cookie) {
+ if (header->ownedData != NULL) {
+ free(header->ownedData);
+ }
+ mHeaders.removeAt(i);
+ break;
+ }
+ }
+}
+
+bool ResTable::isResTypeAllowed(const char* type) const {
+ if (type == NULL) return false;
+ const char* allowedResources[] = { "color", "dimen", "drawable", "mipmap", "style", "anim" };
+ // ALLOWED_RESOURCE_COUNT should match the number of elements in allowedResources
+ const uint32_t ALLOWED_RESOURCE_COUNT = 6;
+ for (uint32_t i = 0; i < ALLOWED_RESOURCE_COUNT; i++) {
+ if (strstr(type, allowedResources[i]) != NULL) return true;
+ }
+ return false;
+}
+
+bool ResTable::isDynamicPackageId(const uint32_t pkgId) const {
+ return pkgId != APP_PACKAGE_ID && pkgId != SYS_PACKAGE_ID
+ && pkgId != OVERLAY_APP_PACKAGE_ID && pkgId != OVERLAY_SYS_PACKAGE_ID
+ && pkgId != OVERLAY_COMMON_PACKAGE_ID;
+}
+
+status_t ResTable::removeIdmappedTypesFromPackageGroup(PackageGroup* packageGroup) const {
+ for (size_t idx = 0; idx < Res_MAXTYPE; idx++) {
+ const TypeList& typeList = packageGroup->types[idx];
+ if (!typeList.isEmpty()) {
+ TypeList& editTypeList = packageGroup->types.editItemAt(idx);
+ // Iterate over the Types of each package.
+ for (Vector<Type*>::iterator iter = editTypeList.begin();
+ iter != editTypeList.end();) {
+ Type* type = *iter;
+ if (type->idmapEntries.hasEntries()) {
+ iter = editTypeList.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+ }
+ }
+ return NO_ERROR;
+}
#define CHAR16_TO_CSTR(c16, len) (String8(String16(c16,len)).string())
diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk
index 5808d201643..632780118c6 100644
--- a/libs/androidfw/tests/Android.mk
+++ b/libs/androidfw/tests/Android.mk
@@ -23,10 +23,12 @@ testFiles := \
ByteBucketArray_test.cpp \
Config_test.cpp \
ConfigLocale_test.cpp \
- Idmap_test.cpp \
+ PackageIdOverride_test.cpp \
ResTable_test.cpp \
Split_test.cpp \
Theme_test.cpp \
+ ThemesBags_test.cpp \
+ ThemesIdmap_test.cpp \
TypeWrappers_test.cpp \
ZipUtils_test.cpp
diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp
index f50c17890b1..181a7ccf324 100644
--- a/libs/androidfw/tests/Idmap_test.cpp
+++ b/libs/androidfw/tests/Idmap_test.cpp
@@ -50,7 +50,7 @@ protected:
ASSERT_EQ(NO_ERROR, mTargetTable.add(basic_arsc, basic_arsc_len));
ASSERT_EQ(NO_ERROR, mOverlayTable.add(overlay_arsc, overlay_arsc_len));
char targetName[256] = "com.android.test.basic";
- ASSERT_EQ(NO_ERROR, mTargetTable.createIdmap(mOverlayTable, 0, 0,
+ ASSERT_EQ(NO_ERROR, mTargetTable.createIdmap(mOverlayTable, 0, 0, 0, 0,
targetName, targetName, &mData, &mDataSize));
}
diff --git a/libs/androidfw/tests/PackageIdOverride_test.cpp b/libs/androidfw/tests/PackageIdOverride_test.cpp
new file mode 100644
index 00000000000..dc59e452e47
--- /dev/null
+++ b/libs/androidfw/tests/PackageIdOverride_test.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2014 The CyanogenMod 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 <utils/String16.h>
+#include "TestHelpers.h"
+#include "data/override/R.h"
+
+#include <gtest/gtest.h>
+
+using namespace android;
+
+namespace {
+
+#include "data/override/override_arsc.h"
+
+TEST(PackageIdOverrideTest, shouldOverridePackageId) {
+ const uint32_t pkgIdOverride = 0x42;
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(override_arsc, override_arsc_len, NULL, 0, -1, false,
+ pkgIdOverride));
+
+ Res_value val;
+ // we should not be able to retrieve the resource using the build time package id
+ uint32_t resId = override::R::string::string1;
+ ssize_t block = table.getResource(resId, &val, false);
+ ASSERT_LT(block, 0);
+
+ // now make sure we can access the resource using the runtime package id
+ resId = (override::R::string::string1 & 0x00ffffff) | (pkgIdOverride << 24);
+ block = table.getResource(resId, &val, false);
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_STRING, val.dataType);
+ const ResStringPool* pool = table.getTableStringBlock(block);
+ ASSERT_TRUE(pool != NULL);
+ ASSERT_LT(val.data, pool->size());
+
+ size_t strLen;
+ const char16_t* targetStr16 = pool->stringAt(val.data, &strLen);
+ ASSERT_TRUE(targetStr16 != NULL);
+ ASSERT_EQ(String16("string1"), String16(targetStr16, strLen));
+}
+
+}
diff --git a/libs/androidfw/tests/ThemesBags_test.cpp b/libs/androidfw/tests/ThemesBags_test.cpp
new file mode 100644
index 00000000000..83078577dba
--- /dev/null
+++ b/libs/androidfw/tests/ThemesBags_test.cpp
@@ -0,0 +1,169 @@
+/*
+ * 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 <utils/String16.h>
+#include "TestHelpers.h"
+#include "data/system/R.h"
+#include "data/app/R.h"
+#include "data/bags/R.h"
+
+#include <gtest/gtest.h>
+
+using namespace android;
+
+namespace {
+
+/**
+ * Include a binary resource table.
+ *
+ * Package: android
+ */
+#include "data/system/system_arsc.h"
+
+/**
+ * Include a binary resource table.
+ *
+ * Package: com.android.app
+ */
+#include "data/app/app_arsc.h"
+
+/**
+ * Include a binary resource table.
+ * This table is an overlay.
+ *
+ * Package: com.android.test.bags
+ */
+#include "data/bags/bags_arsc.h"
+
+enum { MAY_NOT_BE_BAG = false };
+
+class BagsTest : public ::testing::Test {
+protected:
+ virtual void SetUp() {
+ ASSERT_EQ(NO_ERROR, mTargetTable.add(app_arsc, app_arsc_len));
+ ASSERT_EQ(NO_ERROR, mOverlayTable.add(bags_arsc, bags_arsc_len));
+ char targetName[256] = "com.android.app";
+ ASSERT_EQ(NO_ERROR, mTargetTable.createIdmap(mOverlayTable, 0, 0, 0, 0,
+ targetName, targetName, &mData, &mDataSize));
+ ASSERT_EQ(NO_ERROR, mTargetTable.add(system_arsc, system_arsc_len));
+ }
+
+ virtual void TearDown() {
+ free(mData);
+ }
+
+ ResTable mTargetTable;
+ ResTable mOverlayTable;
+ void* mData;
+ size_t mDataSize;
+};
+
+TEST_F(BagsTest, canLoadIdmap) {
+ ASSERT_EQ(NO_ERROR, mTargetTable.add(bags_arsc, bags_arsc_len, mData, mDataSize));
+}
+
+TEST_F(BagsTest, overlayOverridesStyleAttribute) {
+ ASSERT_EQ(NO_ERROR, mTargetTable.add(bags_arsc, bags_arsc_len, mData, mDataSize));
+
+ ResTable::Theme theme2(mTargetTable);
+ ASSERT_EQ(NO_ERROR, theme2.applyStyle(app::R::style::Theme_Two));
+
+ Res_value val;
+ ASSERT_GE(theme2.getAttribute(android::R::attr::background, &val), 0);
+ ASSERT_EQ(Res_value::TYPE_INT_COLOR_RGB8, val.dataType);
+ ASSERT_EQ(uint32_t(0xff0000ff), val.data);
+}
+
+TEST_F(BagsTest, overlayCanResolveReferencesToOwnPackage) {
+ ASSERT_EQ(NO_ERROR, mTargetTable.add(bags_arsc, bags_arsc_len, mData, mDataSize));
+
+ ResTable::Theme theme2(mTargetTable);
+ ASSERT_EQ(NO_ERROR, theme2.applyStyle(app::R::style::Theme_Two));
+
+ Res_value attr;
+ ssize_t block = theme2.getAttribute(android::R::attr::foreground, &attr);
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_REFERENCE, attr.dataType);
+ ASSERT_EQ(uint32_t(bags::R::color::magenta), attr.data);
+ Res_value val;
+ block = mTargetTable.getResource(attr.data, &val, false);
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_INT_COLOR_RGB8, val.dataType);
+ ASSERT_EQ(uint32_t(0xffff00ff), val.data);
+}
+
+TEST_F(BagsTest, overlayCanReferenceOwnStyle) {
+ ASSERT_EQ(NO_ERROR, mTargetTable.add(bags_arsc, bags_arsc_len, mData, mDataSize));
+
+ ResTable::Theme theme3(mTargetTable);
+ ASSERT_EQ(NO_ERROR, theme3.applyStyle(app::R::style::Theme_Three));
+
+ Res_value attr;
+ ssize_t block = theme3.getAttribute(android::R::attr::foreground, &attr);
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_REFERENCE, attr.dataType);
+ ASSERT_EQ(uint32_t(bags::R::color::cyan), attr.data);
+ Res_value val;
+ block = mTargetTable.getResource(attr.data, &val, false);
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_INT_COLOR_RGB8, val.dataType);
+ ASSERT_EQ(uint32_t(0xff00ffff), val.data);
+
+ // verify that we still get the parent attribute for background
+ block = theme3.getAttribute(android::R::attr::background, &attr);
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_INT_COLOR_RGB8, attr.dataType);
+ ASSERT_EQ(uint32_t(0xffff0000), attr.data);
+}
+
+TEST_F(BagsTest, overlaidStyleContainsMissingAttributes) {
+ const uint32_t SOME_DIMEN_VALUE = 0x00003001;
+
+ ASSERT_EQ(NO_ERROR, mTargetTable.add(bags_arsc, bags_arsc_len, mData, mDataSize));
+
+ ResTable::Theme theme(mTargetTable);
+ ASSERT_EQ(NO_ERROR, theme.applyStyle(app::R::style::Theme_Four));
+
+ // First let's make sure we have the themed style by checking the background attribute
+ Res_value attr;
+ ssize_t block = theme.getAttribute(android::R::attr::background, &attr);
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_INT_COLOR_RGB8, attr.dataType);
+ ASSERT_EQ(uint32_t(0xffaabbcc), attr.data);
+
+ // Now check if the someDimen attribute in the parent was merged in correctly since the theme
+ // does not contain this attribute in the overlaid style
+ block = theme.getAttribute(android::R::attr::some_dimen, &attr);
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_DIMENSION, attr.dataType);
+ ASSERT_EQ(SOME_DIMEN_VALUE, attr.data);
+}
+
+TEST_F(BagsTest, protectedAttributeNotOverlaid) {
+ ASSERT_EQ(NO_ERROR, mTargetTable.add(bags_arsc, bags_arsc_len, mData, mDataSize));
+
+ ResTable::Theme theme(mTargetTable);
+ ASSERT_EQ(NO_ERROR, theme.applyStyle(app::R::style::Theme_Two));
+ Res_value val;
+ ASSERT_GE(theme.getAttribute(android::R::attr::windowNoTitle, &val), 0);
+ ASSERT_EQ(Res_value::TYPE_INT_BOOLEAN, val.dataType);
+ ASSERT_NE(0, val.data);
+}
+
+}
diff --git a/libs/androidfw/tests/ThemesIdmap_test.cpp b/libs/androidfw/tests/ThemesIdmap_test.cpp
new file mode 100644
index 00000000000..57ed9b87b1d
--- /dev/null
+++ b/libs/androidfw/tests/ThemesIdmap_test.cpp
@@ -0,0 +1,133 @@
+/*
+ * 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 <utils/String16.h>
+#include "TestHelpers.h"
+#include "data/basic/R.h"
+
+#include <gtest/gtest.h>
+
+using namespace android;
+
+namespace {
+
+/**
+ * Include a binary resource table.
+ *
+ * Package: com.android.test.basic
+ */
+#include "data/basic/basic_arsc.h"
+
+/**
+ * Include a binary resource table.
+ * This table is an overlay.
+ *
+ * Package: com.android.test.basic
+ */
+#include "data/overlay/overlay_arsc.h"
+
+enum { MAY_NOT_BE_BAG = false };
+
+class IdmapTest : public ::testing::Test {
+protected:
+ virtual void SetUp() {
+ ASSERT_EQ(NO_ERROR, mTargetTable.add(basic_arsc, basic_arsc_len));
+ ASSERT_EQ(NO_ERROR, mOverlayTable.add(overlay_arsc, overlay_arsc_len));
+ char targetName[256] = "com.android.test.basic";
+ ASSERT_EQ(NO_ERROR, mTargetTable.createIdmap(mOverlayTable, 0, 0, 0, 0,
+ targetName, targetName, &mData, &mDataSize));
+ }
+
+ virtual void TearDown() {
+ free(mData);
+ }
+
+ ResTable mTargetTable;
+ ResTable mOverlayTable;
+ void* mData;
+ size_t mDataSize;
+};
+
+TEST_F(IdmapTest, canLoadIdmap) {
+ ASSERT_EQ(NO_ERROR, mTargetTable.add(overlay_arsc, overlay_arsc_len, mData, mDataSize));
+}
+
+TEST_F(IdmapTest, overlayOverridesResourceValue) {
+ Res_value val;
+ ssize_t block = mTargetTable.getResource(base::R::dimen::dimen1, &val, false);
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_DIMENSION, val.dataType);
+ ASSERT_NE(val.data, 0);
+
+ ASSERT_EQ(NO_ERROR, mTargetTable.add(overlay_arsc, overlay_arsc_len, mData, mDataSize));
+
+ Res_value newVal;
+ ssize_t newBlock = mTargetTable.getResource(base::R::dimen::dimen1, &newVal, false);
+ ASSERT_GE(newBlock, 0);
+ ASSERT_NE(block, newBlock);
+ ASSERT_EQ(Res_value::TYPE_DIMENSION, newVal.dataType);
+ ASSERT_NE(val.data, newVal.data);
+}
+
+TEST_F(IdmapTest, overlaidResourceHasSameName) {
+ ASSERT_EQ(NO_ERROR, mTargetTable.add(overlay_arsc, overlay_arsc_len, mData, mDataSize));
+
+ ResTable::resource_name resName;
+ ASSERT_TRUE(mTargetTable.getResourceName(base::R::drawable::drawable1, false, &resName));
+
+ ASSERT_TRUE(resName.package != NULL);
+ ASSERT_TRUE(resName.type != NULL);
+ ASSERT_TRUE(resName.name != NULL);
+
+ EXPECT_EQ(String16("com.android.test.basic"), String16(resName.package, resName.packageLen));
+ EXPECT_EQ(String16("drawable"), String16(resName.type, resName.typeLen));
+ EXPECT_EQ(String16("drawable1"), String16(resName.name, resName.nameLen));
+}
+
+TEST_F(IdmapTest, overlayDoesNotOverlayStringResource) {
+ Res_value val;
+ ssize_t block = mTargetTable.getResource(base::R::string::test2, &val, false);
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_STRING, val.dataType);
+ const ResStringPool* pool = mTargetTable.getTableStringBlock(block);
+ ASSERT_TRUE(pool != NULL);
+ ASSERT_LT(val.data, pool->size());
+
+ size_t strLen;
+ const char16_t* targetStr16 = pool->stringAt(val.data, &strLen);
+ ASSERT_TRUE(targetStr16 != NULL);
+ ASSERT_EQ(String16("test2"), String16(targetStr16, strLen));
+
+ ASSERT_EQ(NO_ERROR, mTargetTable.add(overlay_arsc, overlay_arsc_len, mData, mDataSize));
+
+ ssize_t newBlock = mTargetTable.getResource(base::R::string::test2, &val, false);
+ ASSERT_GE(newBlock, 0);
+ ASSERT_EQ(block, newBlock);
+ // the above check should be enough but just to be sure we'll check the string
+ ASSERT_EQ(Res_value::TYPE_STRING, val.dataType);
+ pool = mTargetTable.getTableStringBlock(newBlock);
+ ASSERT_TRUE(pool != NULL);
+ ASSERT_LT(val.data, pool->size());
+
+ targetStr16 = pool->stringAt(val.data, &strLen);
+ ASSERT_TRUE(targetStr16 != NULL);
+ ASSERT_EQ(String16("test2"), String16(targetStr16, strLen));
+}
+
+} // namespace
diff --git a/libs/androidfw/tests/data/app/R.h b/libs/androidfw/tests/data/app/R.h
index 780a11610ef..fd917281da7 100644
--- a/libs/androidfw/tests/data/app/R.h
+++ b/libs/androidfw/tests/data/app/R.h
@@ -13,6 +13,15 @@ namespace attr {
namespace style {
enum {
Theme_One = 0x7f020000, // default
+ Theme_Two = 0x7f020001, // default
+ Theme_Three = 0x7f020002, // default
+ Theme_Four = 0x7f020003, // default
+ };
+}
+
+namespace color {
+ enum {
+ app_color = 0x7f030000, // default
};
}
diff --git a/libs/androidfw/tests/data/app/app_arsc.h b/libs/androidfw/tests/data/app/app_arsc.h
index d5d9a3b8be5..be8a79afd77 100644
--- a/libs/androidfw/tests/data/app/app_arsc.h
+++ b/libs/androidfw/tests/data/app/app_arsc.h
@@ -1,8 +1,8 @@
unsigned char app_arsc[] = {
- 0x02, 0x00, 0x0c, 0x00, 0xc4, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x0c, 0x00, 0x18, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x01, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0x9c, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xf0, 0x03, 0x00, 0x00,
0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00,
0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00,
0x64, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x70, 0x00, 0x70, 0x00, 0x00, 0x00,
@@ -25,38 +25,67 @@ unsigned char app_arsc[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00,
- 0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x73, 0x00, 0x74, 0x00, 0x79, 0x00,
- 0x6c, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00,
- 0x4c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x06, 0x00, 0x6e, 0x00,
- 0x75, 0x00, 0x6d, 0x00, 0x62, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x54, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00,
- 0x2e, 0x00, 0x4f, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
- 0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x00,
- 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
- 0x64, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0x7f, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00
+ 0x03, 0x00, 0x00, 0x00, 0x70, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x50, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x73, 0x00,
+ 0x74, 0x00, 0x79, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x00, 0x00, 0x05, 0x00,
+ 0x63, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x1c, 0x00, 0xb8, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x26, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00,
+ 0x6e, 0x00, 0x00, 0x00, 0x06, 0x00, 0x6e, 0x00, 0x75, 0x00, 0x6d, 0x00,
+ 0x62, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x09, 0x00, 0x54, 0x00,
+ 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x2e, 0x00, 0x4f, 0x00,
+ 0x6e, 0x00, 0x65, 0x00, 0x00, 0x00, 0x09, 0x00, 0x54, 0x00, 0x68, 0x00,
+ 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x2e, 0x00, 0x54, 0x00, 0x77, 0x00,
+ 0x6f, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x54, 0x00, 0x68, 0x00, 0x65, 0x00,
+ 0x6d, 0x00, 0x65, 0x00, 0x2e, 0x00, 0x54, 0x00, 0x68, 0x00, 0x72, 0x00,
+ 0x65, 0x00, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x54, 0x00, 0x68, 0x00,
+ 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x2e, 0x00, 0x46, 0x00, 0x6f, 0x00,
+ 0x75, 0x00, 0x72, 0x00, 0x00, 0x00, 0x09, 0x00, 0x61, 0x00, 0x70, 0x00,
+ 0x70, 0x00, 0x5f, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00,
+ 0x72, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x02, 0x44, 0x00, 0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x04, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x02, 0x44, 0x00, 0xc4, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x1c, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7f, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7f,
+ 0x08, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x7f, 0x08, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x01, 0x08, 0x00, 0x00, 0x05,
+ 0x01, 0x30, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x1d,
+ 0xff, 0xff, 0xff, 0xff
};
-unsigned int app_arsc_len = 708;
+unsigned int app_arsc_len = 1048;
diff --git a/libs/androidfw/tests/data/app/res/values/values.xml b/libs/androidfw/tests/data/app/res/values/values.xml
index b0ead387b0a..6f11070b6d6 100644
--- a/libs/androidfw/tests/data/app/res/values/values.xml
+++ b/libs/androidfw/tests/data/app/res/values/values.xml
@@ -1,7 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="number" format="integer"/>
+
<style name="Theme.One" parent="@android:style/Theme.One">
<item name="number">1</item>
</style>
+
+ <style name="Theme.Two" parent="@android:style/Theme.One">
+ <item name="number">2</item>
+ </style>
+
+ <style name="Theme.Three" parent="@android:style/Theme.One">
+ <item name="number">3</item>
+ </style>
+
+ <style name="Theme.Four" parent="@android:style/Theme.One">
+ <item name="android:someDimen">48dp</item>
+ </style>
+
+ <color name="app_color">#ffffff</color>
</resources>
diff --git a/libs/androidfw/tests/data/bags/AndroidManifest.xml b/libs/androidfw/tests/data/bags/AndroidManifest.xml
new file mode 100644
index 00000000000..69cf30a9b2c
--- /dev/null
+++ b/libs/androidfw/tests/data/bags/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test.bags">
+ <application>
+ </application>
+</manifest>
diff --git a/libs/androidfw/tests/data/bags/R.h b/libs/androidfw/tests/data/bags/R.h
new file mode 100644
index 00000000000..bdb92543f57
--- /dev/null
+++ b/libs/androidfw/tests/data/bags/R.h
@@ -0,0 +1,26 @@
+#ifndef __BAGS_R_H
+#define __BAGS_R_H
+
+namespace bags {
+namespace R {
+
+namespace style {
+ enum {
+ Theme_Two = 0x61020000, // default
+ Theme_Three = 0x61020001, // default
+ Overlay = 0x61020002, // default
+ };
+}
+
+namespace color {
+ enum {
+ app_color = 0x61030000, // default
+ magenta = 0x61030001, // default
+ cyan = 0x61030002, // default
+ };
+}
+
+} // namespace R
+} // namespace bags
+
+#endif // __BAGS_R_H
diff --git a/libs/androidfw/tests/data/bags/bags_arsc.h b/libs/androidfw/tests/data/bags/bags_arsc.h
new file mode 100644
index 00000000000..324cd9ce84e
--- /dev/null
+++ b/libs/androidfw/tests/data/bags/bags_arsc.h
@@ -0,0 +1,88 @@
+unsigned char bags_arsc[] = {
+ 0x02, 0x00, 0x0c, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xd4, 0x03, 0x00, 0x00,
+ 0x61, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00,
+ 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00,
+ 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
+ 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x67, 0x00, 0x73, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x70, 0x01, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x50, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x73, 0x00,
+ 0x74, 0x00, 0x79, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x00, 0x00, 0x05, 0x00,
+ 0x63, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x1c, 0x00, 0xc8, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x00,
+ 0x70, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00, 0x09, 0x00, 0x54, 0x00,
+ 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x2e, 0x00, 0x54, 0x00,
+ 0x77, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x54, 0x00, 0x68, 0x00,
+ 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x2e, 0x00, 0x54, 0x00, 0x68, 0x00,
+ 0x72, 0x00, 0x65, 0x00, 0x65, 0x00, 0x00, 0x00, 0x07, 0x00, 0x4f, 0x00,
+ 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00,
+ 0x00, 0x00, 0x0a, 0x00, 0x54, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00,
+ 0x65, 0x00, 0x2e, 0x00, 0x46, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00,
+ 0x00, 0x00, 0x09, 0x00, 0x61, 0x00, 0x70, 0x00, 0x70, 0x00, 0x5f, 0x00,
+ 0x63, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x6e, 0x00,
+ 0x74, 0x00, 0x61, 0x00, 0x00, 0x00, 0x04, 0x00, 0x63, 0x00, 0x79, 0x00,
+ 0x61, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x20, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
+ 0xd0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x54, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
+ 0x44, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x03, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x01, 0x08, 0x00, 0x00, 0x1d, 0xff, 0x00, 0x00, 0xff,
+ 0x01, 0x00, 0x01, 0x01, 0x08, 0x00, 0x00, 0x01, 0x01, 0x00, 0x03, 0x61,
+ 0x56, 0x00, 0x01, 0x01, 0x08, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x61,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01,
+ 0x08, 0x00, 0x00, 0x01, 0x02, 0x00, 0x03, 0x61, 0x10, 0x00, 0x01, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x01, 0x08, 0x00, 0x00, 0x1d, 0xcc, 0xbb, 0xaa, 0xff,
+ 0x02, 0x02, 0x10, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x1d,
+ 0x00, 0x00, 0x00, 0xff, 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x1d, 0xff, 0x00, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x1d, 0xff, 0xff, 0x00, 0xff
+};
+unsigned int bags_arsc_len = 1020;
diff --git a/libs/androidfw/tests/data/bags/build b/libs/androidfw/tests/data/bags/build
new file mode 100755
index 00000000000..9047b185f2e
--- /dev/null
+++ b/libs/androidfw/tests/data/bags/build
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+aapt package -M AndroidManifest.xml -I ../system/bundle.apk -S res -x 97 -F bundle.apk -f && \
+unzip bundle.apk resources.arsc && \
+mv resources.arsc bags.arsc && \
+xxd -i bags.arsc > bags_arsc.h
diff --git a/libs/androidfw/tests/data/bags/res/values/values.xml b/libs/androidfw/tests/data/bags/res/values/values.xml
new file mode 100644
index 00000000000..32bcf74d78a
--- /dev/null
+++ b/libs/androidfw/tests/data/bags/res/values/values.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <style name="Theme.Two" parent="@android:style/Theme.One">
+ <item name="android:background">#0000ff</item>
+ <item name="android:foreground">@color/magenta</item>
+ <item name="android:windowNoTitle">false</item>
+ </style>
+
+ <style name="Theme.Three" parent="@style/Overlay"/>
+
+ <style name="Overlay" parent="@android:style/Theme.One">
+ <item name="android:foreground">@color/cyan</item>
+ </style>
+
+ <style name="Theme.Four" parent="@android:style/Theme.One">
+ <item name="android:background">#aabbcc</item>
+ </style>
+
+ <color name="app_color">#000000</color>
+ <color name="magenta">#ff00ff</color>
+ <color name="cyan">#00ffff</color>
+
+</resources>
diff --git a/libs/androidfw/tests/data/basic/R.h b/libs/androidfw/tests/data/basic/R.h
index 363dcb9e170..36bb1dee4d1 100644
--- a/libs/androidfw/tests/data/basic/R.h
+++ b/libs/androidfw/tests/data/basic/R.h
@@ -11,41 +11,54 @@ namespace attr {
};
}
+namespace drawable {
+ enum {
+ drawable1 = 0x7f020000, // default
+ };
+}
+
namespace layout {
enum {
- main = 0x7f020000, // default, fr-sw600dp-v13
+ main = 0x7f030000, // default, fr-sw600dp-v13
};
}
namespace string {
enum {
- test1 = 0x7f030000, // default
- test2 = 0x7f030001, // default
+ test1 = 0x7f040000, // default
+ test2 = 0x7f040001, // default
- test3 = 0x7f070000, // default (in feature)
- test4 = 0x7f070001, // default (in feature)
+ test3 = 0x7f0a0000, // default (in feature)
+ test4 = 0x7f0a0001, // default (in feature)
};
}
namespace integer {
enum {
- number1 = 0x7f040000, // default, sv
- number2 = 0x7f040001, // default
+ number1 = 0x7f050000, // default, sv
+ number2 = 0x7f050001, // default
- test3 = 0x7f080000, // default (in feature)
+ test3 = 0x7f0b0000, // default (in feature)
};
}
namespace style {
enum {
- Theme1 = 0x7f050000, // default
- Theme2 = 0x7f050001, // default
+ Theme1 = 0x7f060000, // default
+ Theme2 = 0x7f060001, // default
};
}
namespace array {
enum {
- integerArray1 = 0x7f060000, // default
+ integerArray1 = 0x7f070000, // default
+ };
+}
+
+namespace dimen {
+ enum {
+ dimen1 = 0x7f080000, // default
+ dimen2 = 0x7f080001, // default
};
}
diff --git a/libs/androidfw/tests/data/basic/basic_arsc.h b/libs/androidfw/tests/data/basic/basic_arsc.h
index 61cb94c6582..370c77c64ef 100644
--- a/libs/androidfw/tests/data/basic/basic_arsc.h
+++ b/libs/androidfw/tests/data/basic/basic_arsc.h
@@ -1,9 +1,14 @@
unsigned char basic_arsc[] = {
- 0x02, 0x00, 0x0c, 0x00, 0x60, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x1c, 0x00, 0xbc, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00,
- 0x72, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x13, 0x00, 0x72, 0x00,
+ 0x02, 0x00, 0x0c, 0x00, 0xf4, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x1c, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
+ 0x62, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00,
+ 0x1a, 0x00, 0x72, 0x00, 0x65, 0x00, 0x73, 0x00, 0x2f, 0x00, 0x64, 0x00,
+ 0x72, 0x00, 0x61, 0x00, 0x77, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00,
+ 0x65, 0x00, 0x2f, 0x00, 0x64, 0x00, 0x72, 0x00, 0x61, 0x00, 0x77, 0x00,
+ 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x31, 0x00, 0x2e, 0x00,
+ 0x70, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x00, 0x00, 0x13, 0x00, 0x72, 0x00,
0x65, 0x00, 0x73, 0x00, 0x2f, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00,
0x6f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x2f, 0x00, 0x6d, 0x00, 0x61, 0x00,
0x69, 0x00, 0x6e, 0x00, 0x2e, 0x00, 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00,
@@ -16,7 +21,7 @@ unsigned char basic_arsc[] = {
0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
0x31, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00,
0x74, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01,
- 0x98, 0x06, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00,
+ 0xf0, 0x07, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00,
0x6d, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00,
0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00,
0x73, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00,
@@ -38,124 +43,152 @@ unsigned char basic_arsc[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x20, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xb0, 0x01, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00,
- 0x90, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
- 0x2c, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00,
- 0x74, 0x00, 0x00, 0x00, 0x06, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00,
- 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x00, 0x00, 0x07, 0x00, 0x69, 0x00,
- 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00,
- 0x00, 0x00, 0x05, 0x00, 0x73, 0x00, 0x74, 0x00, 0x79, 0x00, 0x6c, 0x00,
- 0x65, 0x00, 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, 0x72, 0x00, 0x72, 0x00,
- 0x61, 0x00, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00,
- 0xec, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
- 0x28, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00,
- 0x56, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00,
- 0x88, 0x00, 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00,
- 0x72, 0x00, 0x31, 0x00, 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, 0x74, 0x00,
- 0x74, 0x00, 0x72, 0x00, 0x32, 0x00, 0x00, 0x00, 0x04, 0x00, 0x6d, 0x00,
- 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00,
- 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x31, 0x00, 0x00, 0x00, 0x05, 0x00,
- 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x32, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x6e, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x62, 0x00, 0x65, 0x00,
- 0x72, 0x00, 0x31, 0x00, 0x00, 0x00, 0x07, 0x00, 0x6e, 0x00, 0x75, 0x00,
- 0x6d, 0x00, 0x62, 0x00, 0x65, 0x00, 0x72, 0x00, 0x32, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x54, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00,
- 0x31, 0x00, 0x00, 0x00, 0x06, 0x00, 0x54, 0x00, 0x68, 0x00, 0x65, 0x00,
- 0x6d, 0x00, 0x65, 0x00, 0x32, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x69, 0x00,
- 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00,
- 0x41, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00, 0x31, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x84, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
- 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x01, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xd8, 0x01, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00,
+ 0xb8, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00,
+ 0x60, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00, 0x08, 0x00, 0x64, 0x00,
+ 0x72, 0x00, 0x61, 0x00, 0x77, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00,
+ 0x65, 0x00, 0x00, 0x00, 0x06, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00,
+ 0x6f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x00, 0x00, 0x06, 0x00, 0x73, 0x00,
+ 0x74, 0x00, 0x72, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00,
+ 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x73, 0x00, 0x74, 0x00,
+ 0x79, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x00, 0x00, 0x05, 0x00, 0x61, 0x00,
+ 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00, 0x00, 0x00, 0x05, 0x00,
+ 0x64, 0x00, 0x69, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x1c, 0x00, 0x2c, 0x01, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x1c, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00,
+ 0x4c, 0x00, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00,
+ 0x7e, 0x00, 0x00, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x9e, 0x00, 0x00, 0x00,
+ 0xbc, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x05, 0x00, 0x61, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x31, 0x00, 0x00, 0x00, 0x05, 0x00,
+ 0x61, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x32, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x64, 0x00, 0x72, 0x00, 0x61, 0x00, 0x77, 0x00, 0x61, 0x00,
+ 0x62, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x31, 0x00, 0x00, 0x00, 0x04, 0x00,
+ 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x05, 0x00,
+ 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x31, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x32, 0x00,
+ 0x00, 0x00, 0x07, 0x00, 0x6e, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x62, 0x00,
+ 0x65, 0x00, 0x72, 0x00, 0x31, 0x00, 0x00, 0x00, 0x07, 0x00, 0x6e, 0x00,
+ 0x75, 0x00, 0x6d, 0x00, 0x62, 0x00, 0x65, 0x00, 0x72, 0x00, 0x32, 0x00,
+ 0x00, 0x00, 0x06, 0x00, 0x54, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00,
+ 0x65, 0x00, 0x31, 0x00, 0x00, 0x00, 0x06, 0x00, 0x54, 0x00, 0x68, 0x00,
+ 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x32, 0x00, 0x00, 0x00, 0x0d, 0x00,
+ 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00,
+ 0x72, 0x00, 0x41, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00,
+ 0x31, 0x00, 0x00, 0x00, 0x06, 0x00, 0x64, 0x00, 0x69, 0x00, 0x6d, 0x00,
+ 0x65, 0x00, 0x6e, 0x00, 0x31, 0x00, 0x00, 0x00, 0x06, 0x00, 0x64, 0x00,
+ 0x69, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x32, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x02, 0x44, 0x00, 0x84, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x1c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x05, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x05, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
- 0x05, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x24, 0x00, 0x00,
- 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
+ 0x58, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x04, 0x24, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
+ 0x58, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x66, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x58, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
+ 0x02, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x6c, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00,
- 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x72, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
+ 0x04, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x6c, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x02, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
- 0x18, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
- 0x6c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10, 0xc8, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x07, 0x7f, 0x01, 0x02, 0x44, 0x00, 0x5c, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x76, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
- 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
- 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
- 0x6c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10, 0x90, 0x01, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x02, 0x44, 0x00, 0x90, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10,
- 0xc8, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x7f, 0x01, 0x02, 0x44, 0x00,
- 0x5c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x73, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
- 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10,
- 0x90, 0x01, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x90, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
- 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x28, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7f,
+ 0x08, 0x00, 0x00, 0x10, 0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x7f,
+ 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x05, 0x7f, 0x10, 0x00, 0x01, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x7f, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x7f, 0x08, 0x00, 0x00, 0x10, 0x2c, 0x01, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
+ 0x7c, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0x7f, 0x08, 0x00, 0x00, 0x10, 0x64, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x01, 0x7f, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x04, 0x7f,
- 0x10, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x7f,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7f, 0x08, 0x00, 0x00, 0x10,
- 0x2c, 0x01, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x02, 0x44, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x02, 0x44, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x01, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
- 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
- 0x03, 0x00, 0x00, 0x00
+ 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x05, 0x01, 0x30, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x05, 0x00, 0x10, 0x00, 0x00
};
-unsigned int basic_arsc_len = 1888;
+unsigned int basic_arsc_len = 2292;
diff --git a/libs/androidfw/tests/data/basic/res/drawable/drawable1.png b/libs/androidfw/tests/data/basic/res/drawable/drawable1.png
new file mode 100644
index 00000000000..94660d3198f
--- /dev/null
+++ b/libs/androidfw/tests/data/basic/res/drawable/drawable1.png
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/res/values/values.xml b/libs/androidfw/tests/data/basic/res/values/values.xml
index 662eda6a4ed..0d22a4c8971 100644
--- a/libs/androidfw/tests/data/basic/res/values/values.xml
+++ b/libs/androidfw/tests/data/basic/res/values/values.xml
@@ -23,4 +23,7 @@
<item>2</item>
<item>3</item>
</integer-array>
+
+ <dimen name="dimen1">48dp</dimen>
+ <dimen name="dimen2">16px</dimen>
</resources>
diff --git a/libs/androidfw/tests/data/basic/split_de_fr_arsc.h b/libs/androidfw/tests/data/basic/split_de_fr_arsc.h
index a8eaf0b2f69..23cb3b7c337 100644
--- a/libs/androidfw/tests/data/basic/split_de_fr_arsc.h
+++ b/libs/androidfw/tests/data/basic/split_de_fr_arsc.h
@@ -1,5 +1,5 @@
unsigned char split_de_fr_arsc[] = {
- 0x02, 0x00, 0x0c, 0x00, 0xd8, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x0c, 0x00, 0x2c, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x01, 0x00, 0x1c, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
@@ -10,7 +10,7 @@ unsigned char split_de_fr_arsc[] = {
0x32, 0x00, 0x00, 0x00, 0x07, 0x00, 0x65, 0x00, 0x73, 0x00, 0x73, 0x00,
0x61, 0x00, 0x69, 0x00, 0x20, 0x00, 0x31, 0x00, 0x00, 0x00, 0x07, 0x00,
0x65, 0x00, 0x73, 0x00, 0x73, 0x00, 0x61, 0x00, 0x69, 0x00, 0x20, 0x00,
- 0x32, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0x50, 0x03, 0x00, 0x00,
+ 0x32, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xa4, 0x03, 0x00, 0x00,
0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00,
0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00,
0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
@@ -33,32 +33,37 @@ unsigned char split_de_fr_arsc[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0xb0, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x90, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
- 0x3e, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00,
- 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00, 0x06, 0x00, 0x6c, 0x00,
- 0x61, 0x00, 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00, 0x69, 0x00, 0x6e, 0x00,
- 0x67, 0x00, 0x00, 0x00, 0x07, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00,
- 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x05, 0x00,
- 0x73, 0x00, 0x74, 0x00, 0x79, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x61, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0xd8, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0xb8, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
+ 0x6e, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x72, 0x00, 0x00, 0x00, 0x08, 0x00, 0x64, 0x00, 0x72, 0x00, 0x61, 0x00,
+ 0x77, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00,
+ 0x74, 0x00, 0x00, 0x00, 0x06, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00,
+ 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x00, 0x00, 0x07, 0x00, 0x69, 0x00,
+ 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00,
+ 0x00, 0x00, 0x05, 0x00, 0x73, 0x00, 0x74, 0x00, 0x79, 0x00, 0x6c, 0x00,
+ 0x65, 0x00, 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, 0x72, 0x00, 0x72, 0x00,
+ 0x61, 0x00, 0x79, 0x00, 0x00, 0x00, 0x05, 0x00, 0x64, 0x00, 0x69, 0x00,
+ 0x6d, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00,
+ 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x31, 0x00, 0x00, 0x00, 0x05, 0x00,
+ 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x32, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x0e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00,
- 0x74, 0x00, 0x31, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00,
- 0x73, 0x00, 0x74, 0x00, 0x32, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
- 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x6c, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x65, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -67,7 +72,7 @@ unsigned char split_de_fr_arsc[] = {
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
0x01, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x6c, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x72, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -76,10 +81,12 @@ unsigned char split_de_fr_arsc[] = {
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
0x03, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
-unsigned int split_de_fr_arsc_len = 984;
+unsigned int split_de_fr_arsc_len = 1068;
diff --git a/libs/androidfw/tests/data/feature/feature_arsc.h b/libs/androidfw/tests/data/feature/feature_arsc.h
index cf7647d71b0..0efaa15aa2f 100644
--- a/libs/androidfw/tests/data/feature/feature_arsc.h
+++ b/libs/androidfw/tests/data/feature/feature_arsc.h
@@ -1,11 +1,11 @@
unsigned char feature_arsc[] = {
- 0x02, 0x00, 0x0c, 0x00, 0x40, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x0c, 0x00, 0x4c, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x01, 0x00, 0x1c, 0x00, 0x40, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x33, 0x00,
0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
- 0x34, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xf4, 0x02, 0x00, 0x00,
+ 0x34, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0x00, 0x03, 0x00, 0x00,
0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00,
0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00,
0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
@@ -28,10 +28,11 @@ unsigned char feature_arsc[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x9c, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x7c, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0xa8, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x88, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
0x2e, 0x00, 0x00, 0x00, 0x07, 0x00, 0x3c, 0x00, 0x65, 0x00, 0x6d, 0x00,
@@ -47,11 +48,11 @@ unsigned char feature_arsc[] = {
0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
0x34, 0x00, 0x00, 0x00, 0x07, 0x00, 0x6e, 0x00, 0x75, 0x00, 0x6d, 0x00,
0x62, 0x00, 0x65, 0x00, 0x72, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x02, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x6c, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -60,8 +61,8 @@ unsigned char feature_arsc[] = {
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
0x01, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -70,4 +71,4 @@ unsigned char feature_arsc[] = {
0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10,
0xc8, 0x00, 0x00, 0x00
};
-unsigned int feature_arsc_len = 832;
+unsigned int feature_arsc_len = 844;
diff --git a/libs/androidfw/tests/data/lib/lib_arsc.h b/libs/androidfw/tests/data/lib/lib_arsc.h
index d670c5be18a..dd3dad5d5f3 100644
--- a/libs/androidfw/tests/data/lib/lib_arsc.h
+++ b/libs/androidfw/tests/data/lib/lib_arsc.h
@@ -1,8 +1,8 @@
unsigned char lib_arsc[] = {
- 0x02, 0x00, 0x0c, 0x00, 0xc8, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x0c, 0x00, 0xb8, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x01, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xa0, 0x03, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0x90, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00,
0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00,
0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
@@ -37,48 +37,25 @@ unsigned char lib_arsc[] = {
0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x61, 0x00,
0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x31, 0x00, 0x00, 0x00, 0x05, 0x00,
0x54, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x00, 0x00,
- 0x03, 0x02, 0x0c, 0x00, 0x10, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00,
- 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00,
- 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
- 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00, 0x69, 0x00, 0x63, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x64, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00,
- 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
+ 0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
- 0x08, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x64, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00,
- 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
+ 0x64, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
- 0x08, 0x00, 0x00, 0x10, 0xbc, 0x02, 0x00, 0x00
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x10, 0xbc, 0x02, 0x00, 0x00
};
-unsigned int lib_arsc_len = 968;
+unsigned int lib_arsc_len = 696;
diff --git a/libs/androidfw/tests/data/overlay/overlay_arsc.h b/libs/androidfw/tests/data/overlay/overlay_arsc.h
index 5bd98b28409..2d594172ff4 100644
--- a/libs/androidfw/tests/data/overlay/overlay_arsc.h
+++ b/libs/androidfw/tests/data/overlay/overlay_arsc.h
@@ -1,17 +1,22 @@
unsigned char overlay_arsc[] = {
- 0x02, 0x00, 0x0c, 0x00, 0x10, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x1c, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x74, 0x00,
+ 0x02, 0x00, 0x0c, 0x00, 0xa8, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x1c, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
+ 0x1a, 0x00, 0x72, 0x00, 0x65, 0x00, 0x73, 0x00, 0x2f, 0x00, 0x64, 0x00,
+ 0x72, 0x00, 0x61, 0x00, 0x77, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00,
+ 0x65, 0x00, 0x2f, 0x00, 0x64, 0x00, 0x72, 0x00, 0x61, 0x00, 0x77, 0x00,
+ 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x31, 0x00, 0x2e, 0x00,
+ 0x70, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x74, 0x00,
0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x32, 0x00, 0x2d, 0x00, 0x6f, 0x00,
0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xc4, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0x20, 0x04, 0x00, 0x00,
0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00,
0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00,
0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00, 0x69, 0x00, 0x63, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x2e, 0x00, 0x6f, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6c, 0x00,
+ 0x61, 0x00, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -28,21 +33,30 @@ unsigned char overlay_arsc[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x74, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x54, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00,
- 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00, 0x06, 0x00, 0x73, 0x00,
- 0x74, 0x00, 0x72, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x61, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x50, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x0e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00,
- 0x74, 0x00, 0x32, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x69, 0x00, 0x6e, 0x00,
- 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00, 0x41, 0x00,
- 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00, 0x31, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x9c, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x7c, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x3e, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x72, 0x00, 0x00, 0x00, 0x08, 0x00, 0x64, 0x00, 0x72, 0x00, 0x61, 0x00,
+ 0x77, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00, 0x69, 0x00, 0x6e, 0x00,
+ 0x67, 0x00, 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, 0x72, 0x00, 0x72, 0x00,
+ 0x61, 0x00, 0x79, 0x00, 0x00, 0x00, 0x05, 0x00, 0x64, 0x00, 0x69, 0x00,
+ 0x6d, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00,
+ 0x94, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
+ 0x42, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x09, 0x00, 0x64, 0x00,
+ 0x72, 0x00, 0x61, 0x00, 0x77, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00,
+ 0x65, 0x00, 0x31, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00,
+ 0x73, 0x00, 0x74, 0x00, 0x32, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x69, 0x00,
+ 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00,
+ 0x41, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00, 0x31, 0x00,
+ 0x00, 0x00, 0x06, 0x00, 0x64, 0x00, 0x69, 0x00, 0x6d, 0x00, 0x65, 0x00,
+ 0x6e, 0x00, 0x31, 0x00, 0x00, 0x00, 0x06, 0x00, 0x64, 0x00, 0x69, 0x00,
+ 0x6d, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x02, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -55,15 +69,35 @@ unsigned char overlay_arsc[] = {
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x02, 0x44, 0x00, 0x70, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x02, 0x44, 0x00, 0x70, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
- 0x0b, 0x00, 0x00, 0x00
+ 0x0b, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x6c, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x05, 0x02, 0x38, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x05,
+ 0x01, 0x0c, 0x00, 0x00
};
-unsigned int overlay_arsc_len = 784;
+unsigned int overlay_arsc_len = 1192;
diff --git a/libs/androidfw/tests/data/overlay/res/drawable/drawable1.png b/libs/androidfw/tests/data/overlay/res/drawable/drawable1.png
new file mode 100644
index 00000000000..a7045ed3777
--- /dev/null
+++ b/libs/androidfw/tests/data/overlay/res/drawable/drawable1.png
Binary files differ
diff --git a/libs/androidfw/tests/data/overlay/res/values/values.xml b/libs/androidfw/tests/data/overlay/res/values/values.xml
index 227e88973cc..5bf0427f401 100644
--- a/libs/androidfw/tests/data/overlay/res/values/values.xml
+++ b/libs/androidfw/tests/data/overlay/res/values/values.xml
@@ -5,4 +5,8 @@
<item>10</item>
<item>11</item>
</integer-array>
+
+ <dimen name="dimen1">56sp</dimen>
+ <dimen name="dimen2">12dp</dimen>
+
</resources>
diff --git a/libs/androidfw/tests/data/override/AndroidManifest.xml b/libs/androidfw/tests/data/override/AndroidManifest.xml
new file mode 100644
index 00000000000..ed260546c44
--- /dev/null
+++ b/libs/androidfw/tests/data/override/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test.override">
+ <application>
+ </application>
+</manifest>
diff --git a/libs/androidfw/tests/data/override/R.h b/libs/androidfw/tests/data/override/R.h
new file mode 100644
index 00000000000..a57c6b17b58
--- /dev/null
+++ b/libs/androidfw/tests/data/override/R.h
@@ -0,0 +1,16 @@
+#ifndef __BASE_R_H
+#define __BASE_R_H
+
+namespace override {
+namespace R {
+
+namespace string {
+ enum {
+ string1 = 0x7f020000, // default
+ };
+}
+
+} // namespace R
+} // namespace base
+
+#endif // __BASE_R_H
diff --git a/libs/androidfw/tests/data/override/build b/libs/androidfw/tests/data/override/build
new file mode 100755
index 00000000000..f6ba33d9ea7
--- /dev/null
+++ b/libs/androidfw/tests/data/override/build
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+aapt package -M AndroidManifest.xml -S res -F bundle.apk -f && \
+unzip bundle.apk resources.arsc && \
+mv resources.arsc override.arsc && \
+xxd -i override.arsc > override_arsc.h
diff --git a/libs/androidfw/tests/data/override/override_arsc.h b/libs/androidfw/tests/data/override/override_arsc.h
new file mode 100644
index 00000000000..2598635d190
--- /dev/null
+++ b/libs/androidfw/tests/data/override/override_arsc.h
@@ -0,0 +1,53 @@
+unsigned char override_arsc[] = {
+ 0x02, 0x00, 0x0c, 0x00, 0x50, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x1c, 0x00, 0x34, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x73, 0x00,
+ 0x74, 0x00, 0x72, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x31, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0x10, 0x02, 0x00, 0x00,
+ 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00,
+ 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00,
+ 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
+ 0x2e, 0x00, 0x6f, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x72, 0x00,
+ 0x69, 0x00, 0x64, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x72, 0x00, 0x00, 0x00, 0x06, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00,
+ 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00,
+ 0x34, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00,
+ 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x00
+};
+unsigned int override_arsc_len = 592;
diff --git a/libs/androidfw/tests/data/override/res/values/values.xml b/libs/androidfw/tests/data/override/res/values/values.xml
new file mode 100644
index 00000000000..7b607eff501
--- /dev/null
+++ b/libs/androidfw/tests/data/override/res/values/values.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="string1">string1</string>
+</resources>
diff --git a/libs/androidfw/tests/data/system/R.h b/libs/androidfw/tests/data/system/R.h
index 7a9d3db9558..ba83b89eb30 100644
--- a/libs/androidfw/tests/data/system/R.h
+++ b/libs/androidfw/tests/data/system/R.h
@@ -6,8 +6,11 @@ namespace R {
namespace attr {
enum {
- background = 0x01010000, // default
- foreground = 0x01010001, // default
+ background = 0x01010000, // default
+ foreground = 0x01010001, // default
+ some_dimen = 0x01010002, // default
+ another_dimen = 0x01010003, // default
+ windowNoTitle = 0x01010056, // default
};
}
diff --git a/libs/androidfw/tests/data/system/res/values/filler.xml b/libs/androidfw/tests/data/system/res/values/filler.xml
new file mode 100644
index 00000000000..27af8dc7abd
--- /dev/null
+++ b/libs/androidfw/tests/data/system/res/values/filler.xml
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Filler so we can test the protected attribute "windowNoTitle" -->
+ <java-symbol name="dummyAttr04" type="attr" id="0x01010004" />
+ <java-symbol name="dummyAttr05" type="attr" id="0x01010005" />
+ <java-symbol name="dummyAttr06" type="attr" id="0x01010006" />
+ <java-symbol name="dummyAttr07" type="attr" id="0x01010007" />
+ <java-symbol name="dummyAttr08" type="attr" id="0x01010008" />
+ <java-symbol name="dummyAttr09" type="attr" id="0x01010009" />
+ <java-symbol name="dummyAttr0a" type="attr" id="0x0101000a" />
+ <java-symbol name="dummyAttr0b" type="attr" id="0x0101000b" />
+ <java-symbol name="dummyAttr0c" type="attr" id="0x0101000c" />
+ <java-symbol name="dummyAttr0d" type="attr" id="0x0101000d" />
+ <java-symbol name="dummyAttr0e" type="attr" id="0x0101000e" />
+ <java-symbol name="dummyAttr0f" type="attr" id="0x0101000f" />
+ <java-symbol name="dummyAttr10" type="attr" id="0x01010010" />
+ <java-symbol name="dummyAttr11" type="attr" id="0x01010011" />
+ <java-symbol name="dummyAttr12" type="attr" id="0x01010012" />
+ <java-symbol name="dummyAttr13" type="attr" id="0x01010013" />
+ <java-symbol name="dummyAttr14" type="attr" id="0x01010014" />
+ <java-symbol name="dummyAttr15" type="attr" id="0x01010015" />
+ <java-symbol name="dummyAttr16" type="attr" id="0x01010016" />
+ <java-symbol name="dummyAttr17" type="attr" id="0x01010017" />
+ <java-symbol name="dummyAttr18" type="attr" id="0x01010018" />
+ <java-symbol name="dummyAttr19" type="attr" id="0x01010019" />
+ <java-symbol name="dummyAttr1a" type="attr" id="0x0101001a" />
+ <java-symbol name="dummyAttr1b" type="attr" id="0x0101001b" />
+ <java-symbol name="dummyAttr1c" type="attr" id="0x0101001c" />
+ <java-symbol name="dummyAttr1d" type="attr" id="0x0101001d" />
+ <java-symbol name="dummyAttr1e" type="attr" id="0x0101001e" />
+ <java-symbol name="dummyAttr1f" type="attr" id="0x0101001f" />
+ <java-symbol name="dummyAttr20" type="attr" id="0x01010020" />
+ <java-symbol name="dummyAttr21" type="attr" id="0x01010021" />
+ <java-symbol name="dummyAttr22" type="attr" id="0x01010022" />
+ <java-symbol name="dummyAttr23" type="attr" id="0x01010023" />
+ <java-symbol name="dummyAttr24" type="attr" id="0x01010024" />
+ <java-symbol name="dummyAttr25" type="attr" id="0x01010025" />
+ <java-symbol name="dummyAttr26" type="attr" id="0x01010026" />
+ <java-symbol name="dummyAttr27" type="attr" id="0x01010027" />
+ <java-symbol name="dummyAttr28" type="attr" id="0x01010028" />
+ <java-symbol name="dummyAttr29" type="attr" id="0x01010029" />
+ <java-symbol name="dummyAttr2a" type="attr" id="0x0101002a" />
+ <java-symbol name="dummyAttr2b" type="attr" id="0x0101002b" />
+ <java-symbol name="dummyAttr2c" type="attr" id="0x0101002c" />
+ <java-symbol name="dummyAttr2d" type="attr" id="0x0101002d" />
+ <java-symbol name="dummyAttr2e" type="attr" id="0x0101002e" />
+ <java-symbol name="dummyAttr2f" type="attr" id="0x0101002f" />
+ <java-symbol name="dummyAttr30" type="attr" id="0x01010030" />
+ <java-symbol name="dummyAttr31" type="attr" id="0x01010031" />
+ <java-symbol name="dummyAttr32" type="attr" id="0x01010032" />
+ <java-symbol name="dummyAttr33" type="attr" id="0x01010033" />
+ <java-symbol name="dummyAttr34" type="attr" id="0x01010034" />
+ <java-symbol name="dummyAttr35" type="attr" id="0x01010035" />
+ <java-symbol name="dummyAttr36" type="attr" id="0x01010036" />
+ <java-symbol name="dummyAttr37" type="attr" id="0x01010037" />
+ <java-symbol name="dummyAttr38" type="attr" id="0x01010038" />
+ <java-symbol name="dummyAttr39" type="attr" id="0x01010039" />
+ <java-symbol name="dummyAttr3a" type="attr" id="0x0101003a" />
+ <java-symbol name="dummyAttr3b" type="attr" id="0x0101003b" />
+ <java-symbol name="dummyAttr3c" type="attr" id="0x0101003c" />
+ <java-symbol name="dummyAttr3d" type="attr" id="0x0101003d" />
+ <java-symbol name="dummyAttr3e" type="attr" id="0x0101003e" />
+ <java-symbol name="dummyAttr3f" type="attr" id="0x0101003f" />
+ <java-symbol name="dummyAttr40" type="attr" id="0x01010040" />
+ <java-symbol name="dummyAttr41" type="attr" id="0x01010041" />
+ <java-symbol name="dummyAttr42" type="attr" id="0x01010042" />
+ <java-symbol name="dummyAttr43" type="attr" id="0x01010043" />
+ <java-symbol name="dummyAttr44" type="attr" id="0x01010044" />
+ <java-symbol name="dummyAttr45" type="attr" id="0x01010045" />
+ <java-symbol name="dummyAttr46" type="attr" id="0x01010046" />
+ <java-symbol name="dummyAttr47" type="attr" id="0x01010047" />
+ <java-symbol name="dummyAttr48" type="attr" id="0x01010048" />
+ <java-symbol name="dummyAttr49" type="attr" id="0x01010049" />
+ <java-symbol name="dummyAttr4a" type="attr" id="0x0101004a" />
+ <java-symbol name="dummyAttr4b" type="attr" id="0x0101004b" />
+ <java-symbol name="dummyAttr4c" type="attr" id="0x0101004c" />
+ <java-symbol name="dummyAttr4d" type="attr" id="0x0101004d" />
+ <java-symbol name="dummyAttr4e" type="attr" id="0x0101004e" />
+ <java-symbol name="dummyAttr4f" type="attr" id="0x0101004f" />
+ <java-symbol name="dummyAttr50" type="attr" id="0x01010050" />
+ <java-symbol name="dummyAttr51" type="attr" id="0x01010051" />
+ <java-symbol name="dummyAttr52" type="attr" id="0x01010052" />
+ <java-symbol name="dummyAttr53" type="attr" id="0x01010053" />
+ <java-symbol name="dummyAttr54" type="attr" id="0x01010054" />
+ <java-symbol name="dummyAttr55" type="attr" id="0x01010055" />
+ <attr name="dummyAttr04" format="reference"/>
+ <attr name="dummyAttr05" format="reference"/>
+ <attr name="dummyAttr06" format="reference"/>
+ <attr name="dummyAttr07" format="reference"/>
+ <attr name="dummyAttr08" format="reference"/>
+ <attr name="dummyAttr09" format="reference"/>
+ <attr name="dummyAttr0a" format="reference"/>
+ <attr name="dummyAttr0b" format="reference"/>
+ <attr name="dummyAttr0c" format="reference"/>
+ <attr name="dummyAttr0d" format="reference"/>
+ <attr name="dummyAttr0e" format="reference"/>
+ <attr name="dummyAttr0f" format="reference"/>
+ <attr name="dummyAttr10" format="reference"/>
+ <attr name="dummyAttr11" format="reference"/>
+ <attr name="dummyAttr12" format="reference"/>
+ <attr name="dummyAttr13" format="reference"/>
+ <attr name="dummyAttr14" format="reference"/>
+ <attr name="dummyAttr15" format="reference"/>
+ <attr name="dummyAttr16" format="reference"/>
+ <attr name="dummyAttr17" format="reference"/>
+ <attr name="dummyAttr18" format="reference"/>
+ <attr name="dummyAttr19" format="reference"/>
+ <attr name="dummyAttr1a" format="reference"/>
+ <attr name="dummyAttr1b" format="reference"/>
+ <attr name="dummyAttr1c" format="reference"/>
+ <attr name="dummyAttr1d" format="reference"/>
+ <attr name="dummyAttr1e" format="reference"/>
+ <attr name="dummyAttr1f" format="reference"/>
+ <attr name="dummyAttr20" format="reference"/>
+ <attr name="dummyAttr21" format="reference"/>
+ <attr name="dummyAttr22" format="reference"/>
+ <attr name="dummyAttr23" format="reference"/>
+ <attr name="dummyAttr24" format="reference"/>
+ <attr name="dummyAttr25" format="reference"/>
+ <attr name="dummyAttr26" format="reference"/>
+ <attr name="dummyAttr27" format="reference"/>
+ <attr name="dummyAttr28" format="reference"/>
+ <attr name="dummyAttr29" format="reference"/>
+ <attr name="dummyAttr2a" format="reference"/>
+ <attr name="dummyAttr2b" format="reference"/>
+ <attr name="dummyAttr2c" format="reference"/>
+ <attr name="dummyAttr2d" format="reference"/>
+ <attr name="dummyAttr2e" format="reference"/>
+ <attr name="dummyAttr2f" format="reference"/>
+ <attr name="dummyAttr30" format="reference"/>
+ <attr name="dummyAttr31" format="reference"/>
+ <attr name="dummyAttr32" format="reference"/>
+ <attr name="dummyAttr33" format="reference"/>
+ <attr name="dummyAttr34" format="reference"/>
+ <attr name="dummyAttr35" format="reference"/>
+ <attr name="dummyAttr36" format="reference"/>
+ <attr name="dummyAttr37" format="reference"/>
+ <attr name="dummyAttr38" format="reference"/>
+ <attr name="dummyAttr39" format="reference"/>
+ <attr name="dummyAttr3a" format="reference"/>
+ <attr name="dummyAttr3b" format="reference"/>
+ <attr name="dummyAttr3c" format="reference"/>
+ <attr name="dummyAttr3d" format="reference"/>
+ <attr name="dummyAttr3e" format="reference"/>
+ <attr name="dummyAttr3f" format="reference"/>
+ <attr name="dummyAttr40" format="reference"/>
+ <attr name="dummyAttr41" format="reference"/>
+ <attr name="dummyAttr42" format="reference"/>
+ <attr name="dummyAttr43" format="reference"/>
+ <attr name="dummyAttr44" format="reference"/>
+ <attr name="dummyAttr45" format="reference"/>
+ <attr name="dummyAttr46" format="reference"/>
+ <attr name="dummyAttr47" format="reference"/>
+ <attr name="dummyAttr48" format="reference"/>
+ <attr name="dummyAttr49" format="reference"/>
+ <attr name="dummyAttr4a" format="reference"/>
+ <attr name="dummyAttr4b" format="reference"/>
+ <attr name="dummyAttr4c" format="reference"/>
+ <attr name="dummyAttr4d" format="reference"/>
+ <attr name="dummyAttr4e" format="reference"/>
+ <attr name="dummyAttr4f" format="reference"/>
+ <attr name="dummyAttr50" format="reference"/>
+ <attr name="dummyAttr51" format="reference"/>
+ <attr name="dummyAttr52" format="reference"/>
+ <attr name="dummyAttr53" format="reference"/>
+ <attr name="dummyAttr54" format="reference"/>
+ <attr name="dummyAttr55" format="reference"/>
+</resources> \ No newline at end of file
diff --git a/libs/androidfw/tests/data/system/res/values/themes.xml b/libs/androidfw/tests/data/system/res/values/themes.xml
index b29848efd02..c870b1ed3f5 100644
--- a/libs/androidfw/tests/data/system/res/values/themes.xml
+++ b/libs/androidfw/tests/data/system/res/values/themes.xml
@@ -2,12 +2,20 @@
<resources>
<public name="background" type="attr" id="0x01010000"/>
<public name="foreground" type="attr" id="0x01010001"/>
+ <public name="someDimen" type="attr" id="0x01010002" />
+ <public name="anotherDimen" type="attr" id="0x01010003" />
+ <!-- attributes 0x01010004 to 0x01010055 are in filler.xml -->
+ <public name="windowNoTitle" type="attr" id="0x01010056" />
<public name="Theme.One" type="style" id="0x01020000"/>
<attr name="background" format="color|reference"/>
<attr name="foreground" format="color|reference"/>
+ <attr name="someDimen" format="dimension|reference"/>
+ <attr name="anotherDimen" format="dimension|reference"/>
+ <attr name="windowNoTitle" format="boolean|reference"/>
<style name="Theme.One" parent="">
<item name="android:background">#ff0000</item>
<item name="android:foreground">#000000</item>
+ <item name="android:windowNoTitle">true</item>
</style>
</resources>
diff --git a/libs/androidfw/tests/data/system/system_arsc.h b/libs/androidfw/tests/data/system/system_arsc.h
index 215ecae552c..e157a0e835a 100644
--- a/libs/androidfw/tests/data/system/system_arsc.h
+++ b/libs/androidfw/tests/data/system/system_arsc.h
@@ -1,8 +1,8 @@
unsigned char system_arsc[] = {
- 0x02, 0x00, 0x0c, 0x00, 0x18, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x0c, 0x00, 0x10, 0x19, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x01, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xf0, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xe8, 0x18, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00,
0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -25,45 +25,514 @@ unsigned char system_arsc[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x40, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00,
0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x73, 0x00, 0x74, 0x00, 0x79, 0x00,
0x6c, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00,
- 0x70, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x68, 0x0a, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x7c, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x62, 0x00, 0x61, 0x00, 0x63, 0x00, 0x6b, 0x00, 0x67, 0x00,
- 0x72, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x66, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x65, 0x00, 0x67, 0x00,
- 0x72, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x54, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00,
- 0x2e, 0x00, 0x4f, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40,
- 0x01, 0x02, 0x44, 0x00, 0x84, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x46, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00,
+ 0x96, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0xca, 0x00, 0x00, 0x00,
+ 0xe4, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x18, 0x01, 0x00, 0x00,
+ 0x32, 0x01, 0x00, 0x00, 0x4c, 0x01, 0x00, 0x00, 0x66, 0x01, 0x00, 0x00,
+ 0x80, 0x01, 0x00, 0x00, 0x9a, 0x01, 0x00, 0x00, 0xb4, 0x01, 0x00, 0x00,
+ 0xce, 0x01, 0x00, 0x00, 0xe8, 0x01, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00,
+ 0x1c, 0x02, 0x00, 0x00, 0x36, 0x02, 0x00, 0x00, 0x50, 0x02, 0x00, 0x00,
+ 0x6a, 0x02, 0x00, 0x00, 0x84, 0x02, 0x00, 0x00, 0x9e, 0x02, 0x00, 0x00,
+ 0xb8, 0x02, 0x00, 0x00, 0xd2, 0x02, 0x00, 0x00, 0xec, 0x02, 0x00, 0x00,
+ 0x06, 0x03, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x3a, 0x03, 0x00, 0x00,
+ 0x54, 0x03, 0x00, 0x00, 0x6e, 0x03, 0x00, 0x00, 0x88, 0x03, 0x00, 0x00,
+ 0xa2, 0x03, 0x00, 0x00, 0xbc, 0x03, 0x00, 0x00, 0xd6, 0x03, 0x00, 0x00,
+ 0xf0, 0x03, 0x00, 0x00, 0x0a, 0x04, 0x00, 0x00, 0x24, 0x04, 0x00, 0x00,
+ 0x3e, 0x04, 0x00, 0x00, 0x58, 0x04, 0x00, 0x00, 0x72, 0x04, 0x00, 0x00,
+ 0x8c, 0x04, 0x00, 0x00, 0xa6, 0x04, 0x00, 0x00, 0xc0, 0x04, 0x00, 0x00,
+ 0xda, 0x04, 0x00, 0x00, 0xf4, 0x04, 0x00, 0x00, 0x0e, 0x05, 0x00, 0x00,
+ 0x28, 0x05, 0x00, 0x00, 0x42, 0x05, 0x00, 0x00, 0x5c, 0x05, 0x00, 0x00,
+ 0x76, 0x05, 0x00, 0x00, 0x90, 0x05, 0x00, 0x00, 0xaa, 0x05, 0x00, 0x00,
+ 0xc4, 0x05, 0x00, 0x00, 0xde, 0x05, 0x00, 0x00, 0xf8, 0x05, 0x00, 0x00,
+ 0x12, 0x06, 0x00, 0x00, 0x2c, 0x06, 0x00, 0x00, 0x46, 0x06, 0x00, 0x00,
+ 0x60, 0x06, 0x00, 0x00, 0x7a, 0x06, 0x00, 0x00, 0x94, 0x06, 0x00, 0x00,
+ 0xae, 0x06, 0x00, 0x00, 0xc8, 0x06, 0x00, 0x00, 0xe2, 0x06, 0x00, 0x00,
+ 0xfc, 0x06, 0x00, 0x00, 0x16, 0x07, 0x00, 0x00, 0x30, 0x07, 0x00, 0x00,
+ 0x4a, 0x07, 0x00, 0x00, 0x64, 0x07, 0x00, 0x00, 0x7e, 0x07, 0x00, 0x00,
+ 0x98, 0x07, 0x00, 0x00, 0xb2, 0x07, 0x00, 0x00, 0xcc, 0x07, 0x00, 0x00,
+ 0xe6, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x1a, 0x08, 0x00, 0x00,
+ 0x34, 0x08, 0x00, 0x00, 0x4e, 0x08, 0x00, 0x00, 0x68, 0x08, 0x00, 0x00,
+ 0x82, 0x08, 0x00, 0x00, 0x9c, 0x08, 0x00, 0x00, 0xb6, 0x08, 0x00, 0x00,
+ 0xd4, 0x08, 0x00, 0x00, 0x0a, 0x00, 0x62, 0x00, 0x61, 0x00, 0x63, 0x00,
+ 0x6b, 0x00, 0x67, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x6e, 0x00,
+ 0x64, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x66, 0x00, 0x6f, 0x00, 0x72, 0x00,
+ 0x65, 0x00, 0x67, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x6e, 0x00,
+ 0x64, 0x00, 0x00, 0x00, 0x09, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x6d, 0x00,
+ 0x65, 0x00, 0x44, 0x00, 0x69, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x6e, 0x00,
+ 0x00, 0x00, 0x0c, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x74, 0x00,
+ 0x68, 0x00, 0x65, 0x00, 0x72, 0x00, 0x44, 0x00, 0x69, 0x00, 0x6d, 0x00,
+ 0x65, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00,
+ 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x72, 0x00, 0x30, 0x00, 0x34, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00,
+ 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00,
+ 0x74, 0x00, 0x72, 0x00, 0x30, 0x00, 0x35, 0x00, 0x00, 0x00, 0x0b, 0x00,
+ 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x30, 0x00, 0x36, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00,
+ 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x30, 0x00, 0x37, 0x00,
+ 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00,
+ 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x30, 0x00,
+ 0x38, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00,
+ 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00,
+ 0x30, 0x00, 0x39, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00,
+ 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x72, 0x00, 0x30, 0x00, 0x61, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00,
+ 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00,
+ 0x74, 0x00, 0x72, 0x00, 0x30, 0x00, 0x62, 0x00, 0x00, 0x00, 0x0b, 0x00,
+ 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x30, 0x00, 0x63, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00,
+ 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x30, 0x00, 0x64, 0x00,
+ 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00,
+ 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x30, 0x00,
+ 0x65, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00,
+ 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00,
+ 0x30, 0x00, 0x66, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00,
+ 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x72, 0x00, 0x31, 0x00, 0x30, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00,
+ 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00,
+ 0x74, 0x00, 0x72, 0x00, 0x31, 0x00, 0x31, 0x00, 0x00, 0x00, 0x0b, 0x00,
+ 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x31, 0x00, 0x32, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00,
+ 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x31, 0x00, 0x33, 0x00,
+ 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00,
+ 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x31, 0x00,
+ 0x34, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00,
+ 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00,
+ 0x31, 0x00, 0x35, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00,
+ 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x72, 0x00, 0x31, 0x00, 0x36, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00,
+ 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00,
+ 0x74, 0x00, 0x72, 0x00, 0x31, 0x00, 0x37, 0x00, 0x00, 0x00, 0x0b, 0x00,
+ 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x31, 0x00, 0x38, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00,
+ 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x31, 0x00, 0x39, 0x00,
+ 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00,
+ 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x31, 0x00,
+ 0x61, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00,
+ 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00,
+ 0x31, 0x00, 0x62, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00,
+ 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x72, 0x00, 0x31, 0x00, 0x63, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00,
+ 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00,
+ 0x74, 0x00, 0x72, 0x00, 0x31, 0x00, 0x64, 0x00, 0x00, 0x00, 0x0b, 0x00,
+ 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x31, 0x00, 0x65, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00,
+ 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x31, 0x00, 0x66, 0x00,
+ 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00,
+ 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x32, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00,
+ 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00,
+ 0x32, 0x00, 0x31, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00,
+ 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x72, 0x00, 0x32, 0x00, 0x32, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00,
+ 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00,
+ 0x74, 0x00, 0x72, 0x00, 0x32, 0x00, 0x33, 0x00, 0x00, 0x00, 0x0b, 0x00,
+ 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x32, 0x00, 0x34, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00,
+ 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x32, 0x00, 0x35, 0x00,
+ 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00,
+ 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x32, 0x00,
+ 0x36, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00,
+ 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00,
+ 0x32, 0x00, 0x37, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00,
+ 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x72, 0x00, 0x32, 0x00, 0x38, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00,
+ 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00,
+ 0x74, 0x00, 0x72, 0x00, 0x32, 0x00, 0x39, 0x00, 0x00, 0x00, 0x0b, 0x00,
+ 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x32, 0x00, 0x61, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00,
+ 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x32, 0x00, 0x62, 0x00,
+ 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00,
+ 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x32, 0x00,
+ 0x63, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00,
+ 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00,
+ 0x32, 0x00, 0x64, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00,
+ 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x72, 0x00, 0x32, 0x00, 0x65, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00,
+ 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00,
+ 0x74, 0x00, 0x72, 0x00, 0x32, 0x00, 0x66, 0x00, 0x00, 0x00, 0x0b, 0x00,
+ 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x33, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00,
+ 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x33, 0x00, 0x31, 0x00,
+ 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00,
+ 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x33, 0x00,
+ 0x32, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00,
+ 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00,
+ 0x33, 0x00, 0x33, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00,
+ 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x72, 0x00, 0x33, 0x00, 0x34, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00,
+ 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00,
+ 0x74, 0x00, 0x72, 0x00, 0x33, 0x00, 0x35, 0x00, 0x00, 0x00, 0x0b, 0x00,
+ 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x33, 0x00, 0x36, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00,
+ 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x33, 0x00, 0x37, 0x00,
+ 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00,
+ 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x33, 0x00,
+ 0x38, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00,
+ 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00,
+ 0x33, 0x00, 0x39, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00,
+ 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x72, 0x00, 0x33, 0x00, 0x61, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00,
+ 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00,
+ 0x74, 0x00, 0x72, 0x00, 0x33, 0x00, 0x62, 0x00, 0x00, 0x00, 0x0b, 0x00,
+ 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x33, 0x00, 0x63, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00,
+ 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x33, 0x00, 0x64, 0x00,
+ 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00,
+ 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x33, 0x00,
+ 0x65, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00,
+ 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00,
+ 0x33, 0x00, 0x66, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00,
+ 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x72, 0x00, 0x34, 0x00, 0x30, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00,
+ 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00,
+ 0x74, 0x00, 0x72, 0x00, 0x34, 0x00, 0x31, 0x00, 0x00, 0x00, 0x0b, 0x00,
+ 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x34, 0x00, 0x32, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00,
+ 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x34, 0x00, 0x33, 0x00,
+ 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00,
+ 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x34, 0x00,
+ 0x34, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00,
+ 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00,
+ 0x34, 0x00, 0x35, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00,
+ 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x72, 0x00, 0x34, 0x00, 0x36, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00,
+ 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00,
+ 0x74, 0x00, 0x72, 0x00, 0x34, 0x00, 0x37, 0x00, 0x00, 0x00, 0x0b, 0x00,
+ 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x34, 0x00, 0x38, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00,
+ 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x34, 0x00, 0x39, 0x00,
+ 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00,
+ 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x34, 0x00,
+ 0x61, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00,
+ 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00,
+ 0x34, 0x00, 0x62, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00,
+ 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x72, 0x00, 0x34, 0x00, 0x63, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00,
+ 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00,
+ 0x74, 0x00, 0x72, 0x00, 0x34, 0x00, 0x64, 0x00, 0x00, 0x00, 0x0b, 0x00,
+ 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x34, 0x00, 0x65, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00,
+ 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x34, 0x00, 0x66, 0x00,
+ 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00,
+ 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x35, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00,
+ 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00,
+ 0x35, 0x00, 0x31, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00,
+ 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x72, 0x00, 0x35, 0x00, 0x32, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x64, 0x00,
+ 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00, 0x74, 0x00,
+ 0x74, 0x00, 0x72, 0x00, 0x35, 0x00, 0x33, 0x00, 0x00, 0x00, 0x0b, 0x00,
+ 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00, 0x41, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x35, 0x00, 0x34, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x64, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x79, 0x00,
+ 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x35, 0x00, 0x35, 0x00,
+ 0x00, 0x00, 0x0d, 0x00, 0x77, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x64, 0x00,
+ 0x6f, 0x00, 0x77, 0x00, 0x4e, 0x00, 0x6f, 0x00, 0x54, 0x00, 0x69, 0x00,
+ 0x74, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x00, 0x00, 0x09, 0x00, 0x54, 0x00,
+ 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x2e, 0x00, 0x4f, 0x00,
+ 0x6e, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+ 0x6c, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x1c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
- 0x08, 0x00, 0x00, 0x10, 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x01, 0x02, 0x44, 0x00, 0x24, 0x0b, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x57, 0x00, 0x00, 0x00, 0xa0, 0x01, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x1c, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00,
+ 0x70, 0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00,
+ 0xc4, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00,
+ 0x18, 0x01, 0x00, 0x00, 0x34, 0x01, 0x00, 0x00, 0x50, 0x01, 0x00, 0x00,
+ 0x6c, 0x01, 0x00, 0x00, 0x88, 0x01, 0x00, 0x00, 0xa4, 0x01, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0xdc, 0x01, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x00,
+ 0x14, 0x02, 0x00, 0x00, 0x30, 0x02, 0x00, 0x00, 0x4c, 0x02, 0x00, 0x00,
+ 0x68, 0x02, 0x00, 0x00, 0x84, 0x02, 0x00, 0x00, 0xa0, 0x02, 0x00, 0x00,
+ 0xbc, 0x02, 0x00, 0x00, 0xd8, 0x02, 0x00, 0x00, 0xf4, 0x02, 0x00, 0x00,
+ 0x10, 0x03, 0x00, 0x00, 0x2c, 0x03, 0x00, 0x00, 0x48, 0x03, 0x00, 0x00,
+ 0x64, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x9c, 0x03, 0x00, 0x00,
+ 0xb8, 0x03, 0x00, 0x00, 0xd4, 0x03, 0x00, 0x00, 0xf0, 0x03, 0x00, 0x00,
+ 0x0c, 0x04, 0x00, 0x00, 0x28, 0x04, 0x00, 0x00, 0x44, 0x04, 0x00, 0x00,
+ 0x60, 0x04, 0x00, 0x00, 0x7c, 0x04, 0x00, 0x00, 0x98, 0x04, 0x00, 0x00,
+ 0xb4, 0x04, 0x00, 0x00, 0xd0, 0x04, 0x00, 0x00, 0xec, 0x04, 0x00, 0x00,
+ 0x08, 0x05, 0x00, 0x00, 0x24, 0x05, 0x00, 0x00, 0x40, 0x05, 0x00, 0x00,
+ 0x5c, 0x05, 0x00, 0x00, 0x78, 0x05, 0x00, 0x00, 0x94, 0x05, 0x00, 0x00,
+ 0xb0, 0x05, 0x00, 0x00, 0xcc, 0x05, 0x00, 0x00, 0xe8, 0x05, 0x00, 0x00,
+ 0x04, 0x06, 0x00, 0x00, 0x20, 0x06, 0x00, 0x00, 0x3c, 0x06, 0x00, 0x00,
+ 0x58, 0x06, 0x00, 0x00, 0x74, 0x06, 0x00, 0x00, 0x90, 0x06, 0x00, 0x00,
+ 0xac, 0x06, 0x00, 0x00, 0xc8, 0x06, 0x00, 0x00, 0xe4, 0x06, 0x00, 0x00,
+ 0x00, 0x07, 0x00, 0x00, 0x1c, 0x07, 0x00, 0x00, 0x38, 0x07, 0x00, 0x00,
+ 0x54, 0x07, 0x00, 0x00, 0x70, 0x07, 0x00, 0x00, 0x8c, 0x07, 0x00, 0x00,
+ 0xa8, 0x07, 0x00, 0x00, 0xc4, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00, 0x00,
+ 0xfc, 0x07, 0x00, 0x00, 0x18, 0x08, 0x00, 0x00, 0x34, 0x08, 0x00, 0x00,
+ 0x50, 0x08, 0x00, 0x00, 0x6c, 0x08, 0x00, 0x00, 0x88, 0x08, 0x00, 0x00,
+ 0xa4, 0x08, 0x00, 0x00, 0xc0, 0x08, 0x00, 0x00, 0xdc, 0x08, 0x00, 0x00,
+ 0xf8, 0x08, 0x00, 0x00, 0x14, 0x09, 0x00, 0x00, 0x30, 0x09, 0x00, 0x00,
+ 0x4c, 0x09, 0x00, 0x00, 0x68, 0x09, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x11, 0x00, 0x00, 0x00,
- 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x02, 0x44, 0x00,
- 0x70, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x41, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x41, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x17, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x1a, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x1d, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x23, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x26, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x29, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x2c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x2f, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x32, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x35, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x38, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x3b, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x3e, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x41, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x44, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x47, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x4a, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x4d, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x50, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x53, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, 0x56, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x09, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x01, 0x02, 0x44, 0x00, 0x7c, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0x01, 0x08, 0x00, 0x00, 0x1d, 0x00, 0x00, 0xff, 0xff,
- 0x01, 0x00, 0x01, 0x01, 0x08, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0xff
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, 0x57, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01,
+ 0x08, 0x00, 0x00, 0x1d, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x01, 0x01,
+ 0x08, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0xff, 0x56, 0x00, 0x01, 0x01,
+ 0x08, 0x00, 0x00, 0x12, 0xff, 0xff, 0xff, 0xff
};
-unsigned int system_arsc_len = 792;
+unsigned int system_arsc_len = 6416;
diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
index 1f28324242b..4b047865628 100644
--- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
+++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
@@ -186,6 +186,7 @@ public class DefaultContainerService extends IntentService {
ret.recommendedInstallLocation = PackageHelper.resolveInstallLocation(context,
pkg.packageName, pkg.installLocation, sizeBytes, flags);
ret.multiArch = pkg.multiArch;
+ ret.isTheme = pkg.isTheme;
return ret;
}
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index b99b6ecedf8..ef37eee1b93 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -213,4 +213,7 @@
<!-- Default for Settings.Global.GUEST_USER_ENABLED -->
<bool name="def_guest_user_enabled">true</bool>
+ <!-- Default theme -->
+ <string name="def_theme_package"></string>
+ <string name="def_theme_components"></string>
</resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 59a3ea3e3e0..8a65a39b5f2 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -23,9 +23,14 @@ import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.ThemeConfig;
import android.content.res.XmlResourceParser;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteStatement;
import android.media.AudioManager;
@@ -73,8 +78,8 @@ public class DatabaseHelper extends SQLiteOpenHelper {
// database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion'
// is properly propagated through your change. Not doing so will result in a loss of user
// settings.
- private static final int DATABASE_VERSION = 113;
-
+ private static final int DATABASE_VERSION = 115;
+
private static final String HEADSET = "_headset";
private static final String SPEAKER = "_speaker";
private static final String EARPIECE = "_earpiece";
@@ -1837,6 +1842,53 @@ public class DatabaseHelper extends SQLiteOpenHelper {
upgradeVersion = 113;
}
+ if (upgradeVersion < 114) {
+ // CM11 used "holo" as a system default theme. For CM12 and up its been
+ // switched to "system". So change all "holo" references in themeConfig to "system"
+ final String NAME_THEME_CONFIG = "themeConfig";
+ Cursor c = null;
+ try {
+ String[] projection = new String[]{"value"};
+ String selection = "name=?";
+ String[] selectionArgs = new String[] { NAME_THEME_CONFIG };
+ c = db.query(TABLE_SECURE, projection, selection,
+ selectionArgs, null, null, null);
+ if (c != null && c.moveToFirst()) {
+ String jsonConfig = c.getString(0);
+ if (jsonConfig != null) {
+ jsonConfig = jsonConfig.replace(
+ "\"holo\"", '"' + ThemeConfig.SYSTEM_DEFAULT + '"');
+
+ // Now update the entry
+ SQLiteStatement stmt = db.compileStatement(
+ "UPDATE " + TABLE_SECURE + " SET value = ? "
+ + " WHERE name = ?");
+ stmt.bindString(1, jsonConfig);
+ stmt.bindString(2, NAME_THEME_CONFIG);
+ stmt.execute();
+ }
+ }
+ } catch(SQLiteException ex) {
+ Log.e(TAG, "Unable to update theme config value", ex);
+ } finally {
+ if (c != null) c.close();
+ }
+ upgradeVersion = 114;
+ }
+
+ if (upgradeVersion < 115) {
+ db.beginTransaction();
+ SQLiteStatement stmt = null;
+ try {
+ stmt = db.compileStatement("INSERT OR IGNORE INTO secure(name,value) VALUES(?,?);");
+ loadDefaultThemeSettings(stmt);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ if (stmt != null) stmt.close();
+ }
+ upgradeVersion = 115;
+ }
// *** Remember to update DATABASE_VERSION above!
if (upgradeVersion != currentVersion) {
@@ -2416,6 +2468,12 @@ public class DatabaseHelper extends SQLiteOpenHelper {
R.bool.def_haptic_feedback);
}
+ private void loadDefaultThemeSettings(SQLiteStatement stmt) {
+ loadStringSetting(stmt, Settings.Secure.DEFAULT_THEME_PACKAGE, R.string.def_theme_package);
+ loadStringSetting(stmt, Settings.Secure.DEFAULT_THEME_COMPONENTS,
+ R.string.def_theme_components);
+ }
+
private void loadSecureSettings(SQLiteDatabase db) {
SQLiteStatement stmt = null;
try {
@@ -2524,6 +2582,8 @@ public class DatabaseHelper extends SQLiteOpenHelper {
R.string.def_input_method);
}
+ loadDefaultThemeSettings(stmt);
+
if (!TextUtils.isEmpty(mContext.getResources().getString(
R.string.def_enable_input_methods))) {
loadStringSetting(stmt, Settings.Secure.ENABLED_INPUT_METHODS,
diff --git a/packages/SystemUI/res/layout/msim_super_status_bar.xml b/packages/SystemUI/res/layout/msim_super_status_bar.xml
index 491cbdc7909..1047fe7ceaa 100644
--- a/packages/SystemUI/res/layout/msim_super_status_bar.xml
+++ b/packages/SystemUI/res/layout/msim_super_status_bar.xml
@@ -20,12 +20,12 @@
-->
<!-- This is the combined status bar / notification panel window. -->
-<com.android.systemui.statusbar.phone.StatusBarWindowView
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
- android:fitsSystemWindows="true"
+ android:fitsSystemWindows="false"
android:descendantFocusability="afterDescendants">
<com.android.systemui.statusbar.BackDropView
@@ -84,4 +84,4 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
-</com.android.systemui.statusbar.phone.StatusBarWindowView>
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml
index 15d6191493d..3a93d4cc013 100644
--- a/packages/SystemUI/res/layout/super_status_bar.xml
+++ b/packages/SystemUI/res/layout/super_status_bar.xml
@@ -18,12 +18,12 @@
-->
<!-- This is the combined status bar / notification panel window. -->
-<com.android.systemui.statusbar.phone.StatusBarWindowView
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
- android:fitsSystemWindows="true"
+ android:fitsSystemWindows="false"
android:descendantFocusability="afterDescendants">
<com.android.systemui.statusbar.BackDropView
@@ -84,4 +84,4 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
-</com.android.systemui.statusbar.phone.StatusBarWindowView>
+</FrameLayout>
diff --git a/packages/SystemUI/res/values/cm_colors.xml b/packages/SystemUI/res/values/cm_colors.xml
new file mode 100644
index 00000000000..54d1b98a484
--- /dev/null
+++ b/packages/SystemUI/res/values/cm_colors.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The CyanogenMod 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>
+
+ <!-- Color for the "No recent apps" text in the recent apps list. -->
+ <color name="no_recent_apps">#bebebe</color>
+
+ <!-- Colors for the system bars -->
+ <color name="status_bar_background_opaque">@color/system_bar_background_opaque</color>
+ <color name="status_bar_background_semi_transparent">
+ @color/system_bar_background_semi_transparent
+ </color>
+ <color name="status_bar_background_transparent">@color/system_bar_background_transparent</color>
+ <color name="navigation_bar_background_opaque">@color/system_bar_background_opaque</color>
+ <color name="navigation_bar_background_semi_transparent">
+ @color/system_bar_background_semi_transparent
+ </color>
+ <color name="navigation_bar_background_transparent">
+ @color/system_bar_background_transparent
+ </color>
+
+</resources>
diff --git a/packages/SystemUI/res/values/cm_dimens.xml b/packages/SystemUI/res/values/cm_dimens.xml
new file mode 100644
index 00000000000..39fbf85a8f4
--- /dev/null
+++ b/packages/SystemUI/res/values/cm_dimens.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The CyanogenMod 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="weather_text_size">12sp</dimen>
+ <dimen name="detail_radio_group_padding_left">16dp</dimen>
+ <dimen name="detail_radio_group_padding">8dp</dimen>
+ <dimen name="qs_tile_icon_size_visualizer">40dp</dimen>
+
+ <!-- Themes: radius of the corners of the notification dropshadow
+ See also: notification_material_rounded_rect_radius
+ -->
+ <dimen name="notification_material_shadow_rounded_rect_radius">0dp</dimen>
+
+</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index a84ba6d8392..4713b48ea59 100755
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1295,7 +1295,8 @@ public class KeyguardViewMediator extends SystemUI {
// startKeyguardExitAnimation.
mWM.keyguardGoingAway(
mStatusBarKeyguardViewManager.shouldDisableWindowAnimationsForUnlock(),
- mStatusBarKeyguardViewManager.isGoingToNotificationShade());
+ mStatusBarKeyguardViewManager.isGoingToNotificationShade(),
+ mStatusBarKeyguardViewManager.isKeyguardShowingMedia());
} catch (RemoteException e) {
Log.e(TAG, "Error while calling WindowManager", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index e4faa6a3f85..5e68592b514 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -151,6 +151,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mAppearAnimationFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
mRoundedRectCornerRadius = getResources().getDimensionPixelSize(
R.dimen.notification_material_rounded_rect_radius);
+ setRoundCornerRadius(mRoundedRectCornerRadius); // Themes: For drop-shadow rounded corners
mLegacyColor = getResources().getColor(R.color.notification_legacy_background_color);
mNormalColor = getResources().getColor(R.color.notification_material_background_color);
mLowPriorityColor = getResources().getColor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 02a070dd66e..cd76946d43d 100755
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -41,6 +41,7 @@ import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.content.res.ThemeConfig;
import android.database.ContentObserver;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
@@ -1360,12 +1361,15 @@ public abstract class BaseStatusBar extends SystemUI implements
// set up the adaptive layout
View contentViewLocal = null;
View bigContentViewLocal = null;
+ final ThemeConfig themeConfig = mContext.getResources().getConfiguration().themeConfig;
+ String themePackageName = themeConfig != null ?
+ themeConfig.getOverlayPkgNameForApp(mContext.getPackageName()) : null;
try {
contentViewLocal = contentView.apply(mContext, expanded,
- mOnClickHandler);
+ mOnClickHandler, themePackageName);
if (bigContentView != null) {
bigContentViewLocal = bigContentView.apply(mContext, expanded,
- mOnClickHandler);
+ mOnClickHandler, themePackageName);
}
}
catch (RuntimeException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 9db875fab06..396bed28c76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -69,6 +69,7 @@ public class CommandQueue extends IStatusBar.Stub {
private StatusBarIconList mList;
private Callbacks mCallbacks;
private Handler mHandler = new H();
+ private boolean mPaused = false;
/**
* These methods are called back on the main thread.
@@ -238,8 +239,20 @@ public class CommandQueue extends IStatusBar.Stub {
}
}
+ public void pause() {
+ mPaused = true;
+ }
+
+ public void resume() {
+ mPaused = false;
+ }
+
private final class H extends Handler {
public void handleMessage(Message msg) {
+ if (mPaused) {
+ this.sendMessageAtFrontOfQueue(Message.obtain(msg));
+ return;
+ }
final int what = msg.what & MSG_MASK;
switch (what) {
case MSG_ICON: {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
index f85d32b3770..62b53ec7777 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
@@ -31,6 +31,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
private final Rect mOutlineRect = new Rect();
private boolean mCustomOutline;
+ private float mRoundCornerRadius = 0;
public ExpandableOutlineView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -38,12 +39,12 @@ public abstract class ExpandableOutlineView extends ExpandableView {
@Override
public void getOutline(View view, Outline outline) {
if (!mCustomOutline) {
- outline.setRect(0,
+ outline.setRoundRect(0,
mClipTopAmount,
getWidth(),
- Math.max(mActualHeight, mClipTopAmount));
+ Math.max(mActualHeight, mClipTopAmount), mRoundCornerRadius);
} else {
- outline.setRect(mOutlineRect);
+ outline.setRoundRect(mOutlineRect, mRoundCornerRadius);
}
}
});
@@ -82,4 +83,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
invalidateOutline();
}
+ protected void setRoundCornerRadius(float roundRadius) {
+ mRoundCornerRadius = roundRadius;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 34c458ad4b9..8b466db88f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -160,6 +160,14 @@ public class NotificationData {
return mEntries.get(key);
}
+ public Entry get(int i) {
+ return mEntries.valueAt(i);
+ }
+
+ public RankingMap getRankingMap() {
+ return mRankingMap;
+ }
+
public void add(Entry entry, RankingMap ranking) {
mEntries.put(entry.notification.getKey(), entry);
updateRankingAndSort(ranking);
@@ -271,6 +279,14 @@ public class NotificationData {
return false;
}
+ public void clear() {
+ mEntries.clear();
+ }
+
+ public int size() {
+ return mEntries.size();
+ }
+
// Q: What kinds of notifications should show during setup?
// A: Almost none! Only things coming from the system (package is "android") that also
// have special "kind" tags marking them as relevant for setup (see below).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 20dd3e7e1e1..1e8876da3d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -20,6 +20,7 @@ import android.app.Notification;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.content.res.ThemeConfig;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
@@ -180,8 +181,15 @@ public class StatusBarIconView extends AnimatedImageView {
if (userId == UserHandle.USER_ALL) {
userId = UserHandle.USER_OWNER;
}
- r = context.getPackageManager()
- .getResourcesForApplicationAsUser(icon.iconPackage, userId);
+ PackageManager pm = context.getPackageManager();
+ final ThemeConfig config = context.getResources().getConfiguration().themeConfig;
+ if (config != null) {
+ final String pkgName = config.getOverlayPkgNameForApp(context.getPackageName());
+ r = pm.getThemedResourcesForApplicationAsUser(icon.iconPackage,
+ pkgName, userId);
+ } else {
+ r = context.getResources();
+ }
} catch (PackageManager.NameNotFoundException ex) {
Log.e(TAG, "Icon package not found: " + icon.iconPackage);
return null;
@@ -217,6 +225,10 @@ public class StatusBarIconView extends AnimatedImageView {
}
}
+ public String getStatusBarSlot() {
+ return mSlot;
+ }
+
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
index 32fb56766c6..e77ce104a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
@@ -56,15 +56,28 @@ public class BarTransitions {
private int mMode;
- public BarTransitions(View view, int gradientResourceId) {
+ public BarTransitions(View view, int gradientResourceId, int opaqueColorResourceId,
+ int semiTransparentColorResourceId, int transparentColorResourceId,
+ int warningColorResourceId) {
mTag = "BarTransitions." + view.getClass().getSimpleName();
mView = view;
- mBarBackground = new BarBackgroundDrawable(mView.getContext(), gradientResourceId);
+ mBarBackground = new BarBackgroundDrawable(mView.getContext(), gradientResourceId,
+ opaqueColorResourceId, semiTransparentColorResourceId,
+ transparentColorResourceId, warningColorResourceId);
if (HIGH_END) {
mView.setBackground(mBarBackground);
}
}
+ protected void setGradientResourceId(int gradientResourceId) {
+ mBarBackground.setGradientResourceId(mView.getContext().getResources(),
+ gradientResourceId);
+ }
+
+ public void updateResources(Resources res) {
+ mBarBackground.updateResources(res);
+ }
+
public int getMode() {
return mMode;
}
@@ -110,11 +123,11 @@ public class BarTransitions {
}
private static class BarBackgroundDrawable extends Drawable {
- private final int mOpaque;
- private final int mSemiTransparent;
- private final int mTransparent;
- private final int mWarning;
- private final Drawable mGradient;
+ private int mOpaque;
+ private int mSemiTransparent;
+ private int mTransparent;
+ private int mWarning;
+ private Drawable mGradient;
private final TimeInterpolator mInterpolator;
private int mMode = -1;
@@ -128,7 +141,15 @@ public class BarTransitions {
private int mGradientAlphaStart;
private int mColorStart;
- public BarBackgroundDrawable(Context context, int gradientResourceId) {
+ private int mGradientResourceId;
+ private final int mOpaqueColorResourceId;
+ private final int mSemiTransparentColorResourceId;
+ private final int mTransparentColorResourceId;
+ private final int mWarningColorResourceId;
+
+ public BarBackgroundDrawable(Context context, int gradientResourceId,
+ int opaqueColorResourceId, int semiTransparentColorResourceId,
+ int transparentColorResourceId, int warningColorResourceId) {
final Resources res = context.getResources();
if (DEBUG_COLORS) {
mOpaque = 0xff0000ff;
@@ -138,11 +159,34 @@ public class BarTransitions {
} else {
mOpaque = res.getColor(R.color.system_bar_background_opaque);
mSemiTransparent = res.getColor(R.color.system_bar_background_semi_transparent);
- mTransparent = res.getColor(R.color.system_bar_background_transparent);
- mWarning = res.getColor(com.android.internal.R.color.battery_saver_mode_color);
+ mTransparent = res.getColor(transparentColorResourceId);
+ mWarning = res.getColor(warningColorResourceId);
}
mGradient = res.getDrawable(gradientResourceId);
mInterpolator = new LinearInterpolator();
+ mGradientResourceId = gradientResourceId;
+ mOpaqueColorResourceId = opaqueColorResourceId;
+ mSemiTransparentColorResourceId = semiTransparentColorResourceId;
+ mTransparentColorResourceId = transparentColorResourceId;
+ mWarningColorResourceId = warningColorResourceId;
+ }
+
+ public void setGradientResourceId(Resources res, int gradientResourceId) {
+ mGradient = res.getDrawable(gradientResourceId);
+ mGradientResourceId = gradientResourceId;
+ }
+
+ public void updateResources(Resources res) {
+ mOpaque = res.getColor(mOpaqueColorResourceId);
+ mSemiTransparent = res.getColor(mSemiTransparentColorResourceId);
+ mTransparent = res.getColor(mTransparentColorResourceId);
+ mWarning = res.getColor(mWarningColorResourceId);
+ // Retrieve the current bounds for mGradient so they can be set to
+ // the new drawable being loaded, otherwise the bounds will be (0, 0, 0, 0)
+ // and the gradient will not be drawn.
+ Rect bounds = mGradient.getBounds();
+ mGradient = res.getDrawable(mGradientResourceId);
+ mGradient.setBounds(bounds);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index f3930ba1401..ca549e770c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -40,7 +40,10 @@ public final class NavigationBarTransitions extends BarTransitions {
private int mRequestedMode;
public NavigationBarTransitions(NavigationBarView view) {
- super(view, R.drawable.nav_background);
+ super(view, R.drawable.nav_background, R.color.navigation_bar_background_opaque,
+ R.color.navigation_bar_background_semi_transparent,
+ R.color.navigation_bar_background_transparent,
+ com.android.internal.R.color.battery_saver_mode_color);
mView = view;
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 88e71e2cee7..1ab68eefb06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -73,6 +73,7 @@ public class NavigationBarView extends LinearLayout {
private Drawable mBackIcon, mBackLandIcon, mBackAltIcon, mBackAltLandIcon;
private Drawable mRecentIcon;
private Drawable mRecentLandIcon;
+ private Drawable mHomeIcon, mHomeLandIcon;
private NavigationBarViewTaskSwitchHelper mTaskSwitchHelper;
private DelegateViewHelper mDelegateHelper;
@@ -86,6 +87,8 @@ public class NavigationBarView extends LinearLayout {
// performs manual animation in sync with layout transitions
private final NavTransitionListener mTransitionListener = new NavTransitionListener();
+ private Resources mThemedResources;
+
private OnVerticalChangedListener mOnVerticalChangedListener;
private boolean mIsLayoutRtl;
private boolean mDelegateIntercepted;
@@ -273,11 +276,44 @@ public class NavigationBarView extends LinearLayout {
mBackAltLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime);
mRecentIcon = res.getDrawable(R.drawable.ic_sysbar_recent);
mRecentLandIcon = res.getDrawable(R.drawable.ic_sysbar_recent_land);
+ mHomeIcon = res.getDrawable(R.drawable.ic_sysbar_home);
+ mHomeLandIcon = res.getDrawable(R.drawable.ic_sysbar_home_land);
+ }
+
+ public void updateResources(Resources res) {
+ mThemedResources = res;
+ getIcons(mThemedResources);
+ mBarTransitions.updateResources(res);
+ for (int i = 0; i < mRotatedViews.length; i++) {
+ ViewGroup container = (ViewGroup) mRotatedViews[i];
+ if (container != null) {
+ updateLightsOutResources(container);
+ }
+ }
+ }
+
+ private void updateLightsOutResources(ViewGroup container) {
+ ViewGroup lightsOut = (ViewGroup) container.findViewById(R.id.lights_out);
+ if (lightsOut != null) {
+ final int nChildren = lightsOut.getChildCount();
+ for (int i = 0; i < nChildren; i++) {
+ final View child = lightsOut.getChildAt(i);
+ if (child instanceof ImageView) {
+ final ImageView iv = (ImageView) child;
+ // clear out the existing drawable, this is required since the
+ // ImageView keeps track of the resource ID and if it is the same
+ // it will not update the drawable.
+ iv.setImageDrawable(null);
+ iv.setImageDrawable(mThemedResources.getDrawable(
+ R.drawable.ic_sysbar_lights_out_dot_large));
+ }
+ }
+ }
}
@Override
public void setLayoutDirection(int layoutDirection) {
- getIcons(getContext().getResources());
+ getIcons(mThemedResources != null ? mThemedResources : getContext().getResources());
super.setLayoutDirection(layoutDirection);
}
@@ -310,13 +346,13 @@ public class NavigationBarView extends LinearLayout {
: (mVertical ? mBackLandIcon : mBackIcon));
((ImageView)getRecentsButton()).setImageDrawable(mVertical ? mRecentLandIcon : mRecentIcon);
+ ((ImageView)getHomeButton()).setImageDrawable(mVertical ? mHomeLandIcon : mHomeIcon);
final boolean showImeButton = ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0);
getImeSwitchButton().setVisibility(showImeButton ? View.VISIBLE : View.INVISIBLE);
// Update menu button in case the IME state has changed.
setMenuVisibility(mShowMenu, true);
-
setDisabledFlags(mDisabledFlags, true);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 2497bd1265c..ec006ed78bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -41,11 +41,14 @@ import android.app.IActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.StatusBarManager;
+import android.app.WallpaperManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.content.res.ThemeConfig;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
@@ -56,7 +59,6 @@ import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
-import android.graphics.Xfermode;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.InputMethodService;
@@ -84,6 +86,7 @@ import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
+import android.util.Pair;
import android.view.Display;
import android.view.Gravity;
import android.view.HardwareCanvas;
@@ -100,7 +103,6 @@ import android.view.ViewStub;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
@@ -265,6 +267,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
Point mCurrentDisplaySize = new Point();
StatusBarWindowView mStatusBarWindow;
+ FrameLayout mStatusBarWindowContent;
PhoneStatusBarView mStatusBarView;
private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
private StatusBarWindowManager mStatusBarWindowManager;
@@ -320,6 +323,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
KeyguardIndicationController mKeyguardIndicationController;
private boolean mKeyguardFadingAway;
+ private boolean mKeyguardShowingMedia;
private long mKeyguardFadingAwayDelay;
private long mKeyguardFadingAwayDuration;
@@ -357,6 +361,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
int[] mAbsPos = new int[2];
ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
+ // last theme that was applied in order to detect theme change (as opposed
+ // to some other configuration change).
+ ThemeConfig mCurrentTheme;
+ private boolean mRecreating = false;
+
// for disabling the status bar
int mDisabled = 0;
@@ -587,6 +596,17 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
updateDisplaySize();
mScrimSrcModeEnabled = mContext.getResources().getBoolean(
R.bool.config_status_bar_scrim_behind_use_src);
+
+ ThemeConfig currentTheme = mContext.getResources().getConfiguration().themeConfig;
+ if (currentTheme != null) {
+ mCurrentTheme = (ThemeConfig)currentTheme.clone();
+ } else {
+ mCurrentTheme = ThemeConfig.getSystemTheme();
+ }
+
+ mStatusBarWindow = new StatusBarWindowView(mContext, null);
+ mStatusBarWindow.mService = this;
+
super.start(); // calls createAndAddWindows()
mMediaSessionManager
@@ -639,14 +659,13 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
if (isMSim()) {
- mStatusBarWindow = (StatusBarWindowView) View.inflate(context,
+ mStatusBarWindowContent = (FrameLayout) View.inflate(context,
R.layout.msim_super_status_bar, null);
} else {
- mStatusBarWindow = (StatusBarWindowView) View.inflate(context,
+ mStatusBarWindowContent = (FrameLayout) View.inflate(context,
R.layout.super_status_bar, null);
}
- mStatusBarWindow.mService = this;
- mStatusBarWindow.setOnTouchListener(new View.OnTouchListener() {
+ mStatusBarWindowContent.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
checkUserAutohide(v, event);
@@ -655,31 +674,32 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
animateCollapsePanels();
}
}
- return mStatusBarWindow.onTouchEvent(event);
+ return mStatusBarWindowContent.onTouchEvent(event);
}});
if (isMSim()) {
- mStatusBarView = (PhoneStatusBarView) mStatusBarWindow.findViewById(
+ mStatusBarView = (PhoneStatusBarView) mStatusBarWindowContent.findViewById(
R.id.msim_status_bar);
} else {
- mStatusBarView = (PhoneStatusBarView) mStatusBarWindow.findViewById(R.id.status_bar);
+ mStatusBarView = (PhoneStatusBarView) mStatusBarWindowContent.findViewById(
+ R.id.status_bar);
}
mStatusBarView.setBar(this);
PanelHolder holder;
if (isMSim()) {
- holder = (PanelHolder) mStatusBarWindow.findViewById(R.id.msim_panel_holder);
+ holder = (PanelHolder) mStatusBarWindowContent.findViewById(R.id.msim_panel_holder);
} else {
- holder = (PanelHolder) mStatusBarWindow.findViewById(R.id.panel_holder);
+ holder = (PanelHolder) mStatusBarWindowContent.findViewById(R.id.panel_holder);
}
mStatusBarView.setPanelHolder(holder);
- mNotificationPanel = (NotificationPanelView) mStatusBarWindow.findViewById(
+ mNotificationPanel = (NotificationPanelView) mStatusBarWindowContent.findViewById(
R.id.notification_panel);
mNotificationPanel.setStatusBar(this);
if (!ActivityManager.isHighEndGfx()) {
- mStatusBarWindow.setBackground(null);
+ mStatusBarWindowContent.setBackground(null);
mNotificationPanel.setBackground(new FastColorDrawable(context.getResources().getColor(
R.color.notification_panel_solid_background)));
}
@@ -700,9 +720,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
try {
boolean showNav = mWindowManagerService.hasNavigationBar();
if (DEBUG) Log.v(TAG, "hasNavigationBar=" + showNav);
- if (showNav) {
+ if (showNav && !mRecreating) {
mNavigationBarView =
(NavigationBarView) View.inflate(context, R.layout.navigation_bar, null);
+ mNavigationBarView.updateResources(getNavbarThemedResources());
mNavigationBarView.setDisabledFlags(mDisabled);
mNavigationBarView.setBar(this);
@@ -739,7 +760,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mNotificationIcons.setOverflowIndicator(mMoreIcon);
mStatusBarContents = (LinearLayout)mStatusBarView.findViewById(R.id.status_bar_contents);
- mStackScroller = (NotificationStackScrollLayout) mStatusBarWindow.findViewById(
+ mStackScroller = (NotificationStackScrollLayout) mStatusBarWindowContent.findViewById(
R.id.notification_stack_scroller);
mStackScroller.setLongPressListener(getNotificationLongClicker());
mStackScroller.setPhoneStatusBar(this);
@@ -768,26 +789,28 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mStackScroller.setDismissView(mDismissView);
mExpandedContents = mStackScroller;
- mBackdrop = (BackDropView) mStatusBarWindow.findViewById(R.id.backdrop);
+ mBackdrop = (BackDropView) mStatusBarWindowContent.findViewById(R.id.backdrop);
mBackdropFront = (ImageView) mBackdrop.findViewById(R.id.backdrop_front);
mBackdropBack = (ImageView) mBackdrop.findViewById(R.id.backdrop_back);
- ScrimView scrimBehind = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_behind);
- ScrimView scrimInFront = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_in_front);
+ ScrimView scrimBehind = (ScrimView) mStatusBarWindowContent.findViewById(R.id.scrim_behind);
+ ScrimView scrimInFront =
+ (ScrimView) mStatusBarWindowContent.findViewById(R.id.scrim_in_front);
mScrimController = new ScrimController(scrimBehind, scrimInFront, mScrimSrcModeEnabled);
mScrimController.setBackDropView(mBackdrop);
mStatusBarView.setScrimController(mScrimController);
- mHeader = (StatusBarHeaderView) mStatusBarWindow.findViewById(R.id.header);
+ mHeader = (StatusBarHeaderView) mStatusBarWindowContent.findViewById(R.id.header);
mHeader.setActivityStarter(this);
- mKeyguardStatusBar = (KeyguardStatusBarView) mStatusBarWindow.findViewById(R.id.keyguard_header);
+ mKeyguardStatusBar =
+ (KeyguardStatusBarView) mStatusBarWindowContent.findViewById(R.id.keyguard_header);
mStatusIconsKeyguard = (LinearLayout) mKeyguardStatusBar.findViewById(R.id.statusIcons);
- mKeyguardStatusView = mStatusBarWindow.findViewById(R.id.keyguard_status_view);
- mKeyguardBottomArea =
- (KeyguardBottomAreaView) mStatusBarWindow.findViewById(R.id.keyguard_bottom_area);
+ mKeyguardStatusView = mStatusBarWindowContent.findViewById(R.id.keyguard_status_view);
+ mKeyguardBottomArea = (KeyguardBottomAreaView) mStatusBarWindowContent.findViewById(
+ R.id.keyguard_bottom_area);
mKeyguardBottomArea.setActivityStarter(this);
mKeyguardIndicationController = new KeyguardIndicationController(mContext,
- (KeyguardIndicationTextView) mStatusBarWindow.findViewById(
+ (KeyguardIndicationTextView) mStatusBarWindowContent.findViewById(
R.id.keyguard_indication_text));
mKeyguardBottomArea.setKeyguardIndicationController(mKeyguardIndicationController);
@@ -850,8 +873,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mMSimNetworkController.addEmergencyLabelView(mHeader);
- mCarrierLabel = (TextView)mStatusBarWindow.findViewById(R.id.carrier_label);
- mSubsLabel = (TextView)mStatusBarWindow.findViewById(R.id.subs_label);
+ mCarrierLabel = (TextView)mStatusBarWindowContent.findViewById(R.id.carrier_label);
+ mSubsLabel = (TextView)mStatusBarWindowContent.findViewById(R.id.subs_label);
mShowCarrierInPanel = (mCarrierLabel != null);
if (DEBUG) Log.v(TAG, "carrierlabel=" + mCarrierLabel + " show=" +
@@ -898,7 +921,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mNetworkController.addEmergencyLabelView(mHeader);
}
- mCarrierLabel = (TextView)mStatusBarWindow.findViewById(R.id.carrier_label);
+ mCarrierLabel = (TextView)mStatusBarWindowContent.findViewById(R.id.carrier_label);
mShowCarrierInPanel = (mCarrierLabel != null);
if (DEBUG) Log.v(TAG, "carrierlabel=" + mCarrierLabel + " show=" + mShowCarrierInPanel);
if (mShowCarrierInPanel) {
@@ -932,12 +955,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mUserSwitcherController = new UserSwitcherController(mContext, mKeyguardMonitor);
mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
- (ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_user_switcher),
+ (ViewStub) mStatusBarWindowContent.findViewById(R.id.keyguard_user_switcher),
mKeyguardStatusBar, mNotificationPanel, mUserSwitcherController);
// Set up the quick settings tile panel
- mQSPanel = (QSPanel) mStatusBarWindow.findViewById(R.id.quick_settings_panel);
+ mQSPanel = (QSPanel) mStatusBarWindowContent.findViewById(R.id.quick_settings_panel);
if (mQSPanel != null) {
final QSTileHost qsh;
if (isMSim()) {
@@ -957,7 +980,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
mQSPanel.setHost(qsh);
mQSPanel.setTiles(qsh.getTiles());
- mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow);
+ mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindowContent);
mQSPanel.setBrightnessMirror(mBrightnessMirrorController);
mHeader.setQSPanel(mQSPanel);
qsh.setCallback(new QSTileHost.Callback() {
@@ -971,7 +994,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// task manager
if (mContext.getResources().getBoolean(R.bool.config_showTaskManagerSwitcher)) {
mTaskManagerPanel =
- (LinearLayout) mStatusBarWindow.findViewById(R.id.task_manager_panel);
+ (LinearLayout) mStatusBarWindowContent.findViewById(R.id.task_manager_panel);
mTaskManager = new TaskManager(mContext, mTaskManagerPanel);
mTaskManager.setActivityStarter(this);
mTaskManagerButton = (ImageButton) mHeader.findViewById(R.id.task_manager_button);
@@ -1006,6 +1029,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
+ filter.addAction(Intent.ACTION_KEYGUARD_WALLPAPER_CHANGED);
if (DEBUG_MEDIA_FAKE_ARTWORK) {
filter.addAction("fake_artwork");
}
@@ -1272,6 +1296,13 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + mNavigationBarView);
if (mNavigationBarView == null) return;
+ ThemeConfig newTheme = mContext.getResources().getConfiguration().themeConfig;
+ if (newTheme != null &&
+ (mCurrentTheme == null || !mCurrentTheme.equals(newTheme))) {
+ // Nevermind, this will be re-created
+ return;
+ }
+
prepareNavigationBarView();
mWindowManager.addView(mNavigationBarView, getNavigationBarLayoutParams());
@@ -1311,6 +1342,18 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
return lp;
}
+ private Resources getNavbarThemedResources() {
+ String pkgName = mCurrentTheme.getOverlayPkgNameForApp(ThemeConfig.SYSTEMUI_NAVBAR_PKG);
+ Resources res = null;
+ try {
+ res = mContext.getPackageManager().getThemedResourcesForApplication(
+ mContext.getPackageName(), pkgName);
+ } catch (PackageManager.NameNotFoundException e) {
+ res = mContext.getResources();
+ }
+ return res;
+ }
+
private void addHeadsUpView() {
int headsUpHeight = mContext.getResources()
.getDimensionPixelSize(R.dimen.heads_up_window_height);
@@ -1334,7 +1377,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
private void removeHeadsUpView() {
- mWindowManager.removeView(mHeadsUpNotificationView);
+ if (mHeadsUpNotificationView != null && mHeadsUpNotificationView.isAttachedToWindow()) {
+ mWindowManager.removeView(mHeadsUpNotificationView);
+ }
}
public void refreshAllStatusBarIcons() {
@@ -1418,7 +1463,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
notification.getNotification().fullScreenIntent.send();
} catch (PendingIntent.CanceledException e) {
}
- } else {
+ } else if (!mRecreating) {
// usual case: status bar visible & not immersive
// show the ticker if there isn't already a heads up
@@ -1962,18 +2007,30 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
+ " state=" + mState);
}
- Bitmap artworkBitmap = null;
+ Bitmap backdropBitmap = null;
+
+ // apply any album artwork first
if (mMediaMetadata != null) {
- artworkBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
- if (artworkBitmap == null) {
- artworkBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+ backdropBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
+ if (backdropBitmap == null) {
+ backdropBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
// might still be null
}
}
- final boolean hasArtwork = artworkBitmap != null;
+ // apply user lockscreen image
+ if (backdropBitmap == null) {
+ WallpaperManager wm = (WallpaperManager)
+ mContext.getSystemService(Context.WALLPAPER_SERVICE);
+ if (wm != null) {
+ backdropBitmap = wm.getKeyguardBitmap();
+ }
+ }
+
+ final boolean hasBackdrop = backdropBitmap != null;
+ mKeyguardShowingMedia = hasBackdrop;
- if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK)
+ if ((hasBackdrop || DEBUG_MEDIA_FAKE_ARTWORK)
&& (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED)) {
// time to show some art!
if (mBackdrop.getVisibility() != View.VISIBLE) {
@@ -2003,7 +2060,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mBackdropBack.setBackgroundColor(0xFFFFFFFF);
mBackdropBack.setImageDrawable(new ColorDrawable(c));
} else {
- mBackdropBack.setImageBitmap(artworkBitmap);
+ mBackdropBack.setImageBitmap(backdropBitmap);
}
if (mScrimSrcModeEnabled) {
mBackdropBack.getDrawable().mutate().setXfermode(mSrcXferMode);
@@ -2237,6 +2294,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
return mLeaveOpenOnKeyguardHide;
}
+ public boolean isKeyguardShowingMedia() {
+ return mKeyguardShowingMedia;
+ }
+
public boolean isQsExpanded() {
return mNotificationPanel.isQsExpanded();
}
@@ -3106,6 +3167,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private void addStatusBarWindow() {
makeStatusBarView();
+ mStatusBarWindow.addContent(mStatusBarWindowContent);
mStatusBarWindowManager = new StatusBarWindowManager(mContext);
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}
@@ -3217,6 +3279,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (DEBUG_MEDIA_FAKE_ARTWORK) {
updateMediaMetaData(true);
}
+ } else if (Intent.ACTION_KEYGUARD_WALLPAPER_CHANGED.equals(action)) {
+ updateMediaMetaData(true);
}
}
};
@@ -3308,6 +3372,119 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mHeadsUpNotificationView.dismiss();
}
+ private static void copyNotifications(ArrayList<Pair<String, StatusBarNotification>> dest,
+ NotificationData source) {
+ int N = source.size();
+ for (int i = 0; i < N; i++) {
+ NotificationData.Entry entry = source.get(i);
+ dest.add(Pair.create(entry.key, entry.notification));
+ }
+ }
+
+ private void recreateStatusBar() {
+ mRecreating = true;
+
+ if (mMSimNetworkController != null) {
+ mMSimNetworkController.clearSubsLabelView();
+ mContext.unregisterReceiver(mMSimNetworkController);
+ } else if (mNetworkController != null) {
+ mContext.unregisterReceiver(mNetworkController);
+ }
+
+ removeHeadsUpView();
+
+ mStatusBarWindow.removeContent(mStatusBarWindowContent);
+ mStatusBarWindow.clearDisappearingChildren();
+
+ // extract icons from the soon-to-be recreated viewgroup.
+ int nIcons = mStatusIcons != null ? mStatusIcons.getChildCount() : 0;
+ ArrayList<StatusBarIcon> icons = new ArrayList<StatusBarIcon>(nIcons);
+ ArrayList<String> iconSlots = new ArrayList<String>(nIcons);
+ RankingMap rankingMap = mNotificationData.getRankingMap();
+ for (int i = 0; i < nIcons; i++) {
+ StatusBarIconView iconView = (StatusBarIconView)mStatusIcons.getChildAt(i);
+ icons.add(iconView.getStatusBarIcon());
+ iconSlots.add(iconView.getStatusBarSlot());
+ }
+
+ removeAllViews(mStatusBarWindowContent);
+
+ // extract notifications.
+ int nNotifs = mNotificationData.size();
+ ArrayList<Pair<String, StatusBarNotification>> notifications =
+ new ArrayList<Pair<String, StatusBarNotification>>(nNotifs);
+ copyNotifications(notifications, mNotificationData);
+ mNotificationData.clear();
+
+ // Halts the old ticker. A new ticker is created in makeStatusBarView() so
+ // this MUST happen before makeStatusBarView();
+ haltTicker();
+
+ makeStatusBarView();
+ repositionNavigationBar();
+ addHeadsUpView();
+ if (mNavigationBarView != null) {
+ mNavigationBarView.updateResources(getNavbarThemedResources());
+ }
+
+ // recreate StatusBarIconViews.
+ for (int i = 0; i < nIcons; i++) {
+ StatusBarIcon icon = icons.get(i);
+ String slot = iconSlots.get(i);
+ addIcon(slot, i, i, icon);
+ }
+
+ // recreate notifications.
+ for (int i = 0; i < nNotifs; i++) {
+ Pair<String, StatusBarNotification> notifData = notifications.get(i);
+ addNotificationViews(createNotificationViews(notifData.second), rankingMap);
+ }
+ mNotificationData.filterAndSort();
+
+ setAreThereNotifications();
+
+ mStatusBarWindow.addContent(mStatusBarWindowContent);
+
+ updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
+ checkBarModes();
+
+ // Stop the command queue until the new status bar container settles and has a layout pass
+ mCommandQueue.pause();
+ mStatusBarWindow.requestLayout();
+ mStatusBarWindow.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mStatusBarWindow.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ mCommandQueue.resume();
+ mRecreating = false;
+ }
+ });
+ // restart the keyguard so it picks up the newly created ScrimController
+ startKeyguard();
+
+ // if the keyguard was showing while this change occurred we'll need to do some extra work
+ if (mState == StatusBarState.KEYGUARD) {
+ // this will make sure the keyguard is showing
+ showKeyguard();
+ // The following views need to be invisible if the keyguard is showing
+ // These views were hidden but re-inflating the status bar changed them back to visible
+ mNotificationIconArea.setVisibility(View.INVISIBLE);
+ mSystemIconArea.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ private void removeAllViews(ViewGroup parent) {
+ int N = parent.getChildCount();
+ for (int i = 0; i < N; i++) {
+ View child = parent.getChildAt(i);
+ if (child instanceof ViewGroup) {
+ removeAllViews((ViewGroup) child);
+ }
+ }
+ parent.removeAllViews();
+ }
+
/**
* Reload some of our resources when the configuration changes.
*
@@ -3316,6 +3493,19 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
* meantime, just update the things that we know change.
*/
void updateResources() {
+ final Context context = mContext;
+ final Resources res = context.getResources();
+
+ // detect theme change.
+ ThemeConfig newTheme = res.getConfiguration().themeConfig;
+ if (newTheme != null &&
+ (mCurrentTheme == null || !mCurrentTheme.equals(newTheme))) {
+ mCurrentTheme = (ThemeConfig)newTheme.clone();
+ recreateStatusBar();
+ } else {
+ loadDimens();
+ }
+
// Update the quick setting tiles
if (mQSPanel != null) {
mQSPanel.updateResources();
@@ -3334,6 +3524,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (mBrightnessMirrorController != null) {
mBrightnessMirrorController.updateResources();
}
+
+ if (mNavigationBarView != null) {
+ mNavigationBarView.updateResources(getNavbarThemedResources());
+ updateSearchPanel();
+ }
}
private void updateClockSize() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
index 875c0619618..7c281a32c0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
@@ -37,7 +37,10 @@ public final class PhoneStatusBarTransitions extends BarTransitions {
private Animator mCurrentAnimation;
public PhoneStatusBarTransitions(PhoneStatusBarView view) {
- super(view, R.drawable.status_background);
+ super(view, R.drawable.status_background, R.color.status_bar_background_opaque,
+ R.color.status_bar_background_semi_transparent,
+ R.color.status_bar_background_transparent,
+ com.android.internal.R.color.battery_saver_mode_color);
mView = view;
final Resources res = mView.getContext().getResources();
mIconAlphaWhenOpaque = res.getFraction(R.dimen.status_bar_icon_drawing_alpha, 1, 1);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index a047dc85af5..240eecbe6b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -423,4 +423,8 @@ public class StatusBarKeyguardViewManager {
public boolean isGoingToNotificationShade() {
return mPhoneStatusBar.isGoingToNotificationShade();
}
+
+ public boolean isKeyguardShowingMedia() {
+ return mPhoneStatusBar.isKeyguardShowingMedia();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index 917f79d6f95..75304df2ba2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -84,7 +84,6 @@ public class StatusBarWindowManager {
* @param barHeight The height of the status bar in collapsed state.
*/
public void add(View statusBarView, int barHeight) {
-
// Now that the status bar window encompasses the sliding panel and its
// translucent backdrop, the entire thing is made TRANSLUCENT and is
// hardware-accelerated.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index f0c599d044e..d80694aa4eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -33,7 +33,6 @@ import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.widget.FrameLayout;
-
import com.android.systemui.R;
import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.DragDownHelper;
@@ -89,12 +88,6 @@ public class StatusBarWindowView extends FrameLayout {
protected void onAttachedToWindow () {
super.onAttachedToWindow();
- mStackScrollLayout = (NotificationStackScrollLayout) findViewById(
- R.id.notification_stack_scroller);
- mNotificationPanel = (NotificationPanelView) findViewById(R.id.notification_panel);
- mDragDownHelper = new DragDownHelper(getContext(), this, mStackScrollLayout, mService);
- mBrightnessMirror = findViewById(R.id.brightness_mirror);
-
// We really need to be able to animate while window animations are going on
// so that activities may be started asynchronously from panel animations
final ViewRootImpl root = getViewRootImpl();
@@ -241,5 +234,19 @@ public class StatusBarWindowView extends FrameLayout {
mStackScrollLayout.cancelExpandHelper();
}
}
+
+ public void addContent(View content) {
+ addView(content);
+ mStackScrollLayout = (NotificationStackScrollLayout) content.findViewById(
+ R.id.notification_stack_scroller);
+ mNotificationPanel = (NotificationPanelView) content.findViewById(R.id.notification_panel);
+ mDragDownHelper = new DragDownHelper(getContext(), this, mStackScrollLayout, mService);
+ mBrightnessMirror = content.findViewById(R.id.brightness_mirror);
+
+ }
+
+ public void removeContent(View content) {
+ removeView(content);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 534539ee7ad..55ec624dc29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -39,7 +39,7 @@ public class BrightnessMirrorController {
private final View mPanelHolder;
private final int[] mInt2Cache = new int[2];
- public BrightnessMirrorController(StatusBarWindowView statusBarWindow) {
+ public BrightnessMirrorController(View statusBarWindow) {
mScrimBehind = (ScrimView) statusBarWindow.findViewById(R.id.scrim_behind);
mBrightnessMirror = statusBarWindow.findViewById(R.id.brightness_mirror);
if (isMSim()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MSimNetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MSimNetworkControllerImpl.java
index 8d4a51ed77a..83565894b0f 100755
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MSimNetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MSimNetworkControllerImpl.java
@@ -445,6 +445,10 @@ public class MSimNetworkControllerImpl extends NetworkControllerImpl {
mSubsLabelViews.add(v);
}
+ public void clearSubsLabelView() {
+ mSubsLabelViews.clear();
+ }
+
private void updateCarrierText(int sub) {
int textResId = 0;
if (mAirplaneMode) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 1f645426caf..7546cb7aeeb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -2,6 +2,7 @@ package com.android.systemui.volume;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.media.AudioManager;
@@ -62,6 +63,8 @@ public class VolumeUI extends SystemUI {
private VolumePanel mPanel;
private int mDismissDelay;
+ private Configuration mConfiguration;
+
@Override
public void start() {
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
@@ -73,16 +76,31 @@ public class VolumeUI extends SystemUI {
putComponent(VolumeComponent.class, mVolumeController);
updateController();
mContext.getContentResolver().registerContentObserver(SETTING_URI, false, mObserver);
+ mConfiguration = new Configuration(mContext.getResources().getConfiguration());
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
+
+ if (isThemeChange(newConfig)) {
+ initPanel();
+ }
+ mConfiguration.setTo(newConfig);
+
if (mPanel != null) {
mPanel.onConfigurationChanged(newConfig);
}
}
+ private boolean isThemeChange(Configuration newConfig) {
+ if (mConfiguration != null) {
+ int changes = mConfiguration.updateFrom(newConfig);
+ return (changes & ActivityInfo.CONFIG_THEME_RESOURCE) != 0;
+ }
+ return false;
+ }
+
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mPanel != null) {
diff --git a/policy/src/com/android/internal/policy/impl/GlobalActions.java b/policy/src/com/android/internal/policy/impl/GlobalActions.java
index 3a1b6f08578..7b503a4dc92 100755
--- a/policy/src/com/android/internal/policy/impl/GlobalActions.java
+++ b/policy/src/com/android/internal/policy/impl/GlobalActions.java
@@ -36,6 +36,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ThemeUtils;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.graphics.drawable.Drawable;
@@ -111,6 +112,7 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac
private final Context mContext;
private final WindowManagerFuncs mWindowManagerFuncs;
+ private Context mUiContext;
private final AudioManager mAudioManager;
private final IDreamManager mDreamManager;
@@ -148,6 +150,8 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac
filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
context.registerReceiver(mBroadcastReceiver, filter);
+ ThemeUtils.registerThemeChangeReceiver(context, mThemeChangeReceiver);
+
ConnectivityManager cm = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
@@ -173,7 +177,7 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac
public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
mKeyguardShowing = keyguardShowing;
mDeviceProvisioned = isDeviceProvisioned;
- if (mDialog != null) {
+ if (mDialog != null && mUiContext == null) {
mDialog.dismiss();
mDialog = null;
// Show delayed, so that the dismiss of the previous dialog completes
@@ -214,6 +218,14 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac
}
}
+ private Context getUiContext() {
+ if (mUiContext == null) {
+ mUiContext = ThemeUtils.createUiContext(mContext);
+ mUiContext.setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar);
+ }
+ return mUiContext != null ? mUiContext : mContext;
+ }
+
/**
* Create the global actions dialog.
* @return A new dialog.
@@ -335,12 +347,12 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac
mAdapter = new MyAdapter();
- AlertParams params = new AlertParams(mContext);
+ AlertParams params = new AlertParams(getUiContext());
params.mAdapter = mAdapter;
params.mOnClickListener = this;
params.mForceInverseBackground = true;
- GlobalActionsDialog dialog = new GlobalActionsDialog(mContext, params);
+ GlobalActionsDialog dialog = new GlobalActionsDialog(getUiContext(), params);
dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
dialog.getListView().setItemsCanFocus(true);
@@ -382,7 +394,7 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac
names[i++] = profile.getName();
}
- final AlertDialog.Builder ab = new AlertDialog.Builder(mContext);
+ final AlertDialog.Builder ab = new AlertDialog.Builder(getUiContext());
AlertDialog dialog = ab
.setTitle(R.string.global_action_choose_profile)
@@ -767,7 +779,8 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac
public View getView(int position, View convertView, ViewGroup parent) {
Action action = getItem(position);
- return action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
+ final Context context = getUiContext();
+ return action.create(context, convertView, parent, LayoutInflater.from(context));
}
}
@@ -1140,6 +1153,12 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac
}
};
+
+ private BroadcastReceiver mThemeChangeReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ mUiContext = null;
+ }
+ };
PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
@Override
public void onServiceStateChanged(ServiceState serviceState) {
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 90702b13e49..62396b7e75c 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -2329,9 +2329,12 @@ public class PhoneWindowManager implements WindowManagerPolicy {
@Override
- public Animation createForceHideWallpaperExitAnimation(boolean goingToNotificationShade) {
+ public Animation createForceHideWallpaperExitAnimation(boolean goingToNotificationShade,
+ boolean keyguardShowingMedia) {
if (goingToNotificationShade) {
return null;
+ } else if (keyguardShowingMedia) {
+ return AnimationUtils.loadAnimation(mContext, R.anim.lock_screen_wallpaper_exit_noop);
} else {
return AnimationUtils.loadAnimation(mContext, R.anim.lock_screen_wallpaper_exit);
}
diff --git a/services/core/java/com/android/server/ThemeService.java b/services/core/java/com/android/server/ThemeService.java
new file mode 100644
index 00000000000..6a62048b8f7
--- /dev/null
+++ b/services/core/java/com/android/server/ThemeService.java
@@ -0,0 +1,1161 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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.server;
+
+import android.Manifest;
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.WallpaperManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ThemeUtils;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.IThemeProcessingListener;
+import android.content.res.ThemeConfig;
+import android.content.res.IThemeChangeListener;
+import android.content.res.IThemeService;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.RingtoneManager;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.provider.ThemesContract;
+import android.provider.ThemesContract.MixnMatchColumns;
+import android.provider.ThemesContract.ThemesColumns;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.R;
+import com.android.internal.util.cm.ImageUtils;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import static android.content.pm.ThemeUtils.SYSTEM_THEME_PATH;
+import static android.content.pm.ThemeUtils.THEME_BOOTANIMATION_PATH;
+import static android.content.res.ThemeConfig.SYSTEM_DEFAULT;
+
+import java.util.List;
+
+/**
+ * {@hide}
+ */
+public class ThemeService extends IThemeService.Stub {
+ private static final String TAG = ThemeService.class.getName();
+
+ private static final boolean DEBUG = false;
+
+ private static final String GOOGLE_SETUPWIZARD_PACKAGE = "com.google.android.setupwizard";
+ private static final String CM_SETUPWIZARD_PACKAGE = "com.cyanogenmod.account";
+
+ private static final long MAX_ICON_CACHE_SIZE = 33554432L; // 32MB
+ private static final long PURGED_ICON_CACHE_SIZE = 25165824L; // 24 MB
+
+ // Defines a min and max compatible api level for themes on this system.
+ private static final int MIN_COMPATIBLE_VERSION = 21;
+
+ private HandlerThread mWorker;
+ private ThemeWorkerHandler mHandler;
+ private ResourceProcessingHandler mResourceProcessingHandler;
+ private Context mContext;
+ private PackageManager mPM;
+ private int mProgress;
+ private boolean mWallpaperChangedByUs = false;
+ private long mIconCacheSize = 0L;
+
+ private boolean mIsThemeApplying = false;
+
+ private final RemoteCallbackList<IThemeChangeListener> mClients =
+ new RemoteCallbackList<IThemeChangeListener>();
+
+ private final RemoteCallbackList<IThemeProcessingListener> mProcessingListeners =
+ new RemoteCallbackList<IThemeProcessingListener>();
+
+ final private ArrayList<String> mThemesToProcessQueue = new ArrayList<String>(0);
+
+ private class ThemeWorkerHandler extends Handler {
+ private static final int MESSAGE_CHANGE_THEME = 1;
+ private static final int MESSAGE_APPLY_DEFAULT_THEME = 2;
+
+ public ThemeWorkerHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_CHANGE_THEME:
+ final Map<String, String> componentMap = (Map<String, String>) msg.obj;
+ doApplyTheme(componentMap);
+ break;
+ case MESSAGE_APPLY_DEFAULT_THEME:
+ doApplyDefaultTheme();
+ break;
+ default:
+ Log.w(TAG, "Unknown message " + msg.what);
+ break;
+ }
+ }
+ }
+
+ private class ResourceProcessingHandler extends Handler {
+ private static final int MESSAGE_QUEUE_THEME_FOR_PROCESSING = 3;
+ private static final int MESSAGE_DEQUEUE_AND_PROCESS_THEME = 4;
+
+ public ResourceProcessingHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_QUEUE_THEME_FOR_PROCESSING:
+ String pkgName = (String) msg.obj;
+ synchronized (mThemesToProcessQueue) {
+ if (!mThemesToProcessQueue.contains(pkgName)) {
+ if (DEBUG) Log.d(TAG, "Adding " + pkgName + " for processing");
+ mThemesToProcessQueue.add(pkgName);
+ if (mThemesToProcessQueue.size() == 1) {
+ this.sendEmptyMessage(MESSAGE_DEQUEUE_AND_PROCESS_THEME);
+ }
+ }
+ }
+ break;
+ case MESSAGE_DEQUEUE_AND_PROCESS_THEME:
+ synchronized (mThemesToProcessQueue) {
+ pkgName = mThemesToProcessQueue.get(0);
+ }
+ if (pkgName != null) {
+ if (DEBUG) Log.d(TAG, "Processing " + pkgName);
+ String name;
+ try {
+ PackageInfo pi = mPM.getPackageInfo(pkgName, 0);
+ name = getThemeName(pi);
+ } catch (PackageManager.NameNotFoundException e) {
+ name = null;
+ }
+ if (name != null) {
+ int result = mPM.processThemeResources(pkgName);
+ if (result < 0) {
+ postFailedThemeInstallNotification(name);
+ }
+ sendThemeResourcesCachedBroadcast(pkgName, result);
+ }
+ synchronized (mThemesToProcessQueue) {
+ mThemesToProcessQueue.remove(0);
+ if (mThemesToProcessQueue.size() > 0 &&
+ !hasMessages(MESSAGE_DEQUEUE_AND_PROCESS_THEME)) {
+ this.sendEmptyMessage(MESSAGE_DEQUEUE_AND_PROCESS_THEME);
+ }
+ }
+ postFinishedProcessing(pkgName);
+ }
+ break;
+ default:
+ Log.w(TAG, "Unknown message " + msg.what);
+ break;
+ }
+ }
+ }
+
+ public ThemeService(Context context) {
+ super();
+ mContext = context;
+ mWorker = new HandlerThread("ThemeServiceWorker", Process.THREAD_PRIORITY_BACKGROUND);
+ mWorker.start();
+ mHandler = new ThemeWorkerHandler(mWorker.getLooper());
+ Log.i(TAG, "Spawned worker thread");
+
+ HandlerThread processingThread = new HandlerThread("ResourceProcessingThread",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ processingThread.start();
+ mResourceProcessingHandler =
+ new ResourceProcessingHandler(processingThread.getLooper());
+
+ // create the theme directory if it does not exist
+ ThemeUtils.createThemeDirIfNotExists();
+ ThemeUtils.createFontDirIfNotExists();
+ ThemeUtils.createAlarmDirIfNotExists();
+ ThemeUtils.createNotificationDirIfNotExists();
+ ThemeUtils.createRingtoneDirIfNotExists();
+ ThemeUtils.createIconCacheDirIfNotExists();
+ }
+
+ public void systemRunning() {
+ // listen for wallpaper changes
+ IntentFilter filter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
+ mContext.registerReceiver(mWallpaperChangeReceiver, filter);
+
+ mPM = mContext.getPackageManager();
+
+ processInstalledThemes();
+
+ if (!isThemeApiUpToDate()) {
+ Log.d(TAG, "The system has been upgraded to a theme new api, " +
+ "checking if currently set theme is compatible");
+ removeObsoleteThemeOverlayIfExists();
+ updateThemeApi();
+ }
+ }
+
+ private void removeObsoleteThemeOverlayIfExists() {
+ // Get the current overlay theme so we can see it it's overlay should be unapplied
+ final IActivityManager am = ActivityManagerNative.getDefault();
+ ThemeConfig config = null;
+ try {
+ if (am != null) {
+ config = am.getConfiguration().themeConfig;
+ } else {
+ Log.e(TAG, "ActivityManager getDefault() " +
+ "returned null, cannot remove obsolete theme");
+ }
+ } catch(RemoteException e) {
+ Log.e(TAG, "Failed to get the theme config ", e);
+ }
+ if (config == null) return; // No need to unapply a theme if one isn't set
+
+ // Populate the currentTheme map for the components we care about, we'll look
+ // at the compatibility of each pkg below.
+ HashMap<String, String> currentThemeMap = new HashMap<String, String>();
+ currentThemeMap.put(ThemesColumns.MODIFIES_STATUS_BAR, config.getOverlayForStatusBar());
+ currentThemeMap.put(ThemesColumns.MODIFIES_NAVIGATION_BAR,
+ config.getOverlayForNavBar());
+ currentThemeMap.put(ThemesColumns.MODIFIES_OVERLAYS, config.getOverlayPkgName());
+
+ // Look at each component's theme (that we care about at least) and check compatibility
+ // of the pkg with the system. If it is not compatible then we will add it to a theme
+ // change request.
+ Map<String, String> defaults = ThemeUtils.getDefaultComponents(mContext);
+ HashMap<String, String> changeThemeRequestMap = new HashMap<String, String>();
+ for(Map.Entry<String, String> entry : currentThemeMap.entrySet()) {
+ String component = entry.getKey();
+ String pkgName = entry.getValue();
+ String defaultPkg = defaults.get(component);
+
+ // Check that the default overlay theme is not currently set
+ if (defaultPkg.equals(pkgName)) {
+ Log.d(TAG, "Current overlay theme is same as default. " +
+ "Not doing anything for " + component);
+ continue;
+ }
+
+ // No need to unapply a system theme since it is always compatible
+ if (ThemeConfig.SYSTEM_DEFAULT.equals(pkgName)) {
+ Log.d(TAG, "Current overlay theme for "
+ + component + " was system. no need to unapply");
+ continue;
+ }
+
+ if (!isThemeCompatibleWithUpgradedApi(pkgName)) {
+ Log.d(TAG, pkgName + "is incompatible with latest theme api for component " +
+ component + ", Applying " + defaultPkg);
+ changeThemeRequestMap.put(component, pkgName);
+ }
+ }
+
+ // Now actually unapply the incompatible themes
+ if (!changeThemeRequestMap.isEmpty()) {
+ try {
+ requestThemeChange(changeThemeRequestMap);
+ } catch(RemoteException e) {
+ // This cannot happen
+ }
+ } else {
+ Log.d(TAG, "Current theme is compatible with the system. Not unapplying anything");
+ }
+ }
+
+ private boolean isThemeCompatibleWithUpgradedApi(String pkgName) {
+ // Note this function does not cover the case of a downgrade. That case is out of scope and
+ // would require predicting whether the future API levels will be compatible or not.
+ boolean compatible = false;
+ try {
+ PackageInfo pi = mPM.getPackageInfo(pkgName, 0);
+ Log.d(TAG, "Comparing theme target: " + pi.applicationInfo.targetSdkVersion +
+ "to " + android.os.Build.VERSION.SDK_INT);
+ compatible = pi.applicationInfo.targetSdkVersion >= MIN_COMPATIBLE_VERSION;
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Unable to get package info for " + pkgName, e);
+ }
+ return compatible;
+ }
+
+ private boolean isThemeApiUpToDate() {
+ // We can't be 100% sure its an upgrade. If the field is undefined it
+ // could have been a factory reset.
+ final ContentResolver resolver = mContext.getContentResolver();
+ int recordedApiLevel = android.os.Build.VERSION.SDK_INT;
+ try {
+ recordedApiLevel = Settings.Secure.getInt(resolver,
+ Settings.Secure.THEME_PREV_BOOT_API_LEVEL);
+ } catch (SettingNotFoundException e) {
+ recordedApiLevel = -1;
+ Log.d(TAG, "Previous api level not found. First time booting?");
+ }
+ Log.d(TAG, "Prev api level was: " + recordedApiLevel
+ + ", api is now: " + android.os.Build.VERSION.SDK_INT);
+
+ return recordedApiLevel == android.os.Build.VERSION.SDK_INT;
+ }
+
+ private void updateThemeApi() {
+ final ContentResolver resolver = mContext.getContentResolver();
+ boolean success = Settings.Secure.putInt(resolver,
+ Settings.Secure.THEME_PREV_BOOT_API_LEVEL, android.os.Build.VERSION.SDK_INT);
+ if (!success) {
+ Log.e(TAG, "Unable to store latest API level to secure settings");
+ }
+ }
+
+ private void doApplyTheme(Map<String, String> componentMap) {
+ synchronized(this) {
+ mProgress = 0;
+ }
+
+ if (componentMap == null || componentMap.size() == 0) {
+ postFinish(true, componentMap);
+ return;
+ }
+ mIsThemeApplying = true;
+
+ incrementProgress(5);
+
+ // TODO: provide progress updates that reflect the time needed for each component
+ final int progressIncrement = 75 / componentMap.size();
+
+ if (componentMap.containsKey(ThemesColumns.MODIFIES_ICONS)) {
+ if (!updateIcons(componentMap.get(ThemesColumns.MODIFIES_ICONS))) {
+ componentMap.remove(ThemesColumns.MODIFIES_ICONS);
+ }
+ incrementProgress(progressIncrement);
+ }
+
+ if (componentMap.containsKey(ThemesColumns.MODIFIES_LAUNCHER)) {
+ if (updateWallpaper(componentMap.get(ThemesColumns.MODIFIES_LAUNCHER))) {
+ mWallpaperChangedByUs = true;
+ } else {
+ componentMap.remove(ThemesColumns.MODIFIES_LAUNCHER);
+ }
+ incrementProgress(progressIncrement);
+ }
+
+ if (componentMap.containsKey(ThemesColumns.MODIFIES_LOCKSCREEN)) {
+ if (!updateLockscreen(componentMap.get(ThemesColumns.MODIFIES_LOCKSCREEN))) {
+ componentMap.remove(ThemesColumns.MODIFIES_LOCKSCREEN);
+ }
+ incrementProgress(progressIncrement);
+ }
+
+ if (componentMap.containsKey(ThemesColumns.MODIFIES_NOTIFICATIONS)) {
+ if (!updateNotifications(componentMap.get(ThemesColumns.MODIFIES_NOTIFICATIONS))) {
+ componentMap.remove(ThemesColumns.MODIFIES_NOTIFICATIONS);
+ }
+ incrementProgress(progressIncrement);
+ }
+
+ Environment.setUserRequired(false);
+ if (componentMap.containsKey(ThemesColumns.MODIFIES_ALARMS)) {
+ if (!updateAlarms(componentMap.get(ThemesColumns.MODIFIES_ALARMS))) {
+ componentMap.remove(ThemesColumns.MODIFIES_ALARMS);
+ }
+ incrementProgress(progressIncrement);
+ }
+
+ if (componentMap.containsKey(ThemesColumns.MODIFIES_RINGTONES)) {
+ if (!updateRingtones(componentMap.get(ThemesColumns.MODIFIES_RINGTONES))) {
+ componentMap.remove(ThemesColumns.MODIFIES_RINGTONES);
+ }
+ incrementProgress(progressIncrement);
+ }
+
+ if (componentMap.containsKey(ThemesColumns.MODIFIES_BOOT_ANIM)) {
+ if (!updateBootAnim(componentMap.get(ThemesColumns.MODIFIES_BOOT_ANIM))) {
+ componentMap.remove(ThemesColumns.MODIFIES_BOOT_ANIM);
+ }
+ incrementProgress(progressIncrement);
+ }
+ Environment.setUserRequired(true);
+
+ if (componentMap.containsKey(ThemesColumns.MODIFIES_FONTS)) {
+ if (!updateFonts(componentMap.get(ThemesColumns.MODIFIES_FONTS))) {
+ componentMap.remove(ThemesColumns.MODIFIES_FONTS);
+ }
+ incrementProgress(progressIncrement);
+ }
+
+ updateProvider(componentMap);
+
+ updateConfiguration(componentMap);
+
+ killLaunchers(componentMap);
+
+ postFinish(true, componentMap);
+ mIsThemeApplying = false;
+ }
+
+ private void doApplyDefaultTheme() {
+ final ContentResolver resolver = mContext.getContentResolver();
+ final String defaultThemePkg = Settings.Secure.getString(resolver,
+ Settings.Secure.DEFAULT_THEME_PACKAGE);
+ if (!TextUtils.isEmpty(defaultThemePkg)) {
+ String defaultThemeComponents = Settings.Secure.getString(resolver,
+ Settings.Secure.DEFAULT_THEME_COMPONENTS);
+ List<String> components;
+ if (TextUtils.isEmpty(defaultThemeComponents)) {
+ components = ThemeUtils.getAllComponents();
+ } else {
+ components = new ArrayList<String>(
+ Arrays.asList(defaultThemeComponents.split("\\|")));
+ }
+ Map<String, String> componentMap = new HashMap<String, String>(components.size());
+ for (String component : components) {
+ componentMap.put(component, defaultThemePkg);
+ }
+ try {
+ requestThemeChange(componentMap);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to set default theme", e);
+ }
+ }
+ }
+
+ private void updateProvider(Map<String, String> componentMap) {
+ ContentValues values = new ContentValues();
+
+ for (String component : componentMap.keySet()) {
+ values.put(ThemesContract.MixnMatchColumns.COL_VALUE, componentMap.get(component));
+ String where = ThemesContract.MixnMatchColumns.COL_KEY + "=?";
+ String[] selectionArgs = { MixnMatchColumns.componentToMixNMatchKey(component) };
+ if (selectionArgs[0] == null) {
+ continue; // No equivalence between mixnmatch and theme
+ }
+ mContext.getContentResolver().update(MixnMatchColumns.CONTENT_URI, values, where,
+ selectionArgs);
+ }
+ }
+
+ private boolean updateIcons(String pkgName) {
+ try {
+ if (pkgName.equals(SYSTEM_DEFAULT)) {
+ mPM.updateIconMaps(null);
+ } else {
+ mPM.updateIconMaps(pkgName);
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Changing icons failed", e);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean updateFonts(String pkgName) {
+ //Clear the font dir
+ FileUtils.deleteContents(new File(ThemeUtils.SYSTEM_THEME_FONT_PATH));
+
+ if (!pkgName.equals(SYSTEM_DEFAULT)) {
+ //Get Font Assets
+ Context themeCtx;
+ String[] assetList;
+ try {
+ themeCtx = mContext.createPackageContext(pkgName, Context.CONTEXT_IGNORE_SECURITY);
+ AssetManager assetManager = themeCtx.getAssets();
+ assetList = assetManager.list("fonts");
+ } catch (Exception e) {
+ Log.e(TAG, "There was an error getting assets for pkg " + pkgName, e);
+ return false;
+ }
+ if (assetList == null || assetList.length == 0) {
+ Log.e(TAG, "Could not find any font assets");
+ return false;
+ }
+
+ //Copy font assets to font dir
+ for(String asset : assetList) {
+ InputStream is = null;
+ OutputStream os = null;
+ try {
+ is = ThemeUtils.getInputStreamFromAsset(themeCtx,
+ "file:///android_asset/fonts/" + asset);
+ File outFile = new File(ThemeUtils.SYSTEM_THEME_FONT_PATH, asset);
+ FileUtils.copyToFile(is, outFile);
+ FileUtils.setPermissions(outFile,
+ FileUtils.S_IRWXU|FileUtils.S_IRGRP|FileUtils.S_IRWXO, -1, -1);
+ } catch (Exception e) {
+ Log.e(TAG, "There was an error installing the new fonts for pkg " + pkgName, e);
+ return false;
+ } finally {
+ ThemeUtils.closeQuietly(is);
+ ThemeUtils.closeQuietly(os);
+ }
+ }
+ }
+
+ //Notify zygote that themes need a refresh
+ SystemProperties.set("sys.refresh_theme", "1");
+ return true;
+ }
+
+ private boolean updateBootAnim(String pkgName) {
+ if (SYSTEM_DEFAULT.equals(pkgName)) {
+ clearBootAnimation();
+ return true;
+ }
+
+ try {
+ final ApplicationInfo ai = mPM.getApplicationInfo(pkgName, 0);
+ applyBootAnimation(ai.sourceDir);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Changing boot animation failed", e);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean updateAlarms(String pkgName) {
+ return updateAudible(ThemeUtils.SYSTEM_THEME_ALARM_PATH, "alarms",
+ RingtoneManager.TYPE_ALARM, pkgName);
+ }
+
+ private boolean updateNotifications(String pkgName) {
+ return updateAudible(ThemeUtils.SYSTEM_THEME_NOTIFICATION_PATH, "notifications",
+ RingtoneManager.TYPE_NOTIFICATION, pkgName);
+ }
+
+ private boolean updateRingtones(String pkgName) {
+ return updateAudible(ThemeUtils.SYSTEM_THEME_RINGTONE_PATH, "ringtones",
+ RingtoneManager.TYPE_RINGTONE, pkgName);
+ }
+
+ private boolean updateAudible(String dirPath, String assetPath, int type, String pkgName) {
+ //Clear the dir
+ ThemeUtils.clearAudibles(mContext, dirPath);
+ if (pkgName.equals(SYSTEM_DEFAULT)) {
+ if (!ThemeUtils.setDefaultAudible(mContext, type)) {
+ Log.e(TAG, "There was an error installing the default audio file");
+ return false;
+ }
+ return true;
+ }
+
+ PackageInfo pi = null;
+ try {
+ pi = mPM.getPackageInfo(pkgName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Unable to update audible " + dirPath, e);
+ return false;
+ }
+
+ //Get theme Assets
+ Context themeCtx;
+ String[] assetList;
+ try {
+ themeCtx = mContext.createPackageContext(pkgName, Context.CONTEXT_IGNORE_SECURITY);
+ AssetManager assetManager = themeCtx.getAssets();
+ assetList = assetManager.list(assetPath);
+ } catch (Exception e) {
+ Log.e(TAG, "There was an error getting assets for pkg " + pkgName, e);
+ return false;
+ }
+ if (assetList == null || assetList.length == 0) {
+ Log.e(TAG, "Could not find any audio assets");
+ return false;
+ }
+
+ // TODO: right now we just load the first file but this will need to be changed
+ // in the future if multiple audio files are supported.
+ final String asset = assetList[0];
+ if (!ThemeUtils.isValidAudible(asset)) return false;
+
+ InputStream is = null;
+ OutputStream os = null;
+ try {
+ is = ThemeUtils.getInputStreamFromAsset(themeCtx, "file:///android_asset/"
+ + assetPath + File.separator + asset);
+ File outFile = new File(dirPath, asset);
+ FileUtils.copyToFile(is, outFile);
+ FileUtils.setPermissions(outFile,
+ FileUtils.S_IRWXU|FileUtils.S_IRGRP|FileUtils.S_IRWXO,-1, -1);
+ ThemeUtils.setAudible(mContext, outFile, type, pi.themeInfo.name);
+ } catch (Exception e) {
+ Log.e(TAG, "There was an error installing the new audio file for pkg " + pkgName, e);
+ return false;
+ } finally {
+ ThemeUtils.closeQuietly(is);
+ ThemeUtils.closeQuietly(os);
+ }
+ return true;
+ }
+
+ private boolean updateLockscreen(String pkgName) {
+ boolean success;
+ success = setCustomLockScreenWallpaper(pkgName);
+
+ if (success) {
+ mContext.sendBroadcastAsUser(new Intent(Intent.ACTION_KEYGUARD_WALLPAPER_CHANGED),
+ UserHandle.ALL);
+ }
+ return success;
+ }
+
+ private boolean setCustomLockScreenWallpaper(String pkgName) {
+ WallpaperManager wm = WallpaperManager.getInstance(mContext);
+ try {
+ if (SYSTEM_DEFAULT.equals(pkgName) || TextUtils.isEmpty(pkgName)) {
+ wm.clearKeyguardWallpaper();
+ } else {
+ InputStream in = ImageUtils.getCroppedKeyguardStream(pkgName, mContext);
+ if (in != null) {
+ wm.setKeyguardStream(in);
+ ThemeUtils.closeQuietly(in);
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "There was an error setting lockscreen wp for pkg " + pkgName, e);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean updateWallpaper(String pkgName) {
+ String selection = ThemesColumns.PKG_NAME + "= ?";
+ String[] selectionArgs = { pkgName };
+ Cursor c = mContext.getContentResolver().query(ThemesColumns.CONTENT_URI,
+ null, selection,
+ selectionArgs, null);
+ c.moveToFirst();
+ WallpaperManager wm = WallpaperManager.getInstance(mContext);
+ if (SYSTEM_DEFAULT.equals(pkgName)) {
+ try {
+ wm.clear();
+ } catch (IOException e) {
+ return false;
+ } finally {
+ c.close();
+ }
+ } else if (TextUtils.isEmpty(pkgName)) {
+ try {
+ wm.clear(false);
+ } catch (IOException e) {
+ return false;
+ } finally {
+ c.close();
+ }
+ } else {
+ InputStream in = null;
+ try {
+ in = ImageUtils.getCroppedWallpaperStream(pkgName, mContext);
+ if (in != null)
+ wm.setStream(in);
+ } catch (Exception e) {
+ return false;
+ } finally {
+ ThemeUtils.closeQuietly(in);
+ c.close();
+ }
+ }
+ return true;
+ }
+
+ private boolean updateConfiguration(Map<String, String> components) {
+ final IActivityManager am = ActivityManagerNative.getDefault();
+ if (am != null) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ Configuration config = am.getConfiguration();
+ ThemeConfig.Builder themeBuilder = createBuilderFrom(config, components, null);
+ ThemeConfig newConfig = themeBuilder.build();
+
+ // If this is a theme upgrade then new config equals existing config. The result
+ // is that the config is not considered changed and therefore not propagated,
+ // which can be problem if the APK path changes (ex theme-1.apk -> theme-2.apk)
+ if (newConfig.equals(config.themeConfig)) {
+ // We can't just use null for the themeConfig, it won't be registered as
+ // a changed config value because of the way equals in config had to be written.
+ final String defaultThemePkg =
+ Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.DEFAULT_THEME_PACKAGE);
+ ThemeConfig.Builder defaultBuilder =
+ createBuilderFrom(config, components, defaultThemePkg);
+ config.themeConfig = defaultBuilder.build();
+ am.updateConfiguration(config);
+ }
+
+ config.themeConfig = newConfig;
+ am.updateConfiguration(config);
+ } catch (RemoteException e) {
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ return true;
+ }
+
+ private static ThemeConfig.Builder createBuilderFrom(Configuration config,
+ Map<String, String> componentMap, String pkgName) {
+ ThemeConfig.Builder builder = new ThemeConfig.Builder(config.themeConfig);
+
+ if (componentMap.containsKey(ThemesColumns.MODIFIES_ICONS)) {
+ builder.defaultIcon(pkgName == null ?
+ componentMap.get(ThemesColumns.MODIFIES_ICONS) : pkgName);
+ }
+
+ if (componentMap.containsKey(ThemesColumns.MODIFIES_OVERLAYS)) {
+ builder.defaultOverlay(pkgName == null ?
+ componentMap.get(ThemesColumns.MODIFIES_OVERLAYS) : pkgName);
+ }
+
+ if (componentMap.containsKey(ThemesColumns.MODIFIES_FONTS)) {
+ builder.defaultFont(pkgName == null ?
+ componentMap.get(ThemesColumns.MODIFIES_FONTS) : pkgName);
+ }
+
+ if (componentMap.containsKey(ThemesColumns.MODIFIES_STATUS_BAR)) {
+ builder.overlay(ThemeConfig.SYSTEMUI_STATUS_BAR_PKG, pkgName == null ?
+ componentMap.get(ThemesColumns.MODIFIES_STATUS_BAR) : pkgName);
+ }
+
+ if (componentMap.containsKey(ThemesColumns.MODIFIES_NAVIGATION_BAR)) {
+ builder.overlay(ThemeConfig.SYSTEMUI_NAVBAR_PKG, pkgName == null ?
+ componentMap.get(ThemesColumns.MODIFIES_NAVIGATION_BAR) : pkgName);
+ }
+
+ return builder;
+ }
+
+ // Kill the current Home process, they tend to be evil and cache
+ // drawable references in all apps
+ private void killLaunchers(Map<String, String> componentMap) {
+ if (!(componentMap.containsKey(ThemesColumns.MODIFIES_ICONS)
+ || componentMap.containsKey(ThemesColumns.MODIFIES_OVERLAYS))) {
+ return;
+ }
+
+ final ActivityManager am =
+ (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+
+ Intent homeIntent = new Intent();
+ homeIntent.setAction(Intent.ACTION_MAIN);
+ homeIntent.addCategory(Intent.CATEGORY_HOME);
+
+ List<ResolveInfo> infos = mPM.queryIntentActivities(homeIntent, 0);
+ List<ResolveInfo> themeChangeInfos = mPM.queryBroadcastReceivers(
+ new Intent(ThemeUtils.ACTION_THEME_CHANGED), 0);
+ for(ResolveInfo info : infos) {
+ if (info.activityInfo != null && info.activityInfo.applicationInfo != null &&
+ !isSetupActivity(info) && !handlesThemeChanges(
+ info.activityInfo.applicationInfo.packageName, themeChangeInfos)) {
+ String pkgToStop = info.activityInfo.applicationInfo.packageName;
+ Log.d(TAG, "Force stopping " + pkgToStop + " for theme change");
+ try {
+ am.forceStopPackage(pkgToStop);
+ } catch(Exception e) {
+ Log.e(TAG, "Unable to force stop package, did you forget platform signature?",
+ e);
+ }
+ }
+ }
+ }
+
+ private boolean isSetupActivity(ResolveInfo info) {
+ return GOOGLE_SETUPWIZARD_PACKAGE.equals(info.activityInfo.packageName) ||
+ CM_SETUPWIZARD_PACKAGE.equals(info.activityInfo.packageName);
+ }
+
+ private boolean handlesThemeChanges(String pkgName, List<ResolveInfo> infos) {
+ if (infos != null && infos.size() > 0) {
+ for (ResolveInfo info : infos) {
+ if (info.activityInfo.applicationInfo.packageName.equals(pkgName)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void postProgress() {
+ int N = mClients.beginBroadcast();
+ for(int i=0; i < N; i++) {
+ IThemeChangeListener listener = mClients.getBroadcastItem(0);
+ try {
+ listener.onProgress(mProgress);
+ } catch(RemoteException e) {
+ Log.w(TAG, "Unable to post progress to client listener", e);
+ }
+ }
+ mClients.finishBroadcast();
+ }
+
+ private void postFinish(boolean isSuccess, Map<String, String> componentMap) {
+ synchronized(this) {
+ mProgress = 0;
+ }
+
+ int N = mClients.beginBroadcast();
+ for(int i=0; i < N; i++) {
+ IThemeChangeListener listener = mClients.getBroadcastItem(0);
+ try {
+ listener.onFinish(isSuccess);
+ } catch(RemoteException e) {
+ Log.w(TAG, "Unable to post progress to client listener", e);
+ }
+ }
+ mClients.finishBroadcast();
+
+ // if successful, broadcast that the theme changed
+ if (isSuccess) {
+ broadcastThemeChange(componentMap);
+ }
+ }
+
+ private void postFinishedProcessing(String pkgName) {
+ int N = mProcessingListeners.beginBroadcast();
+ for(int i=0; i < N; i++) {
+ IThemeProcessingListener listener = mProcessingListeners.getBroadcastItem(0);
+ try {
+ listener.onFinishedProcessing(pkgName);
+ } catch(RemoteException e) {
+ Log.w(TAG, "Unable to post progress to listener", e);
+ }
+ }
+ mProcessingListeners.finishBroadcast();
+ }
+
+ private void broadcastThemeChange(Map<String, String> components) {
+ final Intent intent = new Intent(ThemeUtils.ACTION_THEME_CHANGED);
+ ArrayList componentsArrayList = new ArrayList(components.keySet());
+ intent.putStringArrayListExtra("components", componentsArrayList);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void incrementProgress(int increment) {
+ synchronized(this) {
+ mProgress += increment;
+ if (mProgress > 100) mProgress = 100;
+ }
+ postProgress();
+ }
+
+ @Override
+ public void requestThemeChangeUpdates(IThemeChangeListener listener) throws RemoteException {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.ACCESS_THEME_MANAGER, null);
+ mClients.register(listener);
+ }
+
+ @Override
+ public void removeUpdates(IThemeChangeListener listener) throws RemoteException {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.ACCESS_THEME_MANAGER, null);
+ mClients.unregister(listener);
+ }
+
+ @Override
+ public void requestThemeChange(Map componentMap) throws RemoteException {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.ACCESS_THEME_MANAGER, null);
+ Message msg;
+
+ /**
+ * Since the ThemeService handles compiling theme resource we need to make sure that any
+ * of the components we are trying to apply are either already processed or put to the
+ * front of the queue and handled before the theme change takes place.
+ *
+ * TODO: create a callback that can be sent to any ThemeChangeListeners to notify them that
+ * the theme will be applied once the processing is done.
+ */
+ synchronized (mThemesToProcessQueue) {
+ for (Object key : componentMap.keySet()) {
+ if (ThemesColumns.MODIFIES_OVERLAYS.equals(key) ||
+ ThemesColumns.MODIFIES_NAVIGATION_BAR.equals(key) ||
+ ThemesColumns.MODIFIES_STATUS_BAR.equals(key) ||
+ ThemesColumns.MODIFIES_ICONS.equals(key)) {
+ String pkgName = (String) componentMap.get(key);
+ if (mThemesToProcessQueue.indexOf(pkgName) > 0) {
+ mThemesToProcessQueue.remove(pkgName);
+ mThemesToProcessQueue.add(0, pkgName);
+ // We want to make sure these resources are taken care of first so
+ // send the dequeue message and place it in the front of the queue
+ msg = mHandler.obtainMessage(
+ ResourceProcessingHandler.MESSAGE_DEQUEUE_AND_PROCESS_THEME);
+ mHandler.sendMessageAtFrontOfQueue(msg);
+ }
+ }
+ }
+ }
+ msg = Message.obtain();
+ msg.what = ThemeWorkerHandler.MESSAGE_CHANGE_THEME;
+ msg.obj = componentMap;
+ mHandler.sendMessage(msg);
+ }
+
+ @Override
+ public void applyDefaultTheme() {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.ACCESS_THEME_MANAGER, null);
+ Message msg = Message.obtain();
+ msg.what = ThemeWorkerHandler.MESSAGE_APPLY_DEFAULT_THEME;
+ mHandler.sendMessage(msg);
+ }
+
+ @Override
+ public boolean isThemeApplying() throws RemoteException {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_THEME_MANAGER, null);
+ return mIsThemeApplying;
+ }
+
+ @Override
+ public int getProgress() throws RemoteException {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_THEME_MANAGER, null);
+ synchronized(this) {
+ return mProgress;
+ }
+ }
+
+ @Override
+ public boolean cacheComposedIcon(Bitmap icon, String fileName) throws RemoteException {
+ final long token = Binder.clearCallingIdentity();
+ boolean success;
+ FileOutputStream os;
+ final File cacheDir = new File(ThemeUtils.SYSTEM_THEME_ICON_CACHE_DIR);
+ if (cacheDir.listFiles().length == 0) {
+ mIconCacheSize = 0;
+ }
+ try {
+ File outFile = new File(cacheDir, fileName);
+ os = new FileOutputStream(outFile);
+ icon.compress(Bitmap.CompressFormat.PNG, 90, os);
+ os.close();
+ FileUtils.setPermissions(outFile,
+ FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH,
+ -1, -1);
+ mIconCacheSize += outFile.length();
+ if (mIconCacheSize > MAX_ICON_CACHE_SIZE) {
+ purgeIconCache();
+ }
+ success = true;
+ } catch (Exception e) {
+ success = false;
+ Log.w(TAG, "Unable to cache icon " + fileName, e);
+ }
+ Binder.restoreCallingIdentity(token);
+ return success;
+ }
+
+ @Override
+ public boolean processThemeResources(String themePkgName) throws RemoteException {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.ACCESS_THEME_MANAGER, null);
+ try {
+ mPM.getPackageInfo(themePkgName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Package doesn't exist so nothing to process
+ return false;
+ }
+ // Obtain a message and send it to the handler to process this theme
+ Message msg = mResourceProcessingHandler.obtainMessage(
+ ResourceProcessingHandler.MESSAGE_QUEUE_THEME_FOR_PROCESSING, 0, 0, themePkgName);
+ mResourceProcessingHandler.sendMessage(msg);
+ return true;
+ }
+
+ @Override
+ public boolean isThemeBeingProcessed(String themePkgName) throws RemoteException {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.ACCESS_THEME_MANAGER, null);
+ synchronized (mThemesToProcessQueue) {
+ return mThemesToProcessQueue.contains(themePkgName);
+ }
+ }
+
+ @Override
+ public void registerThemeProcessingListener(IThemeProcessingListener listener)
+ throws RemoteException {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.ACCESS_THEME_MANAGER, null);
+ mProcessingListeners.register(listener);
+ }
+
+ @Override
+ public void unregisterThemeProcessingListener(IThemeProcessingListener listener)
+ throws RemoteException {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.ACCESS_THEME_MANAGER, null);
+ mProcessingListeners.unregister(listener);
+ }
+
+ private void purgeIconCache() {
+ Log.d(TAG, "Purging icon cahe of size " + mIconCacheSize);
+ File cacheDir = new File(ThemeUtils.SYSTEM_THEME_ICON_CACHE_DIR);
+ File[] files = cacheDir.listFiles();
+ Arrays.sort(files, mOldestFilesFirstComparator);
+ for (File f : files) {
+ if (!f.isDirectory()) {
+ final long size = f.length();
+ if(f.delete()) mIconCacheSize -= size;
+ }
+ if (mIconCacheSize <= PURGED_ICON_CACHE_SIZE) break;
+ }
+ }
+
+ private boolean applyBootAnimation(String themePath) {
+ boolean success = false;
+ try {
+ ZipFile zip = new ZipFile(new File(themePath));
+ ZipEntry ze = zip.getEntry(THEME_BOOTANIMATION_PATH);
+ if (ze != null) {
+ clearBootAnimation();
+ BufferedInputStream is = new BufferedInputStream(zip.getInputStream(ze));
+ final String bootAnimationPath = SYSTEM_THEME_PATH + File.separator
+ + "bootanimation.zip";
+ ThemeUtils.copyAndScaleBootAnimation(mContext, is, bootAnimationPath);
+ FileUtils.setPermissions(bootAnimationPath,
+ FileUtils.S_IRWXU|FileUtils.S_IRGRP|FileUtils.S_IROTH, -1, -1);
+ }
+ zip.close();
+ success = true;
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to load boot animation for " + themePath, e);
+ }
+
+ return success;
+ }
+
+ private void clearBootAnimation() {
+ File anim = new File(SYSTEM_THEME_PATH + File.separator + "bootanimation.zip");
+ if (anim.exists())
+ anim.delete();
+ }
+
+ private BroadcastReceiver mWallpaperChangeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!mWallpaperChangedByUs) {
+ // In case the mixnmatch table has a mods_launcher entry, we'll clear it
+ Map<String, String> components = new HashMap<String, String>(1);
+ components.put(ThemesColumns.MODIFIES_LAUNCHER, "");
+ updateProvider(components);
+ } else {
+ mWallpaperChangedByUs = false;
+ }
+ }
+ };
+
+ private Comparator<File> mOldestFilesFirstComparator = new Comparator<File>() {
+ @Override
+ public int compare(File lhs, File rhs) {
+ return (int) (lhs.lastModified() - rhs.lastModified());
+ }
+ };
+
+ private void processInstalledThemes() {
+ final String defaultTheme = ThemeUtils.getDefaultThemePackageName(mContext);
+ Message msg;
+ // Make sure the default theme is the first to get processed!
+ if (!ThemeConfig.SYSTEM_DEFAULT.equals(defaultTheme)) {
+ msg = mHandler.obtainMessage(
+ ResourceProcessingHandler.MESSAGE_QUEUE_THEME_FOR_PROCESSING,
+ 0, 0, defaultTheme);
+ mResourceProcessingHandler.sendMessage(msg);
+ }
+ // Iterate over all installed packages and queue up the ones that are themes or icon packs
+ List<PackageInfo> packages = mPM.getInstalledPackages(0);
+ for (PackageInfo info : packages) {
+ if (!defaultTheme.equals(info.packageName) &&
+ (info.isThemeApk || info.isLegacyIconPackApk)) {
+ msg = mHandler.obtainMessage(
+ ResourceProcessingHandler.MESSAGE_QUEUE_THEME_FOR_PROCESSING,
+ 0, 0, info.packageName);
+ mResourceProcessingHandler.sendMessage(msg);
+ }
+ }
+ }
+
+ private void sendThemeResourcesCachedBroadcast(String themePkgName, int resultCode) {
+ final Intent intent = new Intent(Intent.ACTION_THEME_RESOURCES_CACHED);
+ intent.putExtra(Intent.EXTRA_THEME_PACKAGE_NAME, themePkgName);
+ intent.putExtra(Intent.EXTRA_THEME_RESULT, resultCode);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ /**
+ * Posts a notification to let the user know the theme was not installed.
+ * @param name
+ */
+ private void postFailedThemeInstallNotification(String name) {
+ NotificationManager nm =
+ (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ Notification notice = new Notification.Builder(mContext)
+ .setAutoCancel(true)
+ .setOngoing(false)
+ .setContentTitle(
+ mContext.getString(R.string.theme_install_error_title))
+ .setContentText(String.format(mContext.getString(
+ R.string.theme_install_error_message),
+ name))
+ .setSmallIcon(android.R.drawable.stat_notify_error)
+ .setWhen(System.currentTimeMillis())
+ .build();
+ nm.notify(name.hashCode(), notice);
+ }
+
+ private String getThemeName(PackageInfo pi) {
+ if (pi.themeInfo != null) {
+ return pi.themeInfo.name;
+ } else if (pi.isLegacyIconPackApk) {
+ return pi.applicationInfo.name;
+ }
+
+ return null;
+ }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b6f8a8b7cdc..adda9769190 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2006-2008 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved.
* Not a Contribution.
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -44,6 +45,7 @@ import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.appwidget.AppWidgetManager;
import android.content.res.Resources;
+import android.content.res.ThemeConfig;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Rect;
@@ -56,6 +58,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseIntArray;
+import android.view.InflateException;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsService;
@@ -145,6 +148,7 @@ import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
+import android.content.pm.ThemeUtils;
import android.content.pm.UserInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PathPermission;
@@ -153,6 +157,7 @@ import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
+import android.content.res.ThemeConfig;
import android.net.Proxy;
import android.net.ProxyInfo;
import android.net.Uri;
@@ -363,6 +368,8 @@ public final class ActivityManagerService extends ActivityManagerNative
// How many bytes to write into the dropbox log before truncating
static final int DROPBOX_MAX_SIZE = 256 * 1024;
+ static final String PROP_REFRESH_THEME = "sys.refresh_theme";
+
// Access modes for handleIncomingUser.
static final int ALLOW_NON_FULL = 0;
static final int ALLOW_NON_FULL_IN_PROFILE = 1;
@@ -911,6 +918,7 @@ public final class ActivityManagerService extends ActivityManagerNative
boolean mLaunchWarningShown = false;
Context mContext;
+ Context mUiContext;
int mFactoryTest;
@@ -1272,7 +1280,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
mShowDialogs = false;
if (mShowDialogs && !mSleeping && !mShuttingDown) {
- Dialog d = new AppErrorDialog(mContext,
+ Dialog d = new AppErrorDialog(getUiContext(),
ActivityManagerService.this, res, proc);
d.show();
proc.crashDialog = d;
@@ -1307,7 +1315,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (mShowDialogs) {
Dialog d = new AppNotRespondingDialog(ActivityManagerService.this,
- mContext, proc, (ActivityRecord)data.get("activity"),
+ getUiContext(), proc, (ActivityRecord)data.get("activity"),
msg.arg1 != 0);
d.show();
proc.anrDialog = d;
@@ -1333,7 +1341,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
AppErrorResult res = (AppErrorResult) data.get("result");
if (mShowDialogs && !mSleeping && !mShuttingDown) {
- Dialog d = new StrictModeViolationDialog(mContext,
+ Dialog d = new StrictModeViolationDialog(getUiContext(),
ActivityManagerService.this, res, proc);
d.show();
proc.crashDialog = d;
@@ -1347,7 +1355,7 @@ public final class ActivityManagerService extends ActivityManagerNative
} break;
case SHOW_FACTORY_ERROR_MSG: {
Dialog d = new FactoryErrorDialog(
- mContext, msg.getData().getCharSequence("msg"));
+ getUiContext(), msg.getData().getCharSequence("msg"));
d.show();
ensureBootCompleted();
} break;
@@ -1367,7 +1375,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (!app.waitedForDebugger) {
Dialog d = new AppWaitingForDebuggerDialog(
ActivityManagerService.this,
- mContext, app);
+ getUiContext(), app);
app.waitDialog = d;
app.waitedForDebugger = true;
d.show();
@@ -1451,7 +1459,7 @@ public final class ActivityManagerService extends ActivityManagerNative
Log.e(TAG, title + ": " + text);
if (mShowDialogs) {
// XXX This is a temporary dialog, no need to localize.
- AlertDialog d = new BaseErrorDialog(mContext);
+ AlertDialog d = new BaseErrorDialog(getUiContext());
d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
d.setCancelable(false);
d.setTitle(title);
@@ -1526,7 +1534,7 @@ public final class ActivityManagerService extends ActivityManagerNative
notification.vibrate = null;
notification.color = mContext.getResources().getColor(
com.android.internal.R.color.system_notification_accent_color);
- notification.setLatestEventInfo(context, text,
+ notification.setLatestEventInfo(getUiContext(), text,
mContext.getText(R.string.heavy_weight_notification_detail),
PendingIntent.getActivityAsUser(mContext, 0, root.intent,
PendingIntent.FLAG_CANCEL_CURRENT, null,
@@ -2540,6 +2548,15 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ private Context getUiContext() {
+ synchronized (this) {
+ if (mUiContext == null && mBooted) {
+ mUiContext = ThemeUtils.createUiContext(mContext);
+ }
+ return mUiContext != null ? mUiContext : mContext;
+ }
+ }
+
/**
* Initialize the application bind args. These are passed to each
* process when the bindApplication() IPC is sent to the process. They're
@@ -3182,6 +3199,13 @@ public final class ActivityManagerService extends ActivityManagerNative
debugFlags |= Zygote.DEBUG_ENABLE_ASSERT;
}
+ //Check if zygote should refresh its fonts
+ boolean refreshTheme = false;
+ if (SystemProperties.getBoolean(PROP_REFRESH_THEME, false)) {
+ SystemProperties.set(PROP_REFRESH_THEME, "false");
+ refreshTheme = true;
+ }
+
String requiredAbi = (abiOverride != null) ? abiOverride : app.info.primaryCpuAbi;
if (requiredAbi == null) {
requiredAbi = Build.SUPPORTED_ABIS[0];
@@ -3200,7 +3224,7 @@ public final class ActivityManagerService extends ActivityManagerNative
Process.ProcessStartResult startResult = Process.start(entryPoint,
app.processName, uid, uid, gids, debugFlags, mountExternal,
app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
- app.info.dataDir, entryPointArgs);
+ app.info.dataDir, refreshTheme, entryPointArgs);
checkTime(startTime, "startProcess: returned from zygote!");
if (app.isolated) {
@@ -5286,7 +5310,7 @@ public final class ActivityManagerService extends ActivityManagerNative
@Override
public void run() {
synchronized (ActivityManagerService.this) {
- final Dialog d = new LaunchWarningWindow(mContext, cur, next);
+ final Dialog d = new LaunchWarningWindow(getUiContext(), cur, next);
d.show();
mHandler.postDelayed(new Runnable() {
@Override
@@ -6349,6 +6373,13 @@ public final class ActivityManagerService extends ActivityManagerNative
// Register receivers to handle package update events
mPackageMonitor.register(mContext, Looper.getMainLooper(), UserHandle.ALL, false);
+ ThemeUtils.registerThemeChangeReceiver(mContext, new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mUiContext = null;
+ }
+ });
+
// Let system services know.
mSystemServiceManager.startBootPhase(SystemService.PHASE_BOOT_COMPLETED);
@@ -11508,6 +11539,28 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ private void sendAppFailureBroadcast(String pkgName) {
+ Intent intent = new Intent(Intent.ACTION_APP_FAILURE,
+ (pkgName != null)? Uri.fromParts("package", pkgName, null) : null);
+ mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT_OR_SELF);
+ }
+
+ /**
+ * A possible theme crash is one that throws one of the following exceptions
+ * {@link android.content.res.Resources.NotFoundException}
+ * {@link android.view.InflateException}
+ *
+ * @param exceptionClassName
+ * @return True if exceptionClassName is one of the above exceptions
+ */
+ private boolean isPossibleThemeCrash(String exceptionClassName) {
+ if (Resources.NotFoundException.class.getName().equals(exceptionClassName) ||
+ InflateException.class.getName().equals(exceptionClassName)) {
+ return true;
+ }
+ return false;
+ }
+
private boolean handleAppCrashLocked(ProcessRecord app, String shortMsg, String longMsg,
String stackTrace) {
long now = SystemClock.uptimeMillis();
@@ -11518,6 +11571,9 @@ public final class ActivityManagerService extends ActivityManagerNative
} else {
crashTime = null;
}
+
+ if (isPossibleThemeCrash(shortMsg)) sendAppFailureBroadcast(app.info.packageName);
+
if (crashTime != null && now < crashTime+ProcessList.MIN_CRASH_INTERVAL) {
// This process loses!
Slog.w(TAG, "Process " + app.info.processName
@@ -16232,6 +16288,9 @@ public final class ActivityManagerService extends ActivityManagerNative
Configuration ci;
synchronized(this) {
ci = new Configuration(mConfiguration);
+ if (ci.themeConfig == null) {
+ ci.themeConfig = ThemeConfig.getBootTheme(mContext.getContentResolver());
+ }
}
return ci;
}
@@ -16303,6 +16362,11 @@ public final class ActivityManagerService extends ActivityManagerNative
values.userSetLocale);
}
+ if (values.themeConfig != null) {
+ saveThemeResourceLocked(values.themeConfig,
+ !values.themeConfig.equals(mConfiguration.themeConfig));
+ }
+
mConfigurationSeq++;
if (mConfigurationSeq <= 0) {
mConfigurationSeq = 1;
@@ -16463,6 +16527,13 @@ public final class ActivityManagerService extends ActivityManagerNative
return srec.launchedFromPackage;
}
+ private void saveThemeResourceLocked(ThemeConfig t, boolean isDiff){
+ if(isDiff) {
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Configuration.THEME_PKG_CONFIGURATION_PERSISTENCE_PROPERTY, t.toJson());
+ }
+ }
+
// =========================================================
// LIFETIME MANAGEMENT
// =========================================================
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index ca11862fa0f..81be892c5b8 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -19,6 +19,7 @@ package com.android.server.pm;
import android.content.Context;
import android.content.pm.PackageStats;
import android.os.Build;
+import android.text.TextUtils;
import android.util.Slog;
import dalvik.system.VMRuntime;
@@ -92,14 +93,51 @@ public final class Installer extends SystemService {
return mInstaller.dexopt(apkPath, uid, isPublic, pkgName, instructionSet, vmSafeMode);
}
- public int idmap(String targetApkPath, String overlayApkPath, int uid) {
+ public int idmap(String targetApkPath, String overlayApkPath, String cachePath,
+ int uid, int targetHash, int overlayHash) {
StringBuilder builder = new StringBuilder("idmap");
builder.append(' ');
builder.append(targetApkPath);
builder.append(' ');
builder.append(overlayApkPath);
builder.append(' ');
+ builder.append(cachePath);
+ builder.append(' ');
builder.append(uid);
+ builder.append(' ');
+ builder.append(targetHash);
+ builder.append(' ');
+ builder.append(overlayHash);
+ return mInstaller.execute(builder.toString());
+ }
+
+ public int aapt(String themeApkPath, String internalPath, String resTablePath, int uid,
+ int pkgId, int minSdkVersion, String commonResourcesPath) {
+
+ StringBuilder builder = new StringBuilder();
+ if (TextUtils.isEmpty(commonResourcesPath)) {
+ builder.append("aapt");
+ } else {
+ builder.append("aapt_with_common");
+ }
+ builder.append(' ');
+ builder.append(themeApkPath);
+ builder.append(' ');
+ builder.append(internalPath);
+ builder.append(' ');
+ builder.append(resTablePath);
+ builder.append(' ');
+ builder.append(uid);
+ builder.append(' ');
+ builder.append(pkgId);
+ builder.append(' ');
+ builder.append(minSdkVersion);
+
+ if (!TextUtils.isEmpty(commonResourcesPath)) {
+ builder.append(' ');
+ builder.append(commonResourcesPath);
+ }
+
return mInstaller.execute(builder.toString());
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 317d5c4e505..88f5f773969 100755
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3,6 +3,7 @@
* Not a Contribution.
*
* Copyright (C) 2006 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +20,7 @@
package com.android.server.pm;
+import static android.Manifest.permission.ACCESS_THEME_MANAGER;
import static android.Manifest.permission.GRANT_REVOKE_PERMISSIONS;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
@@ -61,6 +63,12 @@ import static com.android.internal.util.ArrayUtils.removeInt;
import android.util.ArrayMap;
import com.android.internal.R;
+import android.Manifest;
+import android.app.ComposedIconInfo;
+import android.content.res.AssetManager;
+import android.content.res.ThemeConfig;
+import android.content.res.ThemeManager;
+import android.util.Pair;
import com.android.internal.app.IMediaContainerService;
import com.android.internal.app.ResolverActivity;
import com.android.internal.content.NativeLibraryHelper;
@@ -78,6 +86,7 @@ import com.android.server.SystemConfig;
import com.android.server.Watchdog;
import com.android.server.pm.Settings.DatabaseVersion;
import com.android.server.storage.DeviceStorageMonitorInternal;
+import com.android.server.Watchdog;
import org.xmlpull.v1.XmlSerializer;
@@ -85,6 +94,7 @@ import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.AppGlobals;
import android.app.IActivityManager;
+import android.app.IconPackHelper;
import android.app.admin.IDevicePolicyManager;
import android.app.backup.IBackupManager;
import android.content.BroadcastReceiver;
@@ -121,6 +131,9 @@ import android.content.pm.PackageParser.PackageLite;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.PackageParser;
import android.content.pm.PackageStats;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageParser.Activity;
+import android.content.pm.PackageParser.Package;
import android.content.pm.PackageUserState;
import android.content.pm.ParceledListSlice;
import android.content.pm.PermissionGroupInfo;
@@ -130,6 +143,8 @@ import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.Signature;
import android.content.pm.UserInfo;
+import android.content.pm.ManifestDigest;
+import android.content.pm.ThemeUtils;
import android.content.pm.VerificationParams;
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.VerifierInfo;
@@ -159,6 +174,7 @@ import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings.Global;
import android.provider.Settings.Secure;
import android.security.KeyStore;
import android.security.SystemKeyStore;
@@ -183,17 +199,25 @@ import android.view.Display;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
+import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.IntBuffer;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
@@ -218,6 +242,12 @@ import java.util.concurrent.atomic.AtomicLong;
import dalvik.system.DexFile;
import dalvik.system.StaleDexCacheError;
import dalvik.system.VMRuntime;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
import libcore.io.IoUtils;
import libcore.util.EmptyArray;
@@ -335,8 +365,28 @@ public class PackageManagerService extends IPackageManager.Stub {
final ServiceThread mHandlerThread;
- private static final String IDMAP_PREFIX = "/data/resource-cache/";
- private static final String IDMAP_SUFFIX = "@idmap";
+ //Where overlays are be found in a theme APK
+ private static final String APK_PATH_TO_OVERLAY = "assets/overlays/";
+
+ //Where the icon pack can be found in a themed apk
+ private static final String APK_PATH_TO_ICONS = "assets/icons/";
+
+ private static final String COMMON_OVERLAY = ThemeUtils.COMMON_RES_TARGET;
+
+ private static final long PACKAGE_HASH_EXPIRATION = 3*60*1000; // 3 minutes
+ private static final long COMMON_RESOURCE_EXPIRATION = 3*60*1000; // 3 minutes
+
+ /**
+ * IDMAP hash version code used to alter the resulting hash and force recreating
+ * of the idmap. This value should be changed whenever there is a need to force
+ * an update to all idmaps.
+ */
+ private static final byte IDMAP_HASH_VERSION = 3;
+
+ /**
+ * The offset in bytes to the beginning of the hashes in an idmap
+ */
+ private static final int IDMAP_HASH_START_OFFSET = 16;
final PackageHandler mHandler;
@@ -398,6 +448,8 @@ public class PackageManagerService extends IPackageManager.Stub {
new HashMap<String, PackageParser.Package>();
// Tracks available target package names -> overlay package paths.
+ // Example: com.angrybirds -> (com.theme1 -> theme1pkg, com.theme2 -> theme2pkg)
+ // com.facebook -> (com.theme1 -> theme1pkg)
final HashMap<String, HashMap<String, PackageParser.Package>> mOverlays =
new HashMap<String, HashMap<String, PackageParser.Package>>();
@@ -492,7 +544,21 @@ public class PackageManagerService extends IPackageManager.Stub {
ComponentName mCustomResolverComponentName;
boolean mResolverReplaced = false;
+ private IconPackHelper mIconPackHelper;
+
+ private Map<String, Pair<Integer, Long>> mPackageHashes =
+ new HashMap<String, Pair<Integer, Long>>();
+ private Map<String, Long> mAvailableCommonResources = new HashMap<String, Long>();
+
+ private ThemeConfig mBootThemeConfig;
+
+ final ResolveInfo mPreLaunchCheckResolveInfo = new ResolveInfo();
+ ComponentName mCustomPreLaunchComponentName;
+ private Set<String> mPreLaunchCheckPackages =
+ Collections.synchronizedSet(new HashSet<String>());
+
+ boolean mPreLaunchCheckPackagesReplaced = false;
// Set of pending broadcasts for aggregating enable/disable of components.
static class PendingPackageBroadcasts {
// for each user id, a map of <package name -> components within that package>
@@ -1038,21 +1104,25 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
- res.pkg.applicationInfo.packageName,
+ res.pkg.applicationInfo.packageName, null,
extras, null, null, firstUsers);
final boolean update = res.removedInfo.removedPackage != null;
if (update) {
extras.putBoolean(Intent.EXTRA_REPLACING, true);
}
+ String category = null;
+ if(res.pkg.mIsThemeApk) {
+ category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE;
+ }
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
- res.pkg.applicationInfo.packageName,
+ res.pkg.applicationInfo.packageName, category,
extras, null, null, updateUsers);
if (update) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
- res.pkg.applicationInfo.packageName,
+ res.pkg.applicationInfo.packageName, category,
extras, null, null, updateUsers);
sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
- null, null,
+ null, null, null,
res.pkg.applicationInfo.packageName, null, updateUsers);
// treat asec-hosted packages like removable media on upgrade
@@ -1353,6 +1423,8 @@ public class PackageManagerService extends IPackageManager.Stub {
getDefaultDisplayMetrics(context, mMetrics);
+ removeLegacyResourceCache();
+
SystemConfig systemConfig = SystemConfig.getInstance();
mGlobalGids = systemConfig.getGlobalGids();
mSystemPermissions = systemConfig.getSystemPermissions();
@@ -1549,6 +1621,8 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ mBootThemeConfig = ThemeUtils.getBootThemeDirty();
+
// Collect vendor overlay packages.
// (Do this before scanning any apps.)
// For security and version matching reason, only consider
@@ -4091,56 +4165,34 @@ public class PackageManagerService extends IPackageManager.Stub {
return finalList;
}
-
- private void createIdmapsForPackageLI(PackageParser.Package pkg) {
- HashMap<String, PackageParser.Package> overlays = mOverlays.get(pkg.packageName);
- if (overlays == null) {
- Slog.w(TAG, "Unable to create idmap for " + pkg.packageName + ": no overlay packages");
- return;
- }
- for (PackageParser.Package opkg : overlays.values()) {
- // Not much to do if idmap fails: we already logged the error
- // and we certainly don't want to abort installation of pkg simply
- // because an overlay didn't fit properly. For these reasons,
- // ignore the return value of createIdmapForPackagePairLI.
- createIdmapForPackagePairLI(pkg, opkg);
- }
- }
-
private boolean createIdmapForPackagePairLI(PackageParser.Package pkg,
PackageParser.Package opkg) {
+ if (DEBUG_PACKAGE_SCANNING) Log.d(TAG,
+ "Generating idmaps between " + pkg.packageName + ":" + opkg.packageName);
if (!opkg.mTrustedOverlay) {
Slog.w(TAG, "Skipping target and overlay pair " + pkg.baseCodePath + " and " +
opkg.baseCodePath + ": overlay not trusted");
return false;
}
- HashMap<String, PackageParser.Package> overlaySet = mOverlays.get(pkg.packageName);
+
+ // Some apps like to take on the package name of an existing app so we'll use the
+ // "real" package name, if it is non-null, when performing the idmap
+ final String pkgName = pkg.mRealPackage != null ? pkg.mRealPackage : pkg.packageName;
+ HashMap<String, PackageParser.Package> overlaySet = mOverlays.get(pkgName);
if (overlaySet == null) {
Slog.e(TAG, "was about to create idmap for " + pkg.baseCodePath + " and " +
opkg.baseCodePath + " but target package has no known overlays");
return false;
}
final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
- // TODO: generate idmap for split APKs
- if (mInstaller.idmap(pkg.baseCodePath, opkg.baseCodePath, sharedGid) != 0) {
- Slog.e(TAG, "Failed to generate idmap for " + pkg.baseCodePath + " and "
- + opkg.baseCodePath);
+ final String cachePath =
+ ThemeUtils.getTargetCacheDir(pkgName, opkg.packageName);
+ if (mInstaller.idmap(pkg.baseCodePath, opkg.baseCodePath, cachePath, sharedGid,
+ getPackageHashCode(pkg), getPackageHashCode(opkg)) != 0) {
+ Slog.e(TAG, "Failed to generate idmap for " + pkg.baseCodePath +
+ " and " + opkg.baseCodePath);
return false;
}
- PackageParser.Package[] overlayArray =
- overlaySet.values().toArray(new PackageParser.Package[0]);
- Comparator<PackageParser.Package> cmp = new Comparator<PackageParser.Package>() {
- public int compare(PackageParser.Package p1, PackageParser.Package p2) {
- return p1.mOverlayPriority - p2.mOverlayPriority;
- }
- };
- Arrays.sort(overlayArray, cmp);
-
- pkg.applicationInfo.resourceDirs = new String[overlayArray.length];
- int i = 0;
- for (PackageParser.Package p : overlayArray) {
- pkg.applicationInfo.resourceDirs[i++] = p.baseCodePath;
- }
return true;
}
@@ -4250,7 +4302,7 @@ public class PackageManagerService extends IPackageManager.Stub {
/*
* Scan a package and return the newly parsed package.
- * Returns null in case of errors and the error code is stored in mLastScanError
+ * Returns null in case of errors and the error code is stored in
*/
private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
long currentTime, UserHandle user) throws PackageManagerException {
@@ -6038,6 +6090,13 @@ public class PackageManagerService extends IPackageManager.Stub {
// Add the new setting to mSettings
mSettings.insertPackageSettingLPw(pkgSetting, pkg);
+
+ // Themes: handle case where app was installed after icon mapping applied
+ if (mIconPackHelper != null) {
+ int id = mIconPackHelper.getResourceIdForApp(pkg.packageName);
+ pkg.applicationInfo.themedIcon = id;
+ }
+
// Add the new setting to mPackages
mPackages.put(pkg.applicationInfo.packageName, pkg);
// Make sure we don't accidentally delete its data.
@@ -6199,6 +6258,13 @@ public class PackageManagerService extends IPackageManager.Stub {
PackageParser.Activity a = pkg.activities.get(i);
a.info.processName = fixProcessName(pkg.applicationInfo.processName,
a.info.processName, pkg.applicationInfo.uid);
+
+ // Themes: handle case where app was installed after icon mapping applied
+ if (mIconPackHelper != null) {
+ a.info.themedIcon = mIconPackHelper
+ .getResourceIdForActivityIcon(a.info);
+ }
+
mActivities.addActivity(a, "activity");
if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
if (r == null) {
@@ -6372,27 +6438,94 @@ public class PackageManagerService extends IPackageManager.Stub {
pkgSetting.setTimeStamp(scanFileTime);
- // Create idmap files for pairs of (packages, overlay packages).
- // Note: "android", ie framework-res.apk, is handled by native layers.
- if (pkg.mOverlayTarget != null) {
- // This is an overlay package.
- if (pkg.mOverlayTarget != null && !pkg.mOverlayTarget.equals("android")) {
- if (!mOverlays.containsKey(pkg.mOverlayTarget)) {
- mOverlays.put(pkg.mOverlayTarget,
- new HashMap<String, PackageParser.Package>());
+ final boolean isBootScan = (scanFlags & SCAN_BOOTING) != 0;
+ // Generate resources & idmaps if pkg is NOT a theme
+ // We must compile resources here because during the initial boot process we may get
+ // here before a default theme has had a chance to compile its resources
+ if (pkg.mOverlayTargets.isEmpty() && mOverlays.containsKey(pkg.packageName)) {
+ HashMap<String, PackageParser.Package> themes = mOverlays.get(pkg.packageName);
+ for(PackageParser.Package themePkg : themes.values()) {
+ if (!isBootScan || (mBootThemeConfig != null &&
+ (themePkg.packageName.equals(mBootThemeConfig.getOverlayPkgName()) ||
+ themePkg.packageName.equals(
+ mBootThemeConfig.getOverlayPkgNameForApp(pkg.packageName))))) {
+ try {
+ compileResourcesAndIdmapIfNeeded(pkg, themePkg);
+ } catch (Exception e) {
+ // Do not stop a pkg installation just because of one bad theme
+ // Also we don't break here because we should try to compile other
+ // themes
+ Log.e(TAG, "Unable to compile " + themePkg.packageName
+ + " for target " + pkg.packageName, e);
+ }
+ }
+ }
+ }
+
+ // If this is a theme we should re-compile common resources if they exist so
+ // remove this package from mAvailableCommonResources.
+ if (!isBootScan && pkg.mOverlayTargets.size() > 0) {
+ mAvailableCommonResources.remove(pkg.packageName);
+ }
+
+ // Generate Idmaps and res tables if pkg is a theme
+ for(String target : pkg.mOverlayTargets) {
+ Exception failedException = null;
+ int failReason = 0;
+
+ insertIntoOverlayMap(target, pkg);
+ if (isBootScan && mBootThemeConfig != null &&
+ (pkg.packageName.equals(mBootThemeConfig.getOverlayPkgName()) ||
+ pkg.packageName.equals(
+ mBootThemeConfig.getOverlayPkgNameForApp(target)))) {
+ try {
+ compileResourcesAndIdmapIfNeeded(mPackages.get(target), pkg);
+ } catch (IdmapException e) {
+ failedException = e;
+ failReason = PackageManager.INSTALL_FAILED_THEME_IDMAP_ERROR;
+ } catch (AaptException e) {
+ failedException = e;
+ failReason = PackageManager.INSTALL_FAILED_THEME_AAPT_ERROR;
+ } catch (Exception e) {
+ failedException = e;
+ failReason = PackageManager.INSTALL_FAILED_THEME_UNKNOWN_ERROR;
}
- HashMap<String, PackageParser.Package> map = mOverlays.get(pkg.mOverlayTarget);
- map.put(pkg.packageName, pkg);
- PackageParser.Package orig = mPackages.get(pkg.mOverlayTarget);
- if (orig != null && !createIdmapForPackagePairLI(orig, pkg)) {
- throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
- "scanPackageLI failed to createIdmap");
+
+ if (failedException != null) {
+ Log.w(TAG, "Unable to process theme " + pkgName, failedException);
+ uninstallThemeForAllApps(pkg);
+ deletePackageLI(pkg.packageName, null, true, null, null, 0, null, false);
+ throw new PackageManagerException(failReason,
+ "Unable to process theme " + pkgName, failedException);
+ }
+ }
+ }
+
+ if (!isBootScan && (pkg.mIsThemeApk)) {
+ // Pass this off to the ThemeService for processing
+ ThemeManager tm =
+ (ThemeManager) mContext.getSystemService(Context.THEME_SERVICE);
+ if (tm != null) {
+ tm.processThemeResources(pkg.packageName);
+ }
+ }
+
+ //Icon Packs need aapt too
+ if (isBootScan && (mBootThemeConfig != null &&
+ pkg.packageName.equals(mBootThemeConfig.getIconPackPkgName()))) {
+ if (isIconCompileNeeded(pkg)) {
+ try {
+ ThemeUtils.createCacheDirIfNotExists();
+ ThemeUtils.createIconDirIfNotExists(pkg.packageName);
+ compileIconPack(pkg);
+ } catch (Exception e) {
+ uninstallThemeForAllApps(pkg);
+ deletePackageLI(pkg.packageName, null, true, null, null, 0, null, false);
+ throw new PackageManagerException(
+ PackageManager.INSTALL_FAILED_THEME_AAPT_ERROR,
+ "Unable to process theme " + pkgName);
}
}
- } else if (mOverlays.containsKey(pkg.packageName) &&
- !pkg.packageName.equals("android")) {
- // This is a regular package, with one or more known overlay packages.
- createIdmapsForPackageLI(pkg);
}
}
@@ -6491,6 +6624,363 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+
+ private boolean isIconCompileNeeded(Package pkg) {
+ if (!pkg.hasIconPack) return false;
+ // Read in the stored hash value and compare to the pkgs computed hash value
+ FileInputStream in = null;
+ DataInputStream dataInput = null;
+ try {
+ String hashFile = ThemeUtils.getIconHashFile(pkg.packageName);
+ in = new FileInputStream(hashFile);
+ dataInput = new DataInputStream(in);
+ int storedHashCode = dataInput.readInt();
+ int actualHashCode = getPackageHashCode(pkg);
+ return storedHashCode != actualHashCode;
+ } catch(IOException e) {
+ // all is good enough for government work here,
+ // we'll just return true and the icons will be processed
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(dataInput);
+ }
+
+ return true;
+ }
+
+ private void compileResourcesAndIdmapIfNeeded(PackageParser.Package targetPkg,
+ PackageParser.Package themePkg)
+ throws IdmapException, AaptException, IOException, Exception
+ {
+ if (!shouldCreateIdmap(targetPkg, themePkg)) {
+ return;
+ }
+
+ // Always use the manifest's pkgName when compiling resources
+ // the member value of "packageName" is dependent on whether this was a clean install
+ // or an upgrade w/ If the app is an upgrade then the original package name is used.
+ // because libandroidfw uses the manifests's pkgName during idmap creation we must
+ // be consistent here and use the same name, otherwise idmap will look in the wrong place
+ // for the resource table.
+ String pkgName = targetPkg.mRealPackage != null ?
+ targetPkg.mRealPackage : targetPkg.packageName;
+ compileResourcesIfNeeded(pkgName, themePkg);
+ generateIdmap(targetPkg.packageName, themePkg);
+ }
+
+ private void compileResourcesIfNeeded(String target, PackageParser.Package pkg)
+ throws AaptException, IOException, Exception
+ {
+ ThemeUtils.createCacheDirIfNotExists();
+
+ if (hasCommonResources(pkg)
+ && shouldCompileCommonResources(pkg)) {
+ ThemeUtils.createResourcesDirIfNotExists(COMMON_OVERLAY, pkg.packageName);
+ compileResources(COMMON_OVERLAY, pkg);
+ mAvailableCommonResources.put(pkg.packageName, System.currentTimeMillis());
+ }
+
+ ThemeUtils.createResourcesDirIfNotExists(target, pkg.packageName);
+ compileResources(target, pkg);
+ }
+
+ private void compileResources(String target, PackageParser.Package pkg) throws Exception {
+ if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Compile resource table for " + pkg.packageName);
+ //TODO: cleanup this hack. Modify aapt? Aapt uses the manifests package name
+ //when creating the resource table. We care about the resource table's name because
+ //it is used when removing the table by cookie.
+ try {
+ createTempManifest(COMMON_OVERLAY.equals(target)
+ ? ThemeUtils.getCommonPackageName(pkg.packageName) : pkg.packageName);
+ compileResourcesWithAapt(target, pkg);
+ } finally {
+ cleanupTempManifest();
+ }
+ }
+
+ private void compileIconPack(Package pkg) throws Exception {
+ if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Compile resource table for " + pkg.packageName);
+ OutputStream out = null;
+ DataOutputStream dataOut = null;
+ try {
+ createTempManifest(pkg.packageName);
+ int code = getPackageHashCode(pkg);
+ String hashFile = ThemeUtils.getIconHashFile(pkg.packageName);
+ out = new FileOutputStream(hashFile);
+ dataOut = new DataOutputStream(out);
+ dataOut.writeInt(code);
+ compileIconsWithAapt(pkg);
+ } finally {
+ IoUtils.closeQuietly(out);
+ IoUtils.closeQuietly(dataOut);
+ cleanupTempManifest();
+ }
+ }
+
+ private void insertIntoOverlayMap(String target, PackageParser.Package opkg) {
+ if (!mOverlays.containsKey(target)) {
+ mOverlays.put(target,
+ new HashMap<String, PackageParser.Package>());
+ }
+ HashMap<String, PackageParser.Package> map = mOverlays.get(target);
+ map.put(opkg.packageName, opkg);
+ }
+
+ private void generateIdmap(String target, PackageParser.Package opkg) throws IdmapException {
+ PackageParser.Package targetPkg = mPackages.get(target);
+ if (targetPkg != null && !createIdmapForPackagePairLI(targetPkg, opkg)) {
+ throw new IdmapException("idmap failed for targetPkg: " + targetPkg
+ + " and opkg: " + opkg);
+ }
+ }
+
+ public class AaptException extends Exception {
+ public AaptException(String message) {
+ super(message);
+ }
+ }
+
+ public class IdmapException extends Exception {
+ public IdmapException(String message) {
+ super(message);
+ }
+ }
+
+ private boolean hasCommonResources(PackageParser.Package pkg) throws Exception {
+ boolean ret = false;
+ // check if assets/overlays/common exists in this theme
+ AssetManager assets = new AssetManager();
+ assets.addAssetPath(pkg.baseCodePath);
+ String[] common = assets.list("overlays/common");
+ if (common != null && common.length > 0) ret = true;
+
+ return ret;
+ }
+
+ private void compileResourcesWithAapt(String target, PackageParser.Package pkg)
+ throws Exception {
+ String internalPath = APK_PATH_TO_OVERLAY + target;
+ String resPath = ThemeUtils.getTargetCacheDir(target, pkg);
+ final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
+ int pkgId;
+ if ("android".equals(target)) {
+ pkgId = Resources.THEME_FRAMEWORK_PKG_ID;
+ } else if (COMMON_OVERLAY.equals(target)) {
+ pkgId = Resources.THEME_COMMON_PKG_ID;
+ } else {
+ pkgId = Resources.THEME_APP_PKG_ID;
+ }
+
+ boolean hasCommonResources = (hasCommonResources(pkg) && !COMMON_OVERLAY.equals(target));
+ if (mInstaller.aapt(pkg.baseCodePath, internalPath, resPath, sharedGid, pkgId,
+ pkg.applicationInfo.targetSdkVersion,
+ hasCommonResources ? ThemeUtils.getTargetCacheDir(COMMON_OVERLAY, pkg)
+ + File.separator + "resources.apk" : "") != 0) {
+ throw new AaptException("Failed to run aapt");
+ }
+ }
+
+ private void compileIconsWithAapt(Package pkg) throws Exception {
+ String resPath = ThemeUtils.getIconPackDir(pkg.packageName);
+ final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
+
+ if (mInstaller.aapt(pkg.baseCodePath, APK_PATH_TO_ICONS, resPath, sharedGid,
+ Resources.THEME_ICON_PKG_ID,
+ pkg.applicationInfo.targetSdkVersion,
+ "") != 0) {
+ throw new AaptException("Failed to run aapt");
+ }
+ }
+
+ private void uninstallThemeForAllApps(PackageParser.Package opkg) {
+ for(String target : opkg.mOverlayTargets) {
+ HashMap<String, PackageParser.Package> map = mOverlays.get(target);
+ if (map != null) {
+ map.remove(opkg.packageName);
+
+ if (map.isEmpty()) {
+ mOverlays.remove(target);
+ }
+ }
+ }
+
+ // Now simply delete the root overlay cache directory and all its contents
+ recursiveDelete(new File(ThemeUtils.getOverlayResourceCacheDir(opkg.packageName)));
+ }
+
+ private void uninstallThemeForApp(PackageParser.Package appPkg) {
+ HashMap<String, PackageParser.Package> map = mOverlays.get(appPkg.packageName);
+ if (map == null) return;
+
+ for(PackageParser.Package opkg : map.values()) {
+ String cachePath = ThemeUtils.getTargetCacheDir(appPkg.packageName, opkg.packageName);
+ recursiveDelete(new File(cachePath));
+ }
+ }
+
+ private void recursiveDelete(File f) {
+ if (f.isDirectory()) {
+ for (File c : f.listFiles())
+ recursiveDelete(c);
+ }
+ f.delete();
+ }
+
+ private void createTempManifest(String pkgName) throws Exception {
+ StringBuilder manifest = new StringBuilder();
+ manifest.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
+ manifest.append("<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"");
+ manifest.append(" package=\"" + pkgName+ "\">");
+ manifest.append(" </manifest>");
+
+ BufferedWriter bw = null;
+ try {
+ bw = new BufferedWriter(new FileWriter("/data/app/AndroidManifest.xml"));
+ bw.write(manifest.toString());
+ bw.flush();
+ bw.close();
+ File resFile = new File("/data/app/AndroidManifest.xml");
+ FileUtils.setPermissions(resFile,
+ FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IROTH, -1, -1);
+ } finally {
+ IoUtils.closeQuietly(bw);
+ }
+ }
+
+ private void cleanupTempManifest() {
+ File resFile = new File("/data/app/AndroidManifest.xml");
+ resFile.delete();
+ }
+
+ /**
+ * Checks for existance of resources.arsc in target apk, then
+ * Compares the 32 bit hash of the target and overlay to those stored
+ * in the idmap and returns true if either hash differs
+ * @param targetPkg
+ * @param overlayPkg
+ * @return
+ * @throws IOException
+ */
+ private boolean shouldCreateIdmap(PackageParser.Package targetPkg,
+ PackageParser.Package overlayPkg) {
+ if (targetPkg == null || targetPkg.baseCodePath == null || overlayPkg == null) return false;
+
+ // Check if the target app has resources.arsc.
+ // If it does not, then there is nothing to idmap
+ ZipFile zfile = null;
+ try {
+ zfile = new ZipFile(targetPkg.baseCodePath);
+ if (zfile.getEntry("resources.arsc") == null) return false;
+ } catch (IOException e) {
+ Log.e(TAG, "Error while checking resources.arsc on" + targetPkg.baseCodePath, e);
+ return false;
+ } finally {
+ IoUtils.closeQuietly(zfile);
+ }
+
+
+ int targetHash = getPackageHashCode(targetPkg);
+ int overlayHash = getPackageHashCode(overlayPkg);
+
+ File idmap =
+ new File(ThemeUtils.getIdmapPath(targetPkg.packageName, overlayPkg.packageName));
+ if (!idmap.exists())
+ return true;
+
+ int[] hashes;
+ try {
+ hashes = getIdmapHashes(idmap);
+ } catch (IOException e) {
+ return true;
+ }
+
+ if (targetHash == 0 || overlayHash == 0 ||
+ targetHash != hashes[0] || overlayHash != hashes[1]) {
+ // if the overlay changed we'll want to recreate the common resources if it has any
+ if (overlayHash != hashes[1]
+ && mAvailableCommonResources.containsKey(overlayPkg.packageName)) {
+ mAvailableCommonResources.remove(overlayPkg.packageName);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private boolean shouldCompileCommonResources(PackageParser.Package pkg) {
+ if (!mAvailableCommonResources.containsKey(pkg.packageName)) return true;
+
+ long lastUpdated = mAvailableCommonResources.get(pkg.packageName);
+ long currentTime = System.currentTimeMillis();
+ if (currentTime - lastUpdated > COMMON_RESOURCE_EXPIRATION) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get the file modified times for the overlay and target from the idmap
+ * @param idmap
+ * @return
+ * @throws IOException
+ */
+ private int[] getIdmapHashes(File idmap) throws IOException {
+ int[] times = new int[2];
+ ByteBuffer bb = ByteBuffer.allocate(8);
+ bb.order(ByteOrder.LITTLE_ENDIAN);
+ FileInputStream fis = new FileInputStream(idmap);
+ fis.skip(IDMAP_HASH_START_OFFSET);
+ fis.read(bb.array());
+ fis.close();
+ final IntBuffer ib = bb.asIntBuffer();
+ times[0] = ib.get(0);
+ times[1] = ib.get(1);
+
+ return times;
+ }
+
+ /**
+ * Get a 32 bit hashcode for the given package.
+ * @param pkg
+ * @return
+ */
+ private int getPackageHashCode(PackageParser.Package pkg) {
+ Pair<Integer, Long> p = mPackageHashes.get(pkg.packageName);
+ if (p != null && (System.currentTimeMillis() - p.second < PACKAGE_HASH_EXPIRATION)) {
+ return p.first;
+ }
+ if (p != null) {
+ mPackageHashes.remove(p);
+ }
+
+ byte[] crc = getFileCrC(pkg.baseCodePath);
+ if (crc == null) return 0;
+
+ p = new Pair(Arrays.hashCode(ByteBuffer.wrap(crc).put(IDMAP_HASH_VERSION).array()),
+ System.currentTimeMillis());
+ mPackageHashes.put(pkg.packageName, p);
+ return p.first;
+ }
+
+ private byte[] getFileCrC(String path) {
+ ZipFile zfile = null;
+ try {
+ zfile = new ZipFile(path);
+ ZipEntry entry = zfile.getEntry("META-INF/MANIFEST.MF");
+ if (entry == null) {
+ Log.e(TAG, "Unable to get MANIFEST.MF from " + path);
+ return null;
+ }
+
+ long crc = entry.getCrc();
+ if (crc == -1) Log.e(TAG, "Unable to get CRC for " + path);
+ return ByteBuffer.allocate(8).putLong(crc).array();
+ } catch (Exception e) {
+ } finally {
+ IoUtils.closeQuietly(zfile);
+ }
+ return null;
+ }
+
private void setUpCustomResolverActivity(PackageParser.Package pkg) {
synchronized (mPackages) {
mResolverReplaced = true;
@@ -7902,7 +8392,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
};
- static final void sendPackageBroadcast(String action, String pkg,
+ static final void sendPackageBroadcast(String action, String pkg, String intentCategory,
Bundle extras, String targetPkg, IIntentReceiver finishedReceiver,
int[] userIds) {
IActivityManager am = ActivityManagerNative.getDefault();
@@ -7935,6 +8425,9 @@ public class PackageManagerService extends IPackageManager.Stub {
+ intent.toShortString(false, true, false, false)
+ " " + intent.getExtras(), here);
}
+ if (intentCategory != null) {
+ intent.addCategory(intentCategory);
+ }
am.broadcastIntent(null, intent, null, finishedReceiver,
0, null, null, null, android.app.AppOpsManager.OP_NONE,
finishedReceiver != null, false, id);
@@ -8097,7 +8590,7 @@ public class PackageManagerService extends IPackageManager.Stub {
Bundle extras = new Bundle(1);
extras.putInt(Intent.EXTRA_UID, UserHandle.getUid(userId, pkgSetting.appId));
- sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, null,
packageName, extras, null, null, new int[] {userId});
try {
IActivityManager am = ActivityManagerNative.getDefault();
@@ -8885,6 +9378,9 @@ public class PackageManagerService extends IPackageManager.Stub {
// reader
synchronized (mPackages) {
PackageParser.Package pkg = mPackages.get(packageName);
+ if (pkgLite.isTheme) {
+ return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+ }
if (pkg != null) {
if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
// Check for downgrading.
@@ -9025,7 +9521,7 @@ public class PackageManagerService extends IPackageManager.Stub {
loc = installLocationPolicy(pkgLite);
if (loc == PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE) {
ret = PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
- } else if (!onSd && !onInt) {
+ } else if ((!onSd && !onInt) || pkgLite.isTheme) {
// Override install location with flags
if (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {
// Set the flag to install on external media.
@@ -9037,6 +9533,9 @@ public class PackageManagerService extends IPackageManager.Stub {
installFlags |= PackageManager.INSTALL_INTERNAL;
installFlags &= ~PackageManager.INSTALL_EXTERNAL;
}
+ if (pkgLite.isTheme) {
+ installFlags &= ~PackageManager.INSTALL_FORWARD_LOCK;
+ }
}
}
}
@@ -10894,11 +11393,16 @@ public class PackageManagerService extends IPackageManager.Stub {
? info.removedAppId : info.uid);
extras.putBoolean(Intent.EXTRA_REPLACING, true);
- sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
+ String category = null;
+ if (info.isThemeApk) {
+ category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE;
+ }
+
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, category,
extras, null, null, null);
- sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, category,
extras, null, null, null);
- sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null,
+ sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, null,
null, packageName, null, null);
}
}
@@ -10923,6 +11427,7 @@ public class PackageManagerService extends IPackageManager.Stub {
boolean isRemovedPackageSystemUpdate = false;
// Clean up resources deleted packages.
InstallArgs args = null;
+ boolean isThemeApk = false;
void sendBroadcast(boolean fullRemove, boolean replacing, boolean removedForAllUsers) {
Bundle extras = new Bundle(1);
@@ -10933,15 +11438,19 @@ public class PackageManagerService extends IPackageManager.Stub {
}
extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, removedForAllUsers);
if (removedPackage != null) {
- sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage,
+ String category = null;
+ if (isThemeApk) {
+ category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE;
+ }
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, category,
extras, null, null, removedUsers);
if (fullRemove && !replacing) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED, removedPackage,
- extras, null, null, removedUsers);
+ category, extras, null, null, removedUsers);
}
}
if (removedAppId >= 0) {
- sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras, null, null,
+ sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, null, extras, null, null,
removedUsers);
}
}
@@ -11287,6 +11796,15 @@ public class PackageManagerService extends IPackageManager.Stub {
outInfo, writeSettings);
}
+ //Cleanup theme related data
+ if (ps.pkg != null) {
+ if (ps.pkg.mOverlayTargets.size() > 0) {
+ uninstallThemeForAllApps(ps.pkg);
+ } else if (mOverlays.containsKey(ps.pkg.packageName)) {
+ uninstallThemeForApp(ps.pkg);
+ }
+ }
+
return ret;
}
@@ -12199,7 +12717,7 @@ public class PackageManagerService extends IPackageManager.Stub {
extras.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, nameList);
extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, killFlag);
extras.putInt(Intent.EXTRA_UID, packageUid);
- sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, null, null,
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, null, extras, null, null,
new int[] {UserHandle.getUserId(packageUid)});
}
@@ -12978,7 +13496,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
: Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
- sendPackageBroadcast(action, null, extras, null, finishedReceiver, null);
+ sendPackageBroadcast(action, null, null, extras, null, finishedReceiver, null);
}
}
@@ -13595,4 +14113,125 @@ public class PackageManagerService extends IPackageManager.Stub {
return false;
}
}
+
+ @Override
+ public void updateIconMapping(String pkgName) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_CONFIGURATION,
+ "could not update icon mapping because caller "
+ + "does not have change config permission");
+
+ synchronized (mPackages) {
+ ThemeUtils.clearIconCache();
+ if (pkgName == null) {
+ clearIconMapping();
+ return;
+ }
+ mIconPackHelper = new IconPackHelper(mContext);
+ try {
+ mIconPackHelper.loadIconPack(pkgName);
+ } catch(NameNotFoundException e) {
+ Log.e(TAG, "Unable to find icon pack: " + pkgName);
+ clearIconMapping();
+ return;
+ }
+
+ for (Activity activity : mActivities.mActivities.values()) {
+ activity.info.themedIcon =
+ mIconPackHelper.getResourceIdForActivityIcon(activity.info);
+ }
+
+ for (Package pkg : mPackages.values()) {
+ pkg.applicationInfo.themedIcon =
+ mIconPackHelper.getResourceIdForApp(pkg.packageName);
+ }
+ }
+ }
+
+ private void clearIconMapping() {
+ mIconPackHelper = null;
+ for (Activity activity : mActivities.mActivities.values()) {
+ activity.info.themedIcon = 0;
+ }
+
+ for (Package pkg : mPackages.values()) {
+ pkg.applicationInfo.themedIcon = 0;
+ }
+ }
+
+ @Override
+ public ComposedIconInfo getComposedIconInfo() {
+ return mIconPackHelper != null ? mIconPackHelper.getComposedIconInfo() : null;
+ }
+
+ @Override
+ public int processThemeResources(String themePkgName) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.ACCESS_THEME_MANAGER, null);
+ PackageParser.Package pkg = mPackages.get(themePkgName);
+ if (pkg == null) {
+ Log.w(TAG, "Unable to get pkg for processing " + themePkgName);
+ return 0;
+ }
+
+ // Process icons
+ if (isIconCompileNeeded(pkg)) {
+ try {
+ ThemeUtils.createCacheDirIfNotExists();
+ ThemeUtils.createIconDirIfNotExists(pkg.packageName);
+ compileIconPack(pkg);
+ } catch (Exception e) {
+ uninstallThemeForAllApps(pkg);
+ deletePackageX(themePkgName, getCallingUid(), PackageManager.DELETE_ALL_USERS);
+ return PackageManager.INSTALL_FAILED_THEME_AAPT_ERROR;
+ }
+ }
+
+ int errorCode = 0;
+ // Generate Idmaps and res tables if pkg is a theme
+ for(String target : pkg.mOverlayTargets) {
+ Exception failedException = null;
+ try {
+ compileResourcesAndIdmapIfNeeded(mPackages.get(target), pkg);
+ } catch (IdmapException e) {
+ failedException = e;
+ errorCode = PackageManager.INSTALL_FAILED_THEME_IDMAP_ERROR;
+ } catch (AaptException e) {
+ failedException = e;
+ errorCode = PackageManager.INSTALL_FAILED_THEME_AAPT_ERROR;
+ } catch (Exception e) {
+ failedException = e;
+ errorCode = PackageManager.INSTALL_FAILED_THEME_UNKNOWN_ERROR;
+ }
+
+ if (failedException != null) {
+ Log.e(TAG, "Unable to process theme, uninstalling " + pkg.packageName,
+ failedException);
+ uninstallThemeForAllApps(pkg);
+ deletePackageX(themePkgName, getCallingUid(), PackageManager.DELETE_ALL_USERS);
+ return errorCode;
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * The new resource cache structure does not flatten the paths for idmaps, so this method
+ * checks for files that end with @idmap and assumes this indicates the older format and
+ * removes all files and directories from the resource cache so that it can be rebuilt
+ * using the new format.
+ */
+ private static void removeLegacyResourceCache() {
+ File cacheDir = new File(ThemeUtils.RESOURCE_CACHE_DIR);
+ if (cacheDir.exists()) {
+ for (File f : cacheDir.listFiles()) {
+ if (f.getName().endsWith(ThemeUtils.IDMAP_SUFFIX)) {
+ Log.i(TAG, "Removing old resource cache");
+ FileUtils.deleteContents(new File(ThemeUtils.RESOURCE_CACHE_DIR));
+ return;
+ }
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index d775eb029db..867484694a7 100755
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3290,7 +3290,7 @@ final class Settings {
if (pkgSetting.getNotLaunched(userId)) {
if (pkgSetting.installerPackageName != null) {
PackageManagerService.sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH,
- pkgSetting.name, null,
+ pkgSetting.name, null, null,
pkgSetting.installerPackageName, null, new int[] {userId});
}
pkgSetting.setNotLaunched(false, userId);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 8a3633544d7..f3fffa8fd14 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -101,6 +101,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
static final long MIN_WALLPAPER_CRASH_TIME = 10000;
static final String WALLPAPER = "wallpaper";
static final String WALLPAPER_INFO = "wallpaper_info.xml";
+ static final String KEYGUARD_WALLPAPER = "keyguard_wallpaper";
+ static final String KEYGUARD_WALLPAPER_INFO = "keyguard_wallpaper_info.xml";
/**
* Observes the wallpaper for changes and notifies all IWallpaperServiceCallbacks
@@ -111,15 +113,19 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
private class WallpaperObserver extends FileObserver {
final WallpaperData mWallpaper;
+ final KeyguardWallpaperData mKeyguardWallpaper;
final File mWallpaperDir;
final File mWallpaperFile;
+ final File mKeyguardWallpaperFile;
- public WallpaperObserver(WallpaperData wallpaper) {
+ public WallpaperObserver(WallpaperData wallpaper, KeyguardWallpaperData keyguardWallpaper) {
super(getWallpaperDir(wallpaper.userId).getAbsolutePath(),
CLOSE_WRITE | MOVED_TO | DELETE | DELETE_SELF);
mWallpaperDir = getWallpaperDir(wallpaper.userId);
mWallpaper = wallpaper;
mWallpaperFile = new File(mWallpaperDir, WALLPAPER);
+ mKeyguardWallpaper = keyguardWallpaper;
+ mKeyguardWallpaperFile = new File(mWallpaperDir, KEYGUARD_WALLPAPER);
}
@Override
@@ -128,14 +134,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
return;
}
synchronized (mLock) {
- // changing the wallpaper means we'll need to back up the new one
- long origId = Binder.clearCallingIdentity();
- BackupManager bm = new BackupManager(mContext);
- bm.dataChanged();
- Binder.restoreCallingIdentity(origId);
-
File changedFile = new File(mWallpaperDir, path);
if (mWallpaperFile.equals(changedFile)) {
+ // changing the wallpaper means we'll need to back up the new one
+ long origId = Binder.clearCallingIdentity();
+ BackupManager bm = new BackupManager(mContext);
+ bm.dataChanged();
+ Binder.restoreCallingIdentity(origId);
+
notifyCallbacksLocked(mWallpaper);
final boolean written = (event == CLOSE_WRITE || event == MOVED_TO);
if (mWallpaper.wallpaperComponent == null
@@ -148,6 +154,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
false, mWallpaper, null);
saveSettingsLocked(mWallpaper);
}
+ } else if (mKeyguardWallpaperFile.equals(changedFile)) {
+ notifyCallbacksLocked(mKeyguardWallpaper);
+ if (event == CLOSE_WRITE
+ || mKeyguardWallpaper.imageWallpaperPending) {
+ mKeyguardWallpaper.imageWallpaperPending = false;
+ saveSettingsLocked(mKeyguardWallpaper);
+ }
}
}
}
@@ -166,6 +179,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
final ComponentName mImageWallpaper;
SparseArray<WallpaperData> mWallpaperMap = new SparseArray<WallpaperData>();
+ SparseArray<KeyguardWallpaperData> mKeyguardWallpaperMap
+ = new SparseArray<KeyguardWallpaperData>();
int mCurrentUserId;
@@ -217,6 +232,37 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
}
}
+ static class KeyguardWallpaperData {
+
+ int userId;
+
+ File wallpaperFile;
+
+ /**
+ * Client is currently writing a new image wallpaper.
+ */
+ boolean imageWallpaperPending;
+
+ /**
+ * Resource name if using a picture from the wallpaper gallery
+ */
+ String name = "";
+
+ /**
+ * List of callbacks registered they should each be notified when the wallpaper is changed.
+ */
+ private RemoteCallbackList<IWallpaperManagerCallback> callbacks
+ = new RemoteCallbackList<IWallpaperManagerCallback>();
+
+ int width = -1;
+ int height = -1;
+
+ KeyguardWallpaperData(int userId) {
+ this.userId = userId;
+ wallpaperFile = new File(getWallpaperDir(userId), KEYGUARD_WALLPAPER);
+ }
+ }
+
class WallpaperConnection extends IWallpaperConnection.Stub
implements ServiceConnection {
final WallpaperInfo mInfo;
@@ -471,6 +517,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
mMonitor.register(context, null, UserHandle.ALL, true);
getWallpaperDir(UserHandle.USER_OWNER).mkdirs();
loadSettingsLocked(UserHandle.USER_OWNER);
+ loadKeyguardSettingsLocked(UserHandle.USER_OWNER);
}
private static File getWallpaperDir(int userId) {
@@ -489,8 +536,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
public void systemRunning() {
if (DEBUG) Slog.v(TAG, "systemReady");
WallpaperData wallpaper = mWallpaperMap.get(UserHandle.USER_OWNER);
+ KeyguardWallpaperData keyguardWallpaper = mKeyguardWallpaperMap.get(UserHandle.USER_OWNER);
switchWallpaper(wallpaper, null);
- wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper);
+ wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper, keyguardWallpaper);
wallpaper.wallpaperObserver.startWatching();
IntentFilter userFilter = new IntentFilter();
@@ -552,6 +600,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
wallpaper.wallpaperObserver = null;
}
mWallpaperMap.remove(userId);
+ mKeyguardWallpaperMap.remove(userId);
}
}
}
@@ -564,6 +613,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
wallpaperFile.delete();
File wallpaperInfoFile = new File(getWallpaperDir(userId), WALLPAPER_INFO);
wallpaperInfoFile.delete();
+ File keyguardWallpaperFile = new File(getWallpaperDir(userId), KEYGUARD_WALLPAPER);
+ keyguardWallpaperFile.delete();
+ File keyguardWallpaperInfoFile = new File(getWallpaperDir(userId),
+ KEYGUARD_WALLPAPER_INFO);
+ keyguardWallpaperInfoFile.delete();
}
}
@@ -576,9 +630,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
mWallpaperMap.put(userId, wallpaper);
loadSettingsLocked(userId);
}
+ KeyguardWallpaperData keygaurdWallpaper = mKeyguardWallpaperMap.get(userId);
+ if (keygaurdWallpaper == null) {
+ keygaurdWallpaper = new KeyguardWallpaperData(userId);
+ mKeyguardWallpaperMap.put(userId, keygaurdWallpaper);
+ loadKeyguardSettingsLocked(userId);
+ }
// Not started watching yet, in case wallpaper data was loaded for other reasons.
if (wallpaper.wallpaperObserver == null) {
- wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper);
+ wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper, keygaurdWallpaper);
wallpaper.wallpaperObserver.startWatching();
}
switchWallpaper(wallpaper, reply);
@@ -647,6 +707,40 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
}
}
+ /**
+ * @hide
+ */
+ public void clearKeyguardWallpaper() {
+ if (DEBUG) Slog.v(TAG, "clearWallpaper");
+ synchronized (mLock) {
+ clearKeyguardWallpaperLocked(UserHandle.getCallingUserId(), null);
+ }
+ }
+
+ void clearKeyguardWallpaperLocked(int userId, IRemoteCallback reply) {
+ KeyguardWallpaperData wallpaper = mKeyguardWallpaperMap.get(userId);
+ final long ident = Binder.clearCallingIdentity();
+ wallpaper.imageWallpaperPending = false;
+ wallpaper.height = -1;
+ wallpaper.width = -1;
+ wallpaper.name = "";
+
+ File f = new File(getWallpaperDir(userId), KEYGUARD_WALLPAPER);
+ if (f.exists()) {
+ f.delete();
+ }
+ if (userId != mCurrentUserId)
+ return;
+ Binder.restoreCallingIdentity(ident);
+
+ if (reply != null) {
+ try {
+ reply.sendResult(null);
+ } catch (RemoteException e1) {
+ }
+ }
+ }
+
public boolean hasNamedWallpaper(String name) {
synchronized (mLock) {
List<UserInfo> users;
@@ -799,6 +893,39 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
}
}
+ /** @hide */
+ public ParcelFileDescriptor getKeyguardWallpaper(IWallpaperManagerCallback cb,
+ Bundle outParams) {
+ synchronized (mLock) {
+ // This returns the current user's wallpaper, if called by a system service. Else it
+ // returns the wallpaper for the calling user.
+ int callingUid = Binder.getCallingUid();
+ int wallpaperUserId = 0;
+ if (callingUid == android.os.Process.SYSTEM_UID) {
+ wallpaperUserId = mCurrentUserId;
+ } else {
+ wallpaperUserId = UserHandle.getUserId(callingUid);
+ }
+ KeyguardWallpaperData wallpaper = mKeyguardWallpaperMap.get(wallpaperUserId);
+ try {
+ if (outParams != null) {
+ outParams.putInt("width", wallpaper.width);
+ outParams.putInt("height", wallpaper.height);
+ }
+ wallpaper.callbacks.register(cb);
+ File f = new File(getWallpaperDir(wallpaperUserId), KEYGUARD_WALLPAPER);
+ if (!f.exists()) {
+ return null;
+ }
+ return ParcelFileDescriptor.open(f, MODE_READ_ONLY);
+ } catch (FileNotFoundException e) {
+ /* Shouldn't happen as we check to see if the file exists */
+ Slog.w(TAG, "Error getting wallpaper", e);
+ }
+ return null;
+ }
+ }
+
public WallpaperInfo getWallpaperInfo() {
int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
@@ -810,6 +937,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
}
}
+ /** @hide */
+ public boolean isKeyguardWallpaperSet() {
+ int userId = UserHandle.getCallingUserId();
+ synchronized (mLock) {
+ KeyguardWallpaperData data = mKeyguardWallpaperMap.get(userId);
+ return data.wallpaperFile.exists();
+ }
+ }
+
public ParcelFileDescriptor setWallpaper(String name) {
checkPermission(android.Manifest.permission.SET_WALLPAPER);
synchronized (mLock) {
@@ -857,6 +993,55 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
return null;
}
+ public ParcelFileDescriptor setKeyguardWallpaper(String name) {
+ checkPermission(android.Manifest.permission.SET_KEYGUARD_WALLPAPER);
+ synchronized (mLock) {
+ if (DEBUG) Slog.v(TAG, "setKeyguardWallpaper");
+ int userId = UserHandle.getCallingUserId();
+ KeyguardWallpaperData wallpaper = mKeyguardWallpaperMap.get(userId);
+ if (wallpaper == null) {
+ throw new IllegalStateException("Keyguard wallpaper not yet initialized for user "
+ + userId);
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ ParcelFileDescriptor pfd = updateKeyguardWallpaperBitmapLocked(name, wallpaper);
+ if (pfd != null) {
+ wallpaper.imageWallpaperPending = true;
+ }
+ return pfd;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ public ParcelFileDescriptor updateKeyguardWallpaperBitmapLocked(String name,
+ KeyguardWallpaperData wallpaper) {
+ if (name == null) name = "";
+ try {
+ File dir = getWallpaperDir(wallpaper.userId);
+ if (!dir.exists()) {
+ dir.mkdir();
+ FileUtils.setPermissions(
+ dir.getPath(),
+ FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+ -1, -1);
+ }
+ File file = new File(dir, KEYGUARD_WALLPAPER);
+ ParcelFileDescriptor fd = ParcelFileDescriptor.open(file,
+ MODE_CREATE|MODE_READ_WRITE|MODE_TRUNCATE);
+ if (!SELinux.restorecon(file)) {
+ return null;
+ }
+ wallpaper.name = name;
+ return fd;
+ } catch (FileNotFoundException e) {
+ Slog.w(TAG, "Error setting wallpaper", e);
+ }
+ return null;
+ }
+
public void setWallpaperComponent(ComponentName name) {
checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
synchronized (mLock) {
@@ -1079,6 +1264,22 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId));
}
+ private void notifyCallbacksLocked(KeyguardWallpaperData wallpaper) {
+ final int n = wallpaper.callbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ wallpaper.callbacks.getBroadcastItem(i).onKeyguardWallpaperChanged();
+ } catch (RemoteException e) {
+
+ // The RemoteCallbackList will take care of removing
+ // the dead object for us.
+ }
+ }
+ wallpaper.callbacks.finishBroadcast();
+ final Intent intent = new Intent(Intent.ACTION_KEYGUARD_WALLPAPER_CHANGED);
+ mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId));
+ }
+
private void checkPermission(String permission) {
if (PackageManager.PERMISSION_GRANTED!= mContext.checkCallingOrSelfPermission(permission)) {
throw new SecurityException("Access denied to process: " + Binder.getCallingPid()
@@ -1087,7 +1288,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
}
private static JournaledFile makeJournaledFile(int userId) {
- final String base = new File(getWallpaperDir(userId), WALLPAPER_INFO).getAbsolutePath();
+ return makeJournaledFile(WALLPAPER_INFO, userId);
+ }
+
+ private static JournaledFile makeJournaledFile(String name, int userId) {
+ final String base = new File(getWallpaperDir(userId), name).getAbsolutePath();
return new JournaledFile(new File(base), new File(base + ".tmp"));
}
@@ -1138,6 +1343,36 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
}
}
+ private void saveSettingsLocked(KeyguardWallpaperData wallpaper) {
+ JournaledFile journal = makeJournaledFile(KEYGUARD_WALLPAPER_INFO, wallpaper.userId);
+ FileOutputStream stream = null;
+ try {
+ stream = new FileOutputStream(journal.chooseForWrite(), false);
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(stream, "utf-8");
+ out.startDocument(null, true);
+
+ out.startTag(null, "kwp");
+ out.attribute(null, "width", Integer.toString(wallpaper.width));
+ out.attribute(null, "height", Integer.toString(wallpaper.height));
+ out.attribute(null, "name", wallpaper.name);
+ out.endTag(null, "kwp");
+
+ out.endDocument();
+ stream.close();
+ journal.commit();
+ } catch (IOException e) {
+ try {
+ if (stream != null) {
+ stream.close();
+ }
+ } catch (IOException ex) {
+ // Ignore
+ }
+ journal.rollback();
+ }
+ }
+
private void migrateFromOld() {
File oldWallpaper = new File(WallpaperBackupHelper.WALLPAPER_IMAGE_KEY);
File oldInfo = new File(WallpaperBackupHelper.WALLPAPER_INFO_KEY);
@@ -1253,6 +1488,84 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
}
}
+ private void loadKeyguardSettingsLocked(int userId) {
+ if (DEBUG) Slog.v(TAG, "loadKeyguardSettingsLocked");
+
+ JournaledFile journal = makeJournaledFile(KEYGUARD_WALLPAPER_INFO, userId);
+ FileInputStream stream = null;
+ File file = journal.chooseForRead();
+ if (!file.exists()) {
+ // This should only happen one time, when upgrading from a legacy system
+ migrateFromOld();
+ }
+ KeyguardWallpaperData keyguardWallpaper = mKeyguardWallpaperMap.get(userId);
+ if (keyguardWallpaper == null) {
+ keyguardWallpaper = new KeyguardWallpaperData(userId);
+ mKeyguardWallpaperMap.put(userId, keyguardWallpaper);
+ }
+ boolean success = false;
+ try {
+ stream = new FileInputStream(file);
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, null);
+
+ int type;
+ do {
+ type = parser.next();
+ if (type == XmlPullParser.START_TAG) {
+ String tag = parser.getName();
+ if ("kwp".equals(tag)) {
+ keyguardWallpaper.width = Integer.parseInt(parser.getAttributeValue(null,
+ "width"));
+ keyguardWallpaper.height = Integer.parseInt(parser
+ .getAttributeValue(null, "height"));
+ keyguardWallpaper.name = parser.getAttributeValue(null, "name");
+ if (DEBUG) {
+ Slog.v(TAG, "mWidth:" + keyguardWallpaper.width);
+ Slog.v(TAG, "mHeight:" + keyguardWallpaper.height);
+ Slog.v(TAG, "mName:" + keyguardWallpaper.name);
+ }
+ }
+ }
+ } while (type != XmlPullParser.END_DOCUMENT);
+ success = true;
+ } catch (FileNotFoundException e) {
+ Slog.w(TAG, "no current wallpaper -- first boot?");
+ } catch (NullPointerException e) {
+ Slog.w(TAG, "failed parsing " + file + " " + e);
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "failed parsing " + file + " " + e);
+ } catch (XmlPullParserException e) {
+ Slog.w(TAG, "failed parsing " + file + " " + e);
+ } catch (IOException e) {
+ Slog.w(TAG, "failed parsing " + file + " " + e);
+ } catch (IndexOutOfBoundsException e) {
+ Slog.w(TAG, "failed parsing " + file + " " + e);
+ }
+ try {
+ if (stream != null) {
+ stream.close();
+ }
+ } catch (IOException e) {
+ // Ignore
+ }
+
+ if (!success) {
+ keyguardWallpaper.width = -1;
+ keyguardWallpaper.height = -1;
+ keyguardWallpaper.name = "";
+ }
+
+ // We always want to have some reasonable width hint.
+ int baseSize = getMaximumSizeDimension();
+ if (keyguardWallpaper.width < baseSize) {
+ keyguardWallpaper.width = baseSize;
+ }
+ if (keyguardWallpaper.height < baseSize) {
+ keyguardWallpaper.height = baseSize;
+ }
+ }
+
private int getMaximumSizeDimension() {
WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
Display d = wm.getDefaultDisplay();
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index d737e7f1563..9927578724a 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -427,6 +427,34 @@ final class Session extends IWindowSession.Stub
}
}
+ /**
+ * @hide
+ */
+ public int getLastWallpaperX() {
+ synchronized(mService.mWindowMap) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ return mService.getLastWallpaperX();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public int getLastWallpaperY() {
+ synchronized(mService.mWindowMap) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ return mService.getLastWallpaperY();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
int z, Bundle extras, boolean sync) {
synchronized(mService.mWindowMap) {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index c92a9a2c8f0..17136908a67 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -90,6 +90,7 @@ public class WindowAnimator {
boolean mKeyguardGoingAway;
boolean mKeyguardGoingAwayToNotificationShade;
boolean mKeyguardGoingAwayDisableWindowAnimations;
+ boolean mKeyguardGoingAwayShowingMedia;
// forceHiding states.
static final int KEYGUARD_NOT_SHOWN = 0;
@@ -428,7 +429,7 @@ public class WindowAnimator {
if (!wallpaperInUnForceHiding && wallpaper != null
&& !mKeyguardGoingAwayDisableWindowAnimations) {
Animation a = mPolicy.createForceHideWallpaperExitAnimation(
- mKeyguardGoingAwayToNotificationShade);
+ mKeyguardGoingAwayToNotificationShade, mKeyguardGoingAwayShowingMedia);
if (a != null) {
WindowStateAnimator animator = wallpaper.mWinAnimator;
animator.setAnimation(a);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ea01d43472d..74b9a854af6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2053,6 +2053,36 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ public int getLastWallpaperX() {
+ int curTokenIndex = mWallpaperTokens.size();
+ while (curTokenIndex > 0) {
+ curTokenIndex--;
+ WindowToken token = mWallpaperTokens.get(curTokenIndex);
+ int curWallpaperIndex = token.windows.size();
+ while (curWallpaperIndex > 0) {
+ curWallpaperIndex--;
+ WindowState wallpaperWin = token.windows.get(curWallpaperIndex);
+ return wallpaperWin.mXOffset;
+ }
+ }
+ return -1;
+ }
+
+ public int getLastWallpaperY() {
+ int curTokenIndex = mWallpaperTokens.size();
+ while (curTokenIndex > 0) {
+ curTokenIndex--;
+ WindowToken token = mWallpaperTokens.get(curTokenIndex);
+ int curWallpaperIndex = token.windows.size();
+ while (curWallpaperIndex > 0) {
+ curWallpaperIndex--;
+ WindowState wallpaperWin = token.windows.get(curWallpaperIndex);
+ return wallpaperWin.mYOffset;
+ }
+ }
+ return -1;
+ }
+
boolean updateWallpaperOffsetLocked(WindowState wallpaperWin, int dw, int dh,
boolean sync) {
boolean changed = false;
@@ -5336,7 +5366,7 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public void keyguardGoingAway(boolean disableWindowAnimations,
- boolean keyguardGoingToNotificationShade) {
+ boolean keyguardGoingToNotificationShade, boolean keyguardShowingMedia) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires DISABLE_KEYGUARD permission");
@@ -5345,6 +5375,7 @@ public class WindowManagerService extends IWindowManager.Stub
mAnimator.mKeyguardGoingAway = true;
mAnimator.mKeyguardGoingAwayToNotificationShade = keyguardGoingToNotificationShade;
mAnimator.mKeyguardGoingAwayDisableWindowAnimations = disableWindowAnimations;
+ mAnimator.mKeyguardGoingAwayShowingMedia = keyguardShowingMedia;
requestTraversalLocked();
}
}
diff --git a/services/java/com/android/server/AppsFailureReceiver.java b/services/java/com/android/server/AppsFailureReceiver.java
new file mode 100644
index 00000000000..bebef9b4884
--- /dev/null
+++ b/services/java/com/android/server/AppsFailureReceiver.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2010, T-Mobile USA, Inc.
+ * Copyright (C) 2015 The CyanogenMod 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.server;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ThemeUtils;
+import android.content.res.ThemeConfig;
+import android.content.res.ThemeManager;
+import android.os.SystemClock;
+import android.provider.ThemesContract;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.android.internal.R;
+
+public class AppsFailureReceiver extends BroadcastReceiver {
+
+ private static final int FAILURES_THRESHOLD = 3;
+ private static final int EXPIRATION_TIME_IN_MILLISECONDS = 30000; // 30 seconds
+
+ private static final int THEME_RESET_NOTIFICATION_ID = 0x4641494C;
+
+ private int mFailuresCount = 0;
+ private long mStartTime = 0;
+
+ // This function implements the following logic.
+ // If after a theme was applied the number of application launch failures
+ // at any moment was equal to FAILURES_THRESHOLD
+ // in less than EXPIRATION_TIME_IN_MILLISECONDS
+ // the default theme is applied unconditionally.
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(Intent.ACTION_APP_FAILURE)) {
+ long currentTime = SystemClock.uptimeMillis();
+ String pkgName = intent.getStringExtra("package");
+ if (currentTime - mStartTime > EXPIRATION_TIME_IN_MILLISECONDS) {
+ // reset both the count and the timer
+ mStartTime = currentTime;
+ mFailuresCount = 0;
+ }
+ if (mFailuresCount <= FAILURES_THRESHOLD) {
+ mFailuresCount++;
+ if (mFailuresCount == FAILURES_THRESHOLD) {
+ // let the theme manager take care of getting us back on the default theme
+ ThemeManager tm =
+ (ThemeManager) context.getSystemService(Context.THEME_SERVICE);
+ List<String> components = new ArrayList<String>();
+ components.add(ThemesContract.ThemesColumns.MODIFIES_FONTS);
+ components.add(ThemesContract.ThemesColumns.MODIFIES_LAUNCHER);
+ components.add(ThemesContract.ThemesColumns.MODIFIES_ALARMS);
+ components.add(ThemesContract.ThemesColumns.MODIFIES_BOOT_ANIM);
+ components.add(ThemesContract.ThemesColumns.MODIFIES_ICONS);
+ components.add(ThemesContract.ThemesColumns.MODIFIES_LOCKSCREEN);
+ components.add(ThemesContract.ThemesColumns.MODIFIES_NOTIFICATIONS);
+ components.add(ThemesContract.ThemesColumns.MODIFIES_OVERLAYS);
+ components.add(ThemesContract.ThemesColumns.MODIFIES_RINGTONES);
+ components.add(ThemesContract.ThemesColumns.MODIFIES_STATUS_BAR);
+ components.add(ThemesContract.ThemesColumns.MODIFIES_NAVIGATION_BAR);
+ tm.requestThemeChange(ThemeConfig.SYSTEM_DEFAULT, components);
+ postThemeResetNotification(context);
+ }
+ }
+ } else if (action.equals(Intent.ACTION_APP_FAILURE_RESET)
+ || action.equals(ThemeUtils.ACTION_THEME_CHANGED)) {
+ mFailuresCount = 0;
+ mStartTime = SystemClock.uptimeMillis();
+ } else if (action.equals(Intent.ACTION_PACKAGE_ADDED) ||
+ action.equals(Intent.ACTION_PACKAGE_REMOVED)) {
+ mFailuresCount = 0;
+ mStartTime = SystemClock.uptimeMillis();
+ }
+ }
+
+ /**
+ * Posts a notification to let the user know their theme was reset
+ * @param context
+ */
+ private void postThemeResetNotification(Context context) {
+ NotificationManager nm =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ String title = context.getString(R.string.theme_reset_notification_title);
+ String body = context.getString(R.string.theme_reset_notification_body);
+ Notification notice = new Notification.Builder(context)
+ .setAutoCancel(true)
+ .setOngoing(false)
+ .setContentTitle(title)
+ .setContentText(body)
+ .setStyle(new Notification.BigTextStyle().bigText(body))
+ .setSmallIcon(android.R.drawable.stat_notify_error)
+ .setWhen(System.currentTimeMillis())
+ .setCategory(Notification.CATEGORY_SYSTEM)
+ .setPriority(Notification.PRIORITY_MAX)
+ .build();
+ nm.notify(THEME_RESET_NOTIFICATION_ID, notice);
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3dc1a52e2cf..8650e64cbcf 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -27,10 +27,13 @@ import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.content.pm.ThemeUtils;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.content.res.ThemeConfig;
import android.media.AudioService;
import android.media.tv.TvInputManager;
import android.os.Build;
@@ -529,6 +532,7 @@ public final class SystemServer {
LockSettingsService lockSettings = null;
AssetAtlasService atlas = null;
MediaRouterService mediaRouter = null;
+ ThemeService themeService = null;
// Bring up services needed for UI.
if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
@@ -967,6 +971,14 @@ public final class SystemServer {
mSystemServiceManager.startService(TvInputManagerService.class);
}
+ try {
+ Slog.i(TAG, "Theme Service");
+ themeService = new ThemeService(context);
+ ServiceManager.addService(Context.THEME_SERVICE, themeService);
+ } catch (Throwable e) {
+ reportWtf("starting Theme Service", e);
+ }
+
if (!disableNonCoreServices) {
try {
Slog.i(TAG, "Media Router Service");
@@ -1110,6 +1122,16 @@ public final class SystemServer {
reportWtf("making Display Manager Service ready", e);
}
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_APP_FAILURE);
+ filter.addAction(Intent.ACTION_APP_FAILURE_RESET);
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(ThemeUtils.ACTION_THEME_CHANGED);
+ filter.addCategory(Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE);
+ filter.addDataScheme("package");
+ context.registerReceiver(new AppsFailureReceiver(), filter);
+
// These are needed to propagate to the runnable below.
final MountService mountServiceF = mountService;
final NetworkManagementService networkManagementF = networkManagement;
@@ -1131,6 +1153,7 @@ public final class SystemServer {
final MediaRouterService mediaRouterF = mediaRouter;
final AudioService audioServiceF = audioService;
final MmsServiceBroker mmsServiceF = mmsService;
+ final ThemeService themeServiceF = themeService;
// We now tell the activity manager it is okay to run third party
// code. It will call back into us once it has gotten to the state
@@ -1265,6 +1288,17 @@ public final class SystemServer {
} catch (Throwable e) {
reportWtf("Notifying MmsService running", e);
}
+
+ try {
+ // now that the system is up, apply default theme if applicable
+ if (themeServiceF != null) themeServiceF.systemRunning();
+ ThemeConfig themeConfig =
+ ThemeConfig.getBootTheme(context.getContentResolver());
+ String iconPkg = themeConfig.getIconPackPkgName();
+ mPackageManagerService.updateIconMapping(iconPkg);
+ } catch (Throwable e) {
+ reportWtf("Icon Mapping failed", e);
+ }
}
});
}
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index 46c81b601a6..5f9932a07cd 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -586,6 +586,19 @@ public class MockContext extends Context {
/** {@hide} */
@Override
+ public Context createPackageContextAsUser(String packageName, String themePackageName,
+ int flags, UserHandle user) throws PackageManager.NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@hide} */
+ public Context createApplicationContext(ApplicationInfo application,
+ String themePackageName, int flags) throws PackageManager.NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@hide} */
+ @Override
public int getUserId() {
throw new UnsupportedOperationException();
}
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index b3f492eb1c8..8ec44631224 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -464,6 +464,27 @@ public class MockPackageManager extends PackageManager {
throw new UnsupportedOperationException();
}
+ /** @hide */
+ @Override
+ public Resources getThemedResourcesForApplication(ApplicationInfo app, String themePkgName)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ /** @hide */
+ @Override
+ public Resources getThemedResourcesForApplication(String appPackageName, String themePkgName)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ /** @hide */
+ @Override
+ public Resources getThemedResourcesForApplicationAsUser(String appPackageName,
+ String themePkgName, int userId) throws NameNotFoundException {
+ return null;
+ }
+
@Override
public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags) {
throw new UnsupportedOperationException();
@@ -801,4 +822,19 @@ public class MockPackageManager extends PackageManager {
public Drawable loadItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo) {
throw new UnsupportedOperationException();
}
+
+ /**
+ * @hide
+ */
+ public void updateIconMaps(String pkgName) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public int processThemeResources(String themePkgName) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/tests/ThemeTests/cm11_to_cm12/downgrade_to_cm11.sh b/tests/ThemeTests/cm11_to_cm12/downgrade_to_cm11.sh
new file mode 100755
index 00000000000..9f8a2c6e885
--- /dev/null
+++ b/tests/ThemeTests/cm11_to_cm12/downgrade_to_cm11.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+#****************************************************************************************
+# Run this script to move your CM12 device move back to a CM11 state. This is
+# useful when you want to manually test CM11 to CM12 upgrade without reflashing the device
+#***************************************************************************************
+
+#Delete all themes related data
+adb shell sqlite3 /data/data/com.android.providers.settings/databases/settings.db "DELETE FROM secure WHERE name='themeConfig'";
+adb shell sqlite3 /data/data/com.android.providers.settings/databases/settings.db "DELETE FROM secure WHERE name='theme_prev_boot_api_level'";
+adb shell sqlite3 /data/data/com.android.providers.settings/databases/settings.db "pragma user_version=115"
+adb shell sqlite3 /data/data/com.android.providers.settings/databases/settings.db "DELETE FROM secure WHERE name='default_theme_package'"
+adb shell sqlite3 /data/data/com.android.providers.settings/databases/settings.db "DELETE FROM secure WHERE name='default_theme_components'"
+
+#HEXO Config (Comment HOLO if you use this)
+adb shell sqlite3 /data/data/com.android.providers.settings/databases/settings.db "INSERT INTO secure (name,value) VALUES('themeConfig','{\"default\":{\"mOverlayPkgName\":\"com.cyngn.hexo\",\"mIconPkgName\":\"com.tung91.mianogen\",\"mFontPkgName\":\"bigwave.thyrus.darkuinte\"}}')";
+
+#HOLO Config (Comment HEXO if you use this)
+#adb shell sqlite3 /data/data/com.android.providers.settings/databases/settings.db "INSERT INTO secure (name,value) VALUES('themeConfig','{\"default\":{\"mOverlayPkgName\":\"holo\",\"mIconPkgName\":\"com.tung91.mianogen\",\"mFontPkgName\":\"bigwave.thyrus.darkuinte\"}}')";
+
+
+#Default Theme Package
+adb shell sqlite3 /data/data/com.android.providers.settings/databases/settings.db "INSERT INTO secure (name,value) VALUES('default_theme_package', 'com.cyngn.hexo')"
+adb shell 'sqlite3 /data/data/com.android.providers.settings/databases/settings.db "INSERT INTO secure (name,value) VALUES(\"default_theme_components\", \"mods_overlays\")"'
+
+#Print out the db
+adb shell sqlite3 /data/data/com.android.providers.settings/databases/settings.db "SELECT * from secure"
+
+
+#ThemesProvider's default theme is called "Holo"
+adb shell sqlite3 /data/data/org.cyanogenmod.themes.provider/databases/themes.db "UPDATE themes SET pkg_name='holo', title='Holo' WHERE pkg_name='system'"
+adb shell sqlite3 /data/data/org.cyanogenmod.themes.provider/databases/themes.db "pragma user_version=10"
+
+adb shell sqlite3 /data/data/org.cyanogenmod.themes.provider/databases/themes.db "SELECT * FROM themes"
diff --git a/tests/ThemesTest/Android.mk b/tests/ThemesTest/Android.mk
new file mode 100644
index 00000000000..faddddbade9
--- /dev/null
+++ b/tests/ThemesTest/Android.mk
@@ -0,0 +1,19 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := ThemeTest
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_CERTIFICATE := platform
+
+LOCAL_PRIVILEGED_MODULE := true
+
+include $(BUILD_PACKAGE)
+
diff --git a/tests/ThemesTest/AndroidManifest.xml b/tests/ThemesTest/AndroidManifest.xml
new file mode 100644
index 00000000000..1357abab46a
--- /dev/null
+++ b/tests/ThemesTest/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.themetests" >
+
+ <uses-permission android:name="android.permission.ACCESS_THEME_MANAGER" />
+ <uses-permission android:name="android.permission.READ_THEMES" />
+ <uses-permission android:name="android.permission.WRITE_THEMES" />
+
+ <application
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme" >
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/app_name" >
+ <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/ThemesTest/res/drawable-hdpi/test_wallpaper_thumb.png b/tests/ThemesTest/res/drawable-hdpi/test_wallpaper_thumb.png
new file mode 100644
index 00000000000..df92eb5c1b5
--- /dev/null
+++ b/tests/ThemesTest/res/drawable-hdpi/test_wallpaper_thumb.png
Binary files differ
diff --git a/tests/ThemesTest/res/layout/activity_main.xml b/tests/ThemesTest/res/layout/activity_main.xml
new file mode 100644
index 00000000000..d6098ba5b23
--- /dev/null
+++ b/tests/ThemesTest/res/layout/activity_main.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ListView
+ android:id="@+id/theme_list"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:divider="@android:color/transparent"
+ android:dividerHeight="0dp"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:orientation="horizontal"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="center"/>
+
+ <ImageView
+ android:id="@+id/image"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="center"/>
+
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/detach_assets"
+ android:layout_width="match_parent"
+ android:layout_height="64dp"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center"
+ android:textSize="24dp"
+ android:text="@string/detach_assets"
+ android:enabled="false"/>
+
+</LinearLayout>
diff --git a/tests/ThemesTest/res/layout/theme_list_item.xml b/tests/ThemesTest/res/layout/theme_list_item.xml
new file mode 100644
index 00000000000..f7b56c2d7d2
--- /dev/null
+++ b/tests/ThemesTest/res/layout/theme_list_item.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="64dp">
+
+ <TextView
+ android:id="@+id/theme_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:layout_gravity="center_horizontal"
+ android:textSize="48dp" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/tests/ThemesTest/res/values-v11/styles.xml b/tests/ThemesTest/res/values-v11/styles.xml
new file mode 100644
index 00000000000..8e4857c0694
--- /dev/null
+++ b/tests/ThemesTest/res/values-v11/styles.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2013 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>
+
+ <!--
+ Base application theme for API 11+. This theme completely replaces
+ AppBaseTheme from res/values/styles.xml on API 11+ devices.
+ -->
+ <style name="AppBaseTheme" parent="android:Theme.Holo">
+ <!-- API 11 theme customizations can go here. -->
+ </style>
+
+</resources> \ No newline at end of file
diff --git a/tests/ThemesTest/res/values-v21/styles.xml b/tests/ThemesTest/res/values-v21/styles.xml
new file mode 100644
index 00000000000..0454ba1f06e
--- /dev/null
+++ b/tests/ThemesTest/res/values-v21/styles.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2013 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>
+
+ <!--
+ Base application theme for API 21+. This theme completely replaces
+ AppBaseTheme from BOTH res/values/styles.xml and
+ res/values-v11/styles.xml on API 14+ devices.
+ -->
+ <style name="AppBaseTheme" parent="android:Theme.Material">
+ <!-- API 14 theme customizations can go here. -->
+ </style>
+
+</resources> \ No newline at end of file
diff --git a/tests/ThemesTest/res/values/strings.xml b/tests/ThemesTest/res/values/strings.xml
new file mode 100644
index 00000000000..59bebfcc12f
--- /dev/null
+++ b/tests/ThemesTest/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 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_name">Theme Test</string>
+
+ <string name="detach_assets">Detach theme assets</string>
+</resources>
diff --git a/tests/ThemesTest/res/values/styles.xml b/tests/ThemesTest/res/values/styles.xml
new file mode 100644
index 00000000000..501678aea90
--- /dev/null
+++ b/tests/ThemesTest/res/values/styles.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2013 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>
+
+ <!--
+ Base application theme, dependent on API level. This theme is replaced
+ by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+ -->
+ <style name="AppBaseTheme" parent="android:Theme">
+ <!--
+ Theme customizations available in newer API levels can go in
+ res/values-vXX/styles.xml, while customizations related to
+ backward-compatibility can go here.
+ -->
+ </style>
+
+ <!-- Application theme. -->
+ <style name="AppTheme" parent="AppBaseTheme">
+ <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+ </style>
+
+</resources> \ No newline at end of file
diff --git a/tests/ThemesTest/src/com/example/themetests/MainActivity.java b/tests/ThemesTest/src/com/example/themetests/MainActivity.java
new file mode 100644
index 00000000000..430c997bdc0
--- /dev/null
+++ b/tests/ThemesTest/src/com/example/themetests/MainActivity.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright 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.example.themetests;
+
+import android.app.Activity;
+import android.app.ComposedIconInfo;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ThemeUtils;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.ThemeConfig;
+import android.database.Cursor;
+import android.os.Bundle;
+
+import android.os.ServiceManager;
+import android.provider.ThemesContract.ThemesColumns;
+import android.text.TextUtils;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.CursorAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.List;
+
+public class MainActivity extends Activity {
+ private static final String TAG = "MainActivity";
+
+ private ListView mThemeList;
+ private Button mDetachButton;
+ private ImageView mImage;
+ private ImageView mIcon;
+
+ private Resources mResources;
+ private AssetManager mAssets;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ mThemeList = (ListView) findViewById(R.id.theme_list);
+ mDetachButton = (Button) findViewById(R.id.detach_assets);
+ mDetachButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ detachThemeAssets(mResources, mAssets);
+ mThemeList.setEnabled(true);
+ mDetachButton.setEnabled(false);
+ updateImages();
+ }
+ });
+ mImage = (ImageView) findViewById(R.id.image);
+ mIcon = (ImageView) findViewById(R.id.icon);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ PackageManager pm = getPackageManager();
+ Context ctx = null;
+ try {
+ ctx = createPackageContext("com.android.systemui", 0);
+ mAssets = ctx.getAssets();
+ mResources = ctx.getResources();
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+
+ updateImages();
+ loadThemes();
+ }
+
+ private void loadThemes() {
+ String[] columns = {ThemesColumns._ID, ThemesColumns.TITLE, ThemesColumns.PKG_NAME};
+ String selection = ThemesColumns.PRESENT_AS_THEME + "=? AND " +
+ ThemesColumns.PKG_NAME + "<>?";
+ String[] selectionArgs = {"1", ThemeConfig.SYSTEM_DEFAULT};
+ Cursor c = getContentResolver().query(ThemesColumns.CONTENT_URI, columns, selection,
+ selectionArgs, null);
+ if (c != null) {
+ ThemeAdapter adapter = new ThemeAdapter(this, c, 0);
+ mThemeList.setAdapter(adapter);
+ mThemeList.setOnItemClickListener(mThemeClicked);
+ }
+ }
+
+ private boolean attachThemeAssets(Resources res, AssetManager assets, String pkgName) {
+ final PackageManager pm = getPackageManager();
+ PackageInfo piTheme = null;
+ PackageInfo piAndroid = null;
+ PackageInfo piTarget = null;
+ PackageInfo piIcon = null;
+
+ String basePackageName = null;
+ int count = assets.getBasePackageCount();
+ if (count > 1) {
+ basePackageName = assets.getBasePackageName(1);
+ } else if (count == 1) {
+ basePackageName = assets.getBasePackageName(0);
+ } else {
+ return false;
+ }
+
+ try {
+ piTheme = pm.getPackageInfo(pkgName, 0);
+ piAndroid = getPackageManager().getPackageInfo("android", 0);
+ piTarget = getPackageManager().getPackageInfo(basePackageName, 0);
+ piIcon = getPackageManager().getPackageInfo(pkgName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+
+ if (piTheme == null || piTheme.applicationInfo == null ||
+ piAndroid == null || piAndroid.applicationInfo == null ||
+ piTheme.mOverlayTargets == null) {
+ return false;
+ }
+
+ String themePackageName = pkgName;
+ String themePath = piTheme.applicationInfo.publicSourceDir;
+ if (!piTarget.isThemeApk && piTheme.mOverlayTargets.contains(basePackageName)) {
+ String targetPackagePath = piTarget.applicationInfo.sourceDir;
+ String prefixPath = ThemeUtils.getOverlayPathToTarget(basePackageName);
+
+ String resCachePath = ThemeUtils.getTargetCacheDir(basePackageName, piTheme);
+ String resTablePath = resCachePath + "/resources.arsc";
+ String resApkPath = resCachePath + "/resources.apk";
+ int cookie = assets.addOverlayPath(themePath, resTablePath, resApkPath,
+ targetPackagePath, prefixPath);
+
+ if (cookie != 0) {
+ assets.setThemePackageName(themePackageName);
+ assets.addThemeCookie(cookie);
+ }
+ }
+
+ if (!piTarget.isThemeApk && piTheme.mOverlayTargets.contains("android")) {
+ String resCachePath= ThemeUtils.getTargetCacheDir(piAndroid.packageName, piTheme);
+ String prefixPath = ThemeUtils.getOverlayPathToTarget(piAndroid.packageName);
+ String targetPackagePath = piAndroid.applicationInfo.publicSourceDir;
+ String resTablePath = resCachePath + "/resources.arsc";
+ String resApkPath = resCachePath + "/resources.apk";
+ int cookie = assets.addOverlayPath(themePath, resTablePath,
+ resApkPath, targetPackagePath, prefixPath);
+ if (cookie != 0 && !assets.getThemeCookies().contains(cookie)) {
+ assets.setThemePackageName(themePackageName);
+ assets.addThemeCookie(cookie);
+ }
+ }
+
+ if (piIcon != null) {
+ String themeIconPath = piIcon.applicationInfo.publicSourceDir;
+ String prefixPath = ThemeUtils.ICONS_PATH;
+ String iconDir = ThemeUtils.getIconPackDir(pkgName);
+ String resTablePath = iconDir + "/resources.arsc";
+ String resApkPath = iconDir + "/resources.apk";
+
+ // Legacy Icon packs have everything in their APK
+ if (piIcon.isLegacyIconPackApk) {
+ prefixPath = "";
+ resApkPath = "";
+ resTablePath = "";
+ }
+
+ int cookie = assets.addIconPath(themeIconPath, resTablePath, resApkPath, prefixPath,
+ Resources.THEME_ICON_PKG_ID);
+ if (cookie != 0) {
+ assets.setIconPackCookie(cookie);
+ assets.setIconPackageName(pkgName);
+ setActivityIcons(res);
+ }
+ }
+
+ res.updateStringCache();
+ return true;
+ }
+
+ private void detachThemeAssets(Resources res, AssetManager assets) {
+ String themePackageName = assets.getThemePackageName();
+ String iconPackageName = assets.getIconPackageName();
+ String commonResPackageName = assets.getCommonResPackageName();
+
+ //Remove Icon pack if it exists
+ if (!TextUtils.isEmpty(iconPackageName) && assets.getIconPackCookie() > 0) {
+ assets.removeOverlayPath(iconPackageName, assets.getIconPackCookie());
+ assets.setIconPackageName(null);
+ assets.setIconPackCookie(0);
+ }
+ //Remove common resources if it exists
+ if (!TextUtils.isEmpty(commonResPackageName) && assets.getCommonResCookie() > 0) {
+ assets.removeOverlayPath(commonResPackageName, assets.getCommonResCookie());
+ assets.setCommonResPackageName(null);
+ assets.setCommonResCookie(0);
+ }
+ final List<Integer> themeCookies = assets.getThemeCookies();
+ if (!TextUtils.isEmpty(themePackageName) && !themeCookies.isEmpty()) {
+ // remove overlays in reverse order
+ for (int i = themeCookies.size() - 1; i >= 0; i--) {
+ assets.removeOverlayPath(themePackageName, themeCookies.get(i));
+ }
+ }
+ assets.getThemeCookies().clear();
+ assets.setThemePackageName(null);
+ //res.updateStringCache();
+ }
+
+ private void setActivityIcons(Resources r, String themePkgName) {
+ SparseArray<PackageItemInfo> iconResources = new SparseArray<PackageItemInfo>();
+ String pkgName = r.getAssets().getAppName();
+ PackageInfo pkgInfo = null;
+ ApplicationInfo appInfo = null;
+
+ try {
+ pkgInfo = getPackageManager().getPackageInfo(pkgName, PackageManager.GET_ACTIVITIES);
+ } catch (PackageManager.NameNotFoundException e) {
+ return;
+ }
+
+ final ThemeConfig themeConfig = r.getConfiguration().themeConfig;
+ if (pkgName != null && themeConfig != null &&
+ pkgName.equals(themeConfig.getIconPackPkgName())) {
+ return;
+ }
+
+ //Map application icon
+ if (pkgInfo != null && pkgInfo.applicationInfo != null) {
+ appInfo = pkgInfo.applicationInfo;
+ if (appInfo.themedIcon != 0 || iconResources.get(appInfo.icon) == null) {
+ iconResources.put(appInfo.icon, appInfo);
+ }
+ }
+
+ //Map activity icons.
+ if (pkgInfo != null && pkgInfo.activities != null) {
+ for (ActivityInfo ai : pkgInfo.activities) {
+ if (ai.icon != 0 && (ai.themedIcon != 0 || iconResources.get(ai.icon) == null)) {
+ iconResources.put(ai.icon, ai);
+ } else if (appInfo != null && appInfo.icon != 0 &&
+ (ai.themedIcon != 0 || iconResources.get(appInfo.icon) == null)) {
+ iconResources.put(appInfo.icon, ai);
+ }
+ }
+ }
+
+ r.setIconResources(iconResources);
+ final IPackageManager pm = IPackageManager.Stub.asInterface(
+ ServiceManager.getService("package"));
+ try {
+ ComposedIconInfo iconInfo = pm.getComposedIconInfo();
+ r.setComposedIconInfo(iconInfo);
+ } catch (Exception e) {
+ }
+ }
+
+ AdapterView.OnItemClickListener mThemeClicked = new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ String pkgName = (String) view.getTag();
+ if (attachThemeAssets(mResources, mAssets, pkgName)) {
+ mThemeList.setEnabled(false);
+ mDetachButton.setEnabled(true);
+ updateImages();
+ }
+ }
+ };
+
+ private void updateImages() {
+ int resId = mResources.getIdentifier("ic_sysbar_home", "drawable", "com.android.systemui");
+ if (resId != 0) {
+ mImage.setImageDrawable(mResources.getDrawable(resId));
+ }
+ resId = mResources.getIdentifier("icon", "drawable", "com.android.systemui");
+ if (resId != 0) {
+ mIcon.setImageDrawable(mResources.getDrawable(resId));
+ }
+ }
+
+ class ThemeAdapter extends CursorAdapter {
+ public ThemeAdapter(Context context, Cursor c, int flags) {
+ super(context, c, flags);
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ LayoutInflater inflater = getLayoutInflater();
+ View v = inflater.inflate(R.layout.theme_list_item, parent, false);
+ return v;
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ int titleIdx = cursor.getColumnIndex(ThemesColumns.TITLE);
+ int pkgIdx = cursor.getColumnIndex(ThemesColumns.PKG_NAME);
+ String title = cursor.getString(titleIdx);
+ String pkgName = cursor.getString(pkgIdx);
+ TextView tv = (TextView) view.findViewById(R.id.theme_title);
+ tv.setText(title);
+ view.setTag(pkgName);
+ }
+ }
+}
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp
index 117fc243507..ad4bc31abb5 100644
--- a/tools/aapt/AaptAssets.cpp
+++ b/tools/aapt/AaptAssets.cpp
@@ -1075,8 +1075,23 @@ ssize_t AaptAssets::slurpFromArgs(Bundle* bundle)
goto bail;
}
totalCount += count;
- }
- else {
+ } else if (type == kFileTypeRegular) {
+ ZipFile* zip = new ZipFile;
+ status_t err = zip->open(String8(res), ZipFile::kOpenReadOnly);
+ if (err != NO_ERROR) {
+ fprintf(stderr, "error opening zip file %s\n", res);
+ delete zip;
+ totalCount = -1;
+ goto bail;
+ }
+
+ count = current->slurpResourceZip(bundle, zip, res);
+ delete zip;
+ if (count < 0) {
+ totalCount = count;
+ goto bail;
+ }
+ } else {
fprintf(stderr, "ERROR: '%s' is not a directory\n", res);
return UNKNOWN_ERROR;
}
@@ -1240,96 +1255,91 @@ bail:
}
ssize_t
-AaptAssets::slurpResourceZip(Bundle* bundle, const char* filename)
+AaptAssets::slurpResourceZip(Bundle* bundle, ZipFile* zip, const char* fullZipPath)
{
+ status_t err = NO_ERROR;
int count = 0;
SortedVector<AaptGroupEntry> entries;
- ZipFile* zip = new ZipFile;
- status_t err = zip->open(filename, ZipFile::kOpenReadOnly);
- if (err != NO_ERROR) {
- fprintf(stderr, "error opening zip file %s\n", filename);
- count = err;
- delete zip;
- return -1;
- }
-
const int N = zip->getNumEntries();
for (int i=0; i<N; i++) {
ZipEntry* entry = zip->getEntryByIndex(i);
- if (entry->getDeleted()) {
+
+ if (!isEntryValid(bundle, entry)) {
continue;
}
- String8 entryName(entry->getFileName());
+ String8 entryName(entry->getFileName()); //ex: /res/drawable/foo.png
+ String8 entryLeaf = entryName.getPathLeaf(); //ex: foo.png
+ String8 entryDirFull = entryName.getPathDir(); //ex: res/drawable
+ String8 entryDir = entryDirFull.getPathLeaf(); //ex: drawable
- String8 dirName = entryName.getPathDir();
- sp<AaptDir> dir = dirName == "" ? this : makeDir(dirName);
+ err = addEntry(entryName, entryLeaf, entryDirFull, entryDir, String8(fullZipPath), 0);
+ if (err) continue;
- String8 resType;
- AaptGroupEntry kind;
+ count++;
+ }
- String8 remain;
- if (entryName.walkPath(&remain) == kResourceDir) {
- // these are the resources, pull their type out of the directory name
- kind.initFromDirName(remain.walkPath().string(), &resType);
- } else {
- // these are untyped and don't have an AaptGroupEntry
- }
- if (entries.indexOf(kind) < 0) {
- entries.add(kind);
- mGroupEntries.add(kind);
- }
+bail:
+ return count;
+}
+
+status_t
+AaptAssets::addEntry(const String8& entryName, const String8& entryLeaf,
+ const String8& entryDirFull, const String8& entryDir,
+ const String8& zipFile, int compressionMethod)
+{
+ AaptGroupEntry group;
+ String8 resType;
+ bool b = group.initFromDirName(entryDir, &resType);
+ if (!b) {
+ fprintf(stderr, "invalid resource directory name: %s\n", entryDir.string());
+ return -1;
+ }
- // use the one from the zip file if they both exist.
- dir->removeFile(entryName.getPathLeaf());
+ //This will do a cached lookup as well
+ sp<AaptDir> dir = makeDir(resType); //Does lookup as well on mdirs
+ sp<AaptFile> file = new AaptFile(entryName, group, resType, zipFile);
+ file->setCompressionMethod(compressionMethod);
- sp<AaptFile> file = new AaptFile(entryName, kind, resType);
- status_t err = dir->addLeafFile(entryName.getPathLeaf(), file);
- if (err != NO_ERROR) {
- fprintf(stderr, "err=%s entryName=%s\n", strerror(err), entryName.string());
- count = err;
- goto bail;
- }
- file->setCompressionMethod(entry->getCompressionMethod());
+ dir->addLeafFile(entryLeaf, file);
-#if 0
- if (entryName == "AndroidManifest.xml") {
- printf("AndroidManifest.xml\n");
- }
- printf("\n\nfile: %s\n", entryName.string());
-#endif
-
- size_t len = entry->getUncompressedLen();
- void* data = zip->uncompress(entry);
- void* buf = file->editData(len);
- memcpy(buf, data, len);
-
-#if 0
- const int OFF = 0;
- const unsigned char* p = (unsigned char*)data;
- const unsigned char* end = p+len;
- p += OFF;
- for (int i=0; i<32 && p < end; i++) {
- printf("0x%03x ", i*0x10 + OFF);
- for (int j=0; j<0x10 && p < end; j++) {
- printf(" %02x", *p);
- p++;
- }
- printf("\n");
- }
-#endif
+ sp<AaptDir> rdir = resDir(resType);
+ if (rdir == NULL) {
+ mResDirs.add(dir);
+ }
- free(data);
+ return NO_ERROR;
+}
- count++;
+bool AaptAssets::isEntryValid(Bundle* bundle, ZipEntry* entry) {
+ if (entry == NULL) {
+ return false;
}
-bail:
- delete zip;
- return count;
+ if (entry->getDeleted()) {
+ return false;
+ }
+
+ // Entries that are not inside the internal zip path can be ignored
+ if (bundle->getInternalZipPath()) {
+ bool prefixed = (strncmp(entry->getFileName(),
+ bundle->getInternalZipPath(),
+ strlen(bundle->getInternalZipPath())) == 0);
+ if (!prefixed) {
+ return false;
+ }
+ }
+
+ //Do not process directories
+ if (String8(entry->getFileName()).size() == 0) {
+ return false;
+ }
+
+ return true;
}
+
status_t AaptAssets::filter(Bundle* bundle)
{
WeakResourceFilter reqFilter;
@@ -1556,7 +1566,7 @@ status_t AaptAssets::buildIncludedResources(Bundle* bundle)
printf("Including resources from package: %s\n", includes[i].string());
}
- if (!mIncludedAssets.addAssetPath(includes[i], NULL)) {
+ if (!mIncludedAssets.addAssetPath(includes[i], 0)) {
fprintf(stderr, "ERROR: Asset package include '%s' not found.\n",
includes[i].string());
return UNKNOWN_ERROR;
diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h
index d809c5b3003..213c3523c72 100644
--- a/tools/aapt/AaptAssets.h
+++ b/tools/aapt/AaptAssets.h
@@ -27,6 +27,8 @@ using namespace android;
extern const char * const gDefaultIgnoreAssets;
extern const char * gUserIgnoreAssets;
+extern bool endsWith(const char* haystack, const char* needle);
+
bool valid_symbol_name(const String8& str);
class AaptAssets;
@@ -148,7 +150,7 @@ class AaptFile : public RefBase
{
public:
AaptFile(const String8& sourceFile, const AaptGroupEntry& groupEntry,
- const String8& resType)
+ const String8& resType, const String8& zipFile=String8(""))
: mGroupEntry(groupEntry)
, mResourceType(resType)
, mSourceFile(sourceFile)
@@ -156,9 +158,11 @@ public:
, mDataSize(0)
, mBufferSize(0)
, mCompression(ZipEntry::kCompressStored)
+ , mZipFile(zipFile)
{
//printf("new AaptFile created %s\n", (const char*)sourceFile);
}
+
virtual ~AaptFile() {
free(mData);
}
@@ -190,6 +194,12 @@ public:
// no compression is ZipEntry::kCompressStored.
int getCompressionMethod() const { return mCompression; }
void setCompressionMethod(int c) { mCompression = c; }
+
+ // ZIP support. In this case the sourceFile is the zip entry name
+ // and zipFile is the path to the zip File.
+ // example: sourceFile = drawable-hdpi/foo.png, zipFile = res.zip
+ const String8& getZipFile() const { return mZipFile; }
+
private:
friend class AaptGroup;
@@ -201,6 +211,7 @@ private:
size_t mDataSize;
size_t mBufferSize;
int mCompression;
+ String8 mZipFile;
};
/**
@@ -542,6 +553,8 @@ public:
void addGroupEntry(const AaptGroupEntry& entry) { mGroupEntries.add(entry); }
ssize_t slurpFromArgs(Bundle* bundle);
+ ssize_t slurpResourceZip(Bundle* bundle, ZipFile* zip, const char* fullZipPath);
+ bool isEntryValid(Bundle* bundle, ZipEntry* entry);
sp<AaptSymbols> getSymbolsFor(const String8& name);
@@ -594,7 +607,11 @@ private:
sp<FilePathStore>& fullResPaths);
ssize_t slurpResourceTree(Bundle* bundle, const String8& srcDir);
- ssize_t slurpResourceZip(Bundle* bundle, const char* filename);
+
+
+ status_t addEntry(const String8& entryName, const String8& entryLeaf,
+ const String8& entryDirFull, const String8& entryDir,
+ const String8& zipFile, int compressionMethod);
status_t filter(Bundle* bundle);
diff --git a/tools/aapt/AaptConfig.cpp b/tools/aapt/AaptConfig.cpp
index 32a0cd3d487..906532e68a4 100644
--- a/tools/aapt/AaptConfig.cpp
+++ b/tools/aapt/AaptConfig.cpp
@@ -215,9 +215,20 @@ bool parse(const String8& str, ConfigDescription* out) {
success:
if (out != NULL) {
+#ifndef HAVE_ANDROID_OS
applyVersionForCompatibility(&config);
+#else
+ // Calling applyVersionForCompatibility when compiling a theme can cause
+ // the path to be changed by AAPT which results in the themed assets not being
+ // loaded. The only time (as of right now) that aapt is run on an android device
+ // is when it is being used for themes, so this should be the correct behavior
+ // in this case. If AAPT is ever used on an android device for some other reason,
+ // we will need to change this.
+ printf("AAPT is running on Android, skipping applyVersionForCompatibility");
+#endif
*out = config;
}
+
return true;
}
diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk
index 2cbabe1cf27..74f661a251d 100644
--- a/tools/aapt/Android.mk
+++ b/tools/aapt/Android.mk
@@ -51,7 +51,8 @@ aaptSources := \
aaptTests := \
tests/AaptConfig_test.cpp \
tests/AaptGroupEntry_test.cpp \
- tests/ResourceFilter_test.cpp
+ tests/ResourceFilter_test.cpp \
+ tests/ZipReading_test.cpp \
aaptCIncludes := \
external/libpng \
@@ -115,6 +116,8 @@ LOCAL_STATIC_LIBRARIES += \
libaapt \
$(aaptHostStaticLibs)
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/mocks
+
LOCAL_LDLIBS += $(aaptHostLdLibs)
LOCAL_CFLAGS += $(aaptCFlags)
@@ -130,9 +133,11 @@ LOCAL_MODULE := libaapt_tests
LOCAL_SRC_FILES += $(aaptTests)
LOCAL_C_INCLUDES += $(LOCAL_PATH)
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/mocks
LOCAL_STATIC_LIBRARIES += \
libaapt \
+ libgmock \
$(aaptHostStaticLibs)
LOCAL_LDLIBS += $(aaptHostLdLibs)
diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h
index cb34448874a..b8422bd273e 100644
--- a/tools/aapt/Bundle.h
+++ b/tools/aapt/Bundle.h
@@ -1,5 +1,6 @@
//
// Copyright 2006 The Android Open Source Project
+// This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
//
// State bundle. Used to pass around stuff like command-line args.
//
@@ -60,7 +61,7 @@ public:
Bundle(void)
: mCmd(kCommandUnknown), mVerbose(false), mAndroidList(false),
mForce(false), mGrayscaleTolerance(0), mMakePackageDirs(false),
- mUpdate(false), mExtending(false),
+ mUpdate(false), mExtending(false), mExtendedPackageId(0),
mRequireLocalization(false), mPseudolocalize(NO_PSEUDOLOCALIZATION),
mWantUTF16(false), mValues(false), mIncludeMetaData(false),
mCompressionMethod(0), mJunkPath(false), mOutputAPKFile(NULL),
@@ -75,6 +76,8 @@ public:
mProduct(NULL), mUseCrunchCache(false), mErrorOnFailedInsert(false),
mErrorOnMissingConfigEntry(false), mOutputTextSymbols(NULL),
mSingleCrunchInputFile(NULL), mSingleCrunchOutputFile(NULL),
+ mOutputResourcesApkFile(NULL),
+ mInternalZipPath(NULL), mInputAPKFile(NULL),
mBuildSharedLibrary(false),
mArgc(0), mArgv(NULL)
{}
@@ -104,6 +107,8 @@ public:
void setUpdate(bool val) { mUpdate = val; }
bool getExtending(void) const { return mExtending; }
void setExtending(bool val) { mExtending = val; }
+ int getExtendedPackageId(void) const { return mExtendedPackageId; }
+ void setExtendedPackageId(int val) { mExtendedPackageId = val; }
bool getRequireLocalization(void) const { return mRequireLocalization; }
void setRequireLocalization(bool val) { mRequireLocalization = val; }
short getPseudolocalize(void) const { return mPseudolocalize; }
@@ -119,6 +124,10 @@ public:
void setJunkPath(bool val) { mJunkPath = val; }
const char* getOutputAPKFile() const { return mOutputAPKFile; }
void setOutputAPKFile(const char* val) { mOutputAPKFile = val; }
+ const char* getOutputResApk() { return mOutputResourcesApkFile; }
+ const char* getInputAPKFile() { return mInputAPKFile; }
+ void setInputAPKFile(const char* val) { mInputAPKFile = val; }
+ void setOutputResApk(const char* val) { mOutputResourcesApkFile = val; }
const char* getManifestPackageNameOverride() const { return mManifestPackageNameOverride; }
void setManifestPackageNameOverride(const char * val) { mManifestPackageNameOverride = val; }
const char* getInstrumentationPackageNameOverride() const { return mInstrumentationPackageNameOverride; }
@@ -210,6 +219,8 @@ public:
void setSingleCrunchInputFile(const char* val) { mSingleCrunchInputFile = val; }
const char* getSingleCrunchOutputFile() const { return mSingleCrunchOutputFile; }
void setSingleCrunchOutputFile(const char* val) { mSingleCrunchOutputFile = val; }
+ void setInternalZipPath(const char* val) { mInternalZipPath = val; }
+ const char* getInternalZipPath() const { return mInternalZipPath; }
bool getBuildSharedLibrary() const { return mBuildSharedLibrary; }
void setBuildSharedLibrary(bool val) { mBuildSharedLibrary = val; }
@@ -279,6 +290,7 @@ private:
bool mMakePackageDirs;
bool mUpdate;
bool mExtending;
+ int mExtendedPackageId;
bool mRequireLocalization;
short mPseudolocalize;
bool mWantUTF16;
@@ -327,6 +339,9 @@ private:
const char* mOutputTextSymbols;
const char* mSingleCrunchInputFile;
const char* mSingleCrunchOutputFile;
+ const char* mOutputResourcesApkFile;
+ const char* mInternalZipPath;
+ const char* mInputAPKFile;
bool mBuildSharedLibrary;
android::String8 mPlatformVersionCode;
android::String8 mPlatformVersionName;
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 1e9e3e20ddf..04882282477 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -2420,21 +2420,48 @@ int doPackage(Bundle* bundle)
goto bail;
}
- // Write the apk
- if (outputAPKFile) {
+ if (outputAPKFile || bundle->getOutputResApk()) {
// 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) {
goto bail;
}
+ }
+
+ //Write the res apk
+ if (bundle->getOutputResApk()) {
+ const char* resPath = bundle->getOutputResApk();
+ char *endptr;
+ int resApk_fd = strtol(resPath, &endptr, 10);
+
+ if (*endptr == '\0') {
+ //OutputResDir was a file descriptor
+ //Assume there is only one set of assets, when we deal with actual split apks this may have to change
+ err = writeAPK(bundle, resApk_fd, builder->getBaseSplit(), true);
+ } else {
+ //Assume there is only one set of assets, when we deal with actual split apks this may have to change
+ err = writeAPK(bundle, String8(bundle->getOutputResApk()), builder->getBaseSplit(), true);
+ }
+
+ if (err != NO_ERROR) {
+ fprintf(stderr, "ERROR: writing '%s' failed\n", resPath);
+ goto bail;
+ }
+ }
+
+ // Write the apk
+ if (outputAPKFile) {
+ if (err != NO_ERROR) {
+ 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);
+ err = writeAPK(bundle, outputPath, split, false);
if (err != NO_ERROR) {
fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputPath.string());
goto bail;
diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp
index 5d4a6ac8e0c..d24eda34b88 100644
--- a/tools/aapt/Images.cpp
+++ b/tools/aapt/Images.cpp
@@ -26,6 +26,15 @@ png_write_aapt_file(png_structp png_ptr, png_bytep data, png_size_t length)
}
}
+static void
+png_read_mem_file(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+ PngMemoryFile* pngFile = (PngMemoryFile*) png_get_io_ptr(png_ptr);
+ status_t err = pngFile->read(data, length);
+ if (err != NO_ERROR) {
+ png_error(png_ptr, "Read Error");
+ }
+}
static void
png_flush_aapt_file(png_structp png_ptr)
@@ -1236,29 +1245,39 @@ static void write_png(const char* imageName,
compression_type));
}
-status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets,
+status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets, //non-theme path
const sp<AaptFile>& file, String8* outNewLeafName)
{
String8 ext(file->getPath().getPathExtension());
+ bool isImageInZip = !file->getZipFile().isEmpty();
// We currently only process PNG images.
if (strcmp(ext.string(), ".png") != 0) {
return NO_ERROR;
}
+ String8 printableName(file->getPrintableSource());
+
+ // We currently only process nine patch PNG images when building a theme apk.
+ Bundle* b = const_cast<Bundle*>(bundle);
+ if (!endsWith(printableName.string(), ".9.png") && b->getOutputResApk() != NULL) {
+ if (bundle->getVerbose()) {
+ printf("Skipping image: %s\n", file->getPrintableSource().string());
+ }
+ return NO_ERROR;
+ }
+
// Example of renaming a file:
//*outNewLeafName = file->getPath().getBasePath().getFileName();
//outNewLeafName->append(".nupng");
- String8 printableName(file->getPrintableSource());
-
if (bundle->getVerbose()) {
printf("Processing image: %s\n", printableName.string());
}
png_structp read_ptr = NULL;
png_infop read_info = NULL;
- FILE* fp;
+ FILE* fp = NULL;
image_info imageInfo;
@@ -1269,11 +1288,6 @@ status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets,
const size_t nameLen = file->getPath().length();
- fp = fopen(file->getSourceFile().string(), "rb");
- if (fp == NULL) {
- fprintf(stderr, "%s: ERROR: Unable to open PNG file\n", printableName.string());
- goto bail;
- }
read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL,
(png_error_ptr)NULL);
@@ -1290,7 +1304,34 @@ status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets,
goto bail;
}
- png_init_io(read_ptr, fp);
+ if (isImageInZip) {
+ PngMemoryFile* pmf = new PngMemoryFile();
+
+ ZipFile* zip = new ZipFile;
+ status_t err = zip->open(file->getZipFile(), ZipFile::kOpenReadOnly);
+ if (NO_ERROR != err) {
+ fprintf(stderr, "ERROR: Unable to open %s\n", file->getZipFile().string());
+ return err;
+ }
+
+ ZipEntry* entry = zip->getEntryByName(file->getSourceFile().string());
+ size_t len = entry->getUncompressedLen();
+ void* data = zip->uncompress(entry);
+ void* buf = file->editData(len);
+ memcpy(buf, data, len);
+ free(data);
+
+ pmf->setDataSource((const char*)file->getData(), file->getSize());
+ png_set_read_fn(read_ptr, pmf, png_read_mem_file);
+ } else {
+ fp = fopen(file->getSourceFile().string(), "rb");
+ if (fp == NULL) {
+ fprintf(stderr, "%s: ERROR: Unable to open PNG file\n", printableName.string());
+ goto bail;
+ }
+ png_init_io(read_ptr, fp);
+ }
+
read_png(printableName.string(), read_ptr, read_info, &imageInfo);
@@ -1303,6 +1344,10 @@ status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets,
}
}
+ if (isImageInZip) {
+ file->clearData();
+ }
+
write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL,
(png_error_ptr)NULL);
if (!write_ptr)
@@ -1329,13 +1374,15 @@ status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets,
error = NO_ERROR;
- if (bundle->getVerbose()) {
+ if (bundle->getVerbose() && !isImageInZip) {
fseek(fp, 0, SEEK_END);
size_t oldSize = (size_t)ftell(fp);
size_t newSize = file->getSize();
float factor = ((float)newSize)/oldSize;
int percent = (int)(factor*100);
printf(" (processed image %s: %d%% size of source)\n", printableName.string(), percent);
+ } else if (bundle->getVerbose() && isImageInZip) {
+ printf(" (processed image %s)\n", printableName.string());
}
bail:
@@ -1497,3 +1544,17 @@ status_t postProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets,
return NO_ERROR;
}
+
+status_t PngMemoryFile::read(png_bytep data, png_size_t length) {
+ if (data == NULL)
+ return -1;
+
+ if ((mIndex + length) >= mDataSize) {
+ length = mDataSize - mIndex;
+ }
+
+ memcpy(data, mData + mIndex, length);
+ mIndex += length;
+
+ return NO_ERROR;
+}
diff --git a/tools/aapt/Images.h b/tools/aapt/Images.h
index a0a94f8b9d1..3230ddcd80f 100644
--- a/tools/aapt/Images.h
+++ b/tools/aapt/Images.h
@@ -10,6 +10,8 @@
#include "ResourceTable.h"
#include "Bundle.h"
+#include <png.h>
+
#include <utils/String8.h>
#include <utils/RefBase.h>
@@ -23,4 +25,18 @@ status_t preProcessImageToCache(const Bundle* bundle, const String8& source, con
status_t postProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets,
ResourceTable* table, const sp<AaptFile>& file);
+class PngMemoryFile {
+public:
+ PngMemoryFile(void)
+ : mData(NULL), mDataSize(0), mIndex(0)
+ {}
+ void setDataSource(const char* data, uint32_t size) { mData = data; mDataSize = size; mIndex = 0; }
+ status_t read(png_bytep data, png_size_t length);
+
+private:
+ const char* mData;
+ uint32_t mDataSize;
+ uint32_t mIndex;
+};
+
#endif
diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp
index 2857b59034e..fb922877301 100644
--- a/tools/aapt/Main.cpp
+++ b/tools/aapt/Main.cpp
@@ -1,5 +1,6 @@
//
// Copyright 2006 The Android Open Source Project
+// This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
//
// Android Asset Packaging Tool main entry point.
//
@@ -14,6 +15,7 @@
#include <stdlib.h>
#include <getopt.h>
#include <assert.h>
+#include <ctype.h>
using namespace android;
@@ -56,7 +58,7 @@ void usage(void)
" xmltree Print the compiled xmls in the given assets.\n"
" xmlstrings Print the strings of the given compiled xml assets.\n\n", gProgName);
fprintf(stderr,
- " %s p[ackage] [-d][-f][-m][-u][-v][-x][-z][-M AndroidManifest.xml] \\\n"
+ " %s p[ackage] [-d][-f][-m][-u][-v][-x[ extending-resource-id]][-z][-M AndroidManifest.xml] \\\n"
" [-0 extension [-0 extension ...]] [-g tolerance] [-j jarfile] \\\n"
" [--debug-mode] [--min-sdk-version VAL] [--target-sdk-version VAL] \\\n"
" [--app-version VAL] [--app-version-name TEXT] [--custom-package VAL] \\\n"
@@ -114,7 +116,7 @@ void usage(void)
" -m make package directories under location specified by -J\n"
" -u update existing packages (add new, replace older, remove deleted files)\n"
" -v verbose output\n"
- " -x create extending (non-application) resource IDs\n"
+ " -x either create or assign (if specified) extending (non-application) resource IDs\n"
" -z require localization of resource attributes marked with\n"
" localization=\"suggested\"\n"
" -A additional directory in which to find raw asset files\n"
@@ -342,6 +344,14 @@ int main(int argc, char* const argv[])
break;
case 'x':
bundle.setExtending(true);
+ argc--;
+ argv++;
+ if (!argc || !isdigit(argv[0][0])) {
+ argc++;
+ argv--;
+ } else {
+ bundle.setExtendedPackageId(atoi(argv[0]));
+ }
break;
case 'z':
bundle.setRequireLocalization(true);
@@ -423,6 +433,17 @@ int main(int argc, char* const argv[])
convertPath(argv[0]);
bundle.setAndroidManifestFile(argv[0]);
break;
+ case 'X':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-X' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.setInternalZipPath(argv[0]);
+ break;
case 'P':
argc--;
argv++;
@@ -492,6 +513,28 @@ int main(int argc, char* const argv[])
bundle.setCompressionMethod(ZipEntry::kCompressStored);
}
break;
+ case 'Z':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-Z' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.setInputAPKFile(argv[0]);
+ break;
+ case 'r':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-r' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.setOutputResApk(argv[0]);
+ break;
case '-':
if (strcmp(cp, "-debug-mode") == 0) {
bundle.setDebugMode(true);
diff --git a/tools/aapt/Main.h b/tools/aapt/Main.h
index e84c4c503cd..0b8adbe5dbc 100644
--- a/tools/aapt/Main.h
+++ b/tools/aapt/Main.h
@@ -42,7 +42,14 @@ extern int calcPercent(long uncompressedLen, long compressedLen);
extern android::status_t writeAPK(Bundle* bundle,
const android::String8& outputFile,
- const android::sp<OutputSet>& outputSet);
+ const android::sp<OutputSet>& outputSet,
+ bool isOverlay);
+extern android::status_t writeAPK(Bundle* bundle,
+ int fd,
+ const android::sp<OutputSet>& outputSet,
+ bool isOverlay);
+extern android::status_t writeResFile(FILE* fp, const sp<AaptAssets>& assets, sp<ApkBuilder>& builder);
+extern sp<AaptFile> getResourceFile(const sp<AaptAssets>& assets, bool makeIfNecessary=true);
extern android::status_t updatePreProcessedCache(Bundle* bundle);
diff --git a/tools/aapt/OutputSet.h b/tools/aapt/OutputSet.h
index ea9ef709b56..974b8d0a694 100644
--- a/tools/aapt/OutputSet.h
+++ b/tools/aapt/OutputSet.h
@@ -27,10 +27,10 @@ class AaptFile;
class OutputEntry {
public:
OutputEntry() {}
- OutputEntry(const android::String8& path, const android::sp<const AaptFile>& file)
+ OutputEntry(const android::String8& path, const android::sp<AaptFile>& file)
: mPath(path), mFile(file) {}
- inline const android::sp<const AaptFile>& getFile() const {
+ inline const android::sp<AaptFile>& getFile() const {
return mFile;
}
@@ -43,7 +43,7 @@ public:
private:
android::String8 mPath;
- android::sp<const AaptFile> mFile;
+ android::sp<AaptFile> mFile;
};
class OutputSet : public virtual android::RefBase {
diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp
index 246a9ea57aa..5b7b2bebade 100644
--- a/tools/aapt/Package.cpp
+++ b/tools/aapt/Package.cpp
@@ -37,8 +37,10 @@ static const char* kNoCompressExt[] = {
};
/* fwd decls, so I can write this downward */
-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 processFile(Bundle* bundle, ZipFile* zip, String8 storageName, const sp<AaptFile>& file);
+ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& outputSet, bool isOverlay);
+bool processOverlayFile(Bundle* bundle, ZipFile* zip,
+ String8 storageName, const sp<AaptFile>& file);
bool okayToCompress(Bundle* bundle, const String8& pathName);
ssize_t processJarFiles(Bundle* bundle, ZipFile* zip);
@@ -49,7 +51,81 @@ 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 String8& outputFile, const sp<OutputSet>& outputSet)
+status_t writeAPK(Bundle* bundle, ZipFile* zip, const char* outputFileName,
+ const sp<OutputSet>& outputSet, bool isOverlay)
+{
+ status_t result = NO_ERROR;
+ int count;
+
+ if (bundle->getVerbose()) {
+ printf("Writing all files...\n");
+ }
+
+ count = processAssets(bundle, zip, outputSet, isOverlay);
+ if (count < 0) {
+ fprintf(stderr, "ERROR: unable to process assets while packaging '%s'\n",
+ outputFileName);
+ result = count;
+ goto bail;
+ }
+
+ if (bundle->getVerbose()) {
+ printf("Generated %d file%s\n", count, (count==1) ? "" : "s");
+ }
+
+ if (!isOverlay) {
+ count = processJarFiles(bundle, zip);
+ if (count < 0) {
+ fprintf(stderr, "ERROR: unable to process jar files while packaging '%s'\n",
+ outputFileName);
+ result = count;
+ goto bail;
+ }
+
+ if (bundle->getVerbose())
+ printf("Included %d file%s from jar/zip files.\n", count, (count==1) ? "" : "s");
+ }
+
+ result = NO_ERROR;
+
+ /*
+ * Check for cruft. We set the "marked" flag on all entries we created
+ * or decided not to update. If the entry isn't already slated for
+ * deletion, remove it now.
+ */
+ {
+ if (bundle->getVerbose())
+ printf("Checking for deleted files\n");
+ int i, removed = 0;
+ for (i = 0; i < zip->getNumEntries(); i++) {
+ ZipEntry* entry = zip->getEntryByIndex(i);
+
+ if (!entry->getMarked() && entry->getDeleted()) {
+ if (bundle->getVerbose()) {
+ printf(" (removing crufty '%s')\n",
+ entry->getFileName());
+ }
+ zip->remove(entry);
+ removed++;
+ }
+ }
+ if (bundle->getVerbose() && removed > 0)
+ printf("Removed %d file%s\n", removed, (removed==1) ? "" : "s");
+ }
+
+ /* tell Zip lib to process deletions and other pending changes */
+ result = zip->flush();
+ if (result != NO_ERROR) {
+ fprintf(stderr, "ERROR: Zip flush failed, archive may be hosed\n");
+ goto bail;
+ }
+
+bail:
+ return result;
+}
+
+status_t writeAPK(Bundle* bundle, const String8& outputFile,
+ const sp<OutputSet>& outputSet, bool isOverlay)
{
#if BENCHMARK
fprintf(stdout, "BENCHMARK: Starting APK Bundling \n");
@@ -105,64 +181,10 @@ status_t writeAPK(Bundle* bundle, const String8& outputFile, const sp<OutputSet>
goto bail;
}
- if (bundle->getVerbose()) {
- printf("Writing all files...\n");
- }
-
- count = processAssets(bundle, zip, outputSet);
- if (count < 0) {
- fprintf(stderr, "ERROR: unable to process assets while packaging '%s'\n",
- outputFile.string());
- result = count;
- goto bail;
- }
+ result = writeAPK(bundle, zip, outputFile.string(), outputSet, isOverlay);
- if (bundle->getVerbose()) {
- printf("Generated %d file%s\n", count, (count==1) ? "" : "s");
- }
-
- count = processJarFiles(bundle, zip);
- if (count < 0) {
- fprintf(stderr, "ERROR: unable to process jar files while packaging '%s'\n",
- outputFile.string());
- result = count;
- goto bail;
- }
-
- if (bundle->getVerbose())
- printf("Included %d file%s from jar/zip files.\n", count, (count==1) ? "" : "s");
-
- result = NO_ERROR;
-
- /*
- * Check for cruft. We set the "marked" flag on all entries we created
- * or decided not to update. If the entry isn't already slated for
- * deletion, remove it now.
- */
- {
- if (bundle->getVerbose())
- printf("Checking for deleted files\n");
- int i, removed = 0;
- for (i = 0; i < zip->getNumEntries(); i++) {
- ZipEntry* entry = zip->getEntryByIndex(i);
-
- if (!entry->getMarked() && entry->getDeleted()) {
- if (bundle->getVerbose()) {
- printf(" (removing crufty '%s')\n",
- entry->getFileName());
- }
- zip->remove(entry);
- removed++;
- }
- }
- if (bundle->getVerbose() && removed > 0)
- printf("Removed %d file%s\n", removed, (removed==1) ? "" : "s");
- }
-
- /* tell Zip lib to process deletions and other pending changes */
- result = zip->flush();
if (result != NO_ERROR) {
- fprintf(stderr, "ERROR: Zip flush failed, archive may be hosed\n");
+ fprintf(stderr, "ERROR: Writing apk failed\n");
goto bail;
}
@@ -215,7 +237,99 @@ bail:
return result;
}
-ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& outputSet)
+/*
+ * The directory hierarchy looks like this:
+ * "outputDir" and "assetRoot" are existing directories.
+ *
+ * On success, "bundle->numPackages" will be the number of Zip packages
+ * we created.
+ */
+status_t writeAPK(Bundle* bundle, int fd, const sp<OutputSet>& outputSet, bool isOverlay)
+{
+ #if BENCHMARK
+ fprintf(stdout, "BENCHMARK: Starting APK Bundling \n");
+ long startAPKTime = clock();
+ #endif /* BENCHMARK */
+
+ status_t result = NO_ERROR;
+ ZipFile* zip = NULL;
+ int count;
+
+ status_t status;
+ zip = new ZipFile;
+ status = zip->openfd(fd, ZipFile::kOpenReadWrite);
+ if (status != NO_ERROR) {
+ fprintf(stderr, "ERROR: unable to open file as Zip file for writing\n");
+ result = PERMISSION_DENIED;
+ goto bail;
+ }
+
+ result = writeAPK(bundle, zip, "file_descriptor", outputSet, isOverlay);
+
+ if (result != NO_ERROR) {
+ fprintf(stderr, "ERROR: Writing apk failed\n");
+ goto bail;
+ }
+
+ /* anything here? */
+ if (zip->getNumEntries() == 0) {
+ if (bundle->getVerbose()) {
+ printf("Archive is empty -- removing\n");
+ }
+ delete zip; // close the file so we can remove it in Win32
+ zip = NULL;
+ close(fd);
+ }
+
+ assert(result == NO_ERROR);
+
+bail:
+ delete zip; // must close before remove in Win32
+ close(fd);
+ if (result != NO_ERROR) {
+ if (bundle->getVerbose()) {
+ printf("Removing archive due to earlier failures\n");
+ }
+ }
+
+ if (result == NO_ERROR && bundle->getVerbose())
+ printf("Done!\n");
+
+ #if BENCHMARK
+ fprintf(stdout, "BENCHMARK: End APK Bundling. Time Elapsed: %f ms \n",(clock() - startAPKTime)/1000.0);
+ #endif /* BENCHMARK */
+ return result;
+}
+
+status_t writeResFile(FILE* fp, const sp<AaptAssets>& assets, sp<ApkBuilder>& builder) {
+ if (fp == NULL) {
+ fprintf(stderr, "Unable to open resFile for writing resTable\n");
+ return PERMISSION_DENIED;
+ }
+
+ sp<ApkSplit> split = builder->getBaseSplit();
+ const std::set<OutputEntry>& entries = split->getEntries();
+ std::set<OutputEntry>::const_iterator iter = entries.begin();
+ for (; iter != entries.end(); iter++) {
+ const OutputEntry& entry = *iter;
+
+ if (entry.getPath() == String8("resources.arsc")) {
+ sp<AaptFile> resFile = entry.getFile();
+
+ int count = 0;
+ count = fwrite(resFile->getData(), 1, resFile->getSize(), fp);
+
+ if (count == 0) {
+ fprintf(stderr, "Nothing written to resFile\n");
+ }
+ }
+ }
+
+ return NO_ERROR;
+}
+
+ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& outputSet,
+ bool isOverlay)
{
ssize_t count = 0;
const std::set<OutputEntry>& entries = outputSet->getEntries();
@@ -227,7 +341,9 @@ ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& o
} else {
String8 storagePath(entry.getPath());
storagePath.convertToResPath();
- if (!processFile(bundle, zip, storagePath, entry.getFile())) {
+ bool ret = isOverlay ? processOverlayFile(bundle, zip, storagePath, entry.getFile())
+ : processFile(bundle, zip, storagePath, entry.getFile());
+ if (!ret) {
return UNKNOWN_ERROR;
}
count++;
@@ -243,7 +359,7 @@ ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& o
* delete the existing entry before adding the new one.
*/
bool processFile(Bundle* bundle, ZipFile* zip,
- String8 storageName, const sp<const AaptFile>& file)
+ String8 storageName, const sp<AaptFile>& file)
{
const bool hasData = file->hasData();
@@ -360,6 +476,76 @@ bool processFile(Bundle* bundle, ZipFile* zip,
}
/*
+ * Process a regular file, adding it to the archive if appropriate.
+ *
+ * This function is intended for use when creating a cached overlay package.
+ * Only xml and .9.png files are processed and added to the package.
+ *
+ * If we're in "update" mode, and the file already exists in the archive,
+ * delete the existing entry before adding the new one.
+ */
+bool processOverlayFile(Bundle* bundle, ZipFile* zip,
+ String8 storageName, const sp<AaptFile>& file)
+{
+ const bool hasData = file->hasData();
+
+ storageName.convertToResPath();
+ ZipEntry* entry;
+ bool fromGzip = false;
+ status_t result;
+
+ if (strcasecmp(storageName.getPathExtension().string(), ".gz") == 0) {
+ fromGzip = true;
+ storageName = storageName.getBasePath();
+ }
+
+ if (bundle->getUpdate()) {
+ entry = zip->getEntryByName(storageName.string());
+ if (entry != NULL) {
+ /* file already exists in archive; there can be only one */
+ if (entry->getMarked()) {
+ fprintf(stderr,
+ "ERROR: '%s' exists twice (check for with & w/o '.gz'?)\n",
+ file->getPrintableSource().string());
+ return false;
+ }
+ zip->remove(entry);
+ }
+ }
+
+ if (hasData) {
+ const char* name = storageName.string();
+ if (endsWith(name, ".9.png") || endsWith(name, ".xml") || endsWith(name, ".arsc")) {
+ result = zip->add(file->getData(), file->getSize(), storageName.string(),
+ file->getCompressionMethod(), &entry);
+ if (result == NO_ERROR) {
+ if (bundle->getVerbose()) {
+ printf(" '%s'%s", storageName.string(), fromGzip ? " (from .gz)" : "");
+ if (entry->getCompressionMethod() == ZipEntry::kCompressStored) {
+ printf(" (not compressed)\n");
+ } else {
+ printf(" (compressed %d%%)\n", calcPercent(entry->getUncompressedLen(),
+ entry->getCompressedLen()));
+ }
+ }
+ entry->setMarked(true);
+ } else {
+ if (result == ALREADY_EXISTS) {
+ 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());
+ }
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+/*
* Determine whether or not we want to try to compress this file based
* on the file extension.
*/
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index a4c9dabb6be..8b0a0124138 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -208,6 +208,24 @@ bool isValidResourceType(const String8& type)
|| type == "color" || type == "menu" || type == "mipmap";
}
+sp<AaptFile> getResourceFile(const sp<AaptAssets>& assets, bool makeIfNecessary)
+{
+ 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)
{
@@ -1124,7 +1142,9 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil
packageType = ResourceTable::AppFeature;
}
- ResourceTable table(bundle, String16(assets->getPackage()), packageType);
+ int extendedPackageId = bundle->getExtendedPackageId();
+
+ ResourceTable table(bundle, String16(assets->getPackage()), packageType, extendedPackageId);
err = table.addIncludedResources(bundle, assets);
if (err != NO_ERROR) {
return err;
@@ -1204,7 +1224,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil
bool hasErrors = false;
if (drawables != NULL) {
- if (bundle->getOutputAPKFile() != NULL) {
+ if (bundle->getOutputAPKFile() != NULL || bundle->getOutputResApk()) {
err = preProcessImages(bundle, assets, drawables, "drawable");
}
if (err == NO_ERROR) {
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 4587a4bfa1f..45ee38fb955 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -1715,7 +1715,7 @@ status_t compileResourceFile(Bundle* bundle,
return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
}
-ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage, ResourceTable::PackageType type)
+ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage, ResourceTable::PackageType type, ssize_t pkgIdOverride)
: mAssetsPackage(assetsPackage)
, mPackageType(type)
, mTypeIdOffset(0)
@@ -1741,6 +1741,11 @@ ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage, Reso
assert(0);
break;
}
+
+ if (pkgIdOverride != 0) {
+ packageId = pkgIdOverride;
+ }
+
sp<Package> package = new Package(mAssetsPackage, packageId);
mPackages.add(assetsPackage, package);
mOrderedPackages.add(package);
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index eac5dd3f919..25b8a8f558f 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -101,7 +101,8 @@ public:
class Type;
class Entry;
- ResourceTable(Bundle* bundle, const String16& assetsPackage, PackageType type);
+ ResourceTable(Bundle* bundle, const String16& assetsPackage, PackageType type,
+ ssize_t pkgIdOverride);
const String16& getAssetsPackage() const {
return mAssetsPackage;
diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp
index 51a4154d533..ae8f2a50f5e 100644
--- a/tools/aapt/XMLNode.cpp
+++ b/tools/aapt/XMLNode.cpp
@@ -11,6 +11,7 @@
#include <utils/ByteOrder.h>
#include <errno.h>
#include <string.h>
+#include <androidfw/AssetManager.h>
#ifndef HAVE_MS_C_RUNTIME
#define O_BINARY 0
@@ -575,9 +576,51 @@ status_t parseXMLResource(const sp<AaptFile>& file, ResXMLTree* outTree,
return NO_ERROR;
}
+sp<XMLNode> XMLNode::parseFromZip(const sp<AaptFile>& file) {
+ AssetManager assets;
+ int32_t cookie;
+
+ if (!assets.addAssetPath(file->getZipFile(), &cookie)) {
+ fprintf(stderr, "Error: Could not open path %s\n", file->getZipFile().string());
+ return NULL;
+ }
+
+ Asset* asset = assets.openNonAsset(cookie, file->getSourceFile(), Asset::ACCESS_BUFFER);
+ ssize_t len = asset->getLength();
+ const void* buf = asset->getBuffer(false);
+
+ XML_Parser parser = XML_ParserCreateNS(NULL, 1);
+ ParseState state;
+ state.filename = file->getPrintableSource();
+ state.parser = parser;
+ XML_SetUserData(parser, &state);
+ XML_SetElementHandler(parser, startElement, endElement);
+ XML_SetNamespaceDeclHandler(parser, startNamespace, endNamespace);
+ XML_SetCharacterDataHandler(parser, characterData);
+ XML_SetCommentHandler(parser, commentData);
+
+ bool done = true;
+ if (XML_Parse(parser, (char*) buf, len, done) == XML_STATUS_ERROR) {
+ SourcePos(file->getSourceFile(), (int)XML_GetCurrentLineNumber(parser)).error(
+ "Error parsing XML: %s\n", XML_ErrorString(XML_GetErrorCode(parser)));
+ return NULL;
+ }
+ XML_ParserFree(parser);
+ if (state.root == NULL) {
+ SourcePos(file->getSourceFile(), -1).error("No XML data generated when parsing");
+ }
+ return state.root;
+}
+
sp<XMLNode> XMLNode::parse(const sp<AaptFile>& file)
{
char buf[16384];
+
+ //Check for zip first
+ if (file->getZipFile().length() > 0) {
+ return parseFromZip(file);
+ }
+
int fd = open(file->getSourceFile().string(), O_RDONLY | O_BINARY);
if (fd < 0) {
SourcePos(file->getSourceFile(), -1).error("Unable to open file for read: %s",
diff --git a/tools/aapt/XMLNode.h b/tools/aapt/XMLNode.h
index 3161f650029..905c6fdf8ee 100644
--- a/tools/aapt/XMLNode.h
+++ b/tools/aapt/XMLNode.h
@@ -188,6 +188,9 @@ private:
status_t flatten_node(const StringPool& strings, const sp<AaptFile>& dest,
bool stripComments, bool stripRawValues) const;
+ static sp<XMLNode> parseFromZip(const sp<AaptFile>& file);
+ static sp<XMLNode> parseFromAsset(const Asset& asset);
+
String16 mNamespacePrefix;
String16 mNamespaceUri;
String16 mElementName;
diff --git a/tools/aapt/ZipEntry.h b/tools/aapt/ZipEntry.h
index c2f3227cf23..ec079208ae0 100644
--- a/tools/aapt/ZipEntry.h
+++ b/tools/aapt/ZipEntry.h
@@ -58,7 +58,7 @@ public:
bool isCompressed(void) const {
return mCDE.mCompressionMethod != kCompressStored;
}
- int getCompressionMethod(void) const { return mCDE.mCompressionMethod; }
+ virtual int getCompressionMethod(void) const { return mCDE.mCompressionMethod; }
/*
* Return the uncompressed length.
@@ -100,7 +100,7 @@ public:
/*
* Return the archived file name.
*/
- const char* getFileName(void) const { return (const char*) mCDE.mFileName; }
+ virtual const char* getFileName(void) const { return (const char*) mCDE.mFileName; }
/*
* Application-defined "mark". Can be useful when synchronizing the
diff --git a/tools/aapt/ZipFile.cpp b/tools/aapt/ZipFile.cpp
index 8057068dd68..a56bd3367f9 100644
--- a/tools/aapt/ZipFile.cpp
+++ b/tools/aapt/ZipFile.cpp
@@ -130,6 +130,71 @@ status_t ZipFile::open(const char* zipFileName, int flags)
}
/*
+ * Open a file and parse its guts.
+ */
+status_t ZipFile::openfd(int fd, int flags)
+{
+ bool newArchive = true;
+
+ assert(mZipFp == NULL); // no reopen
+
+ if ((flags & kOpenTruncate))
+ flags |= kOpenCreate; // trunc implies create
+
+ if ((flags & kOpenReadOnly) && (flags & kOpenReadWrite))
+ return INVALID_OPERATION; // not both
+ if (!((flags & kOpenReadOnly) || (flags & kOpenReadWrite)))
+ return INVALID_OPERATION; // not neither
+ if ((flags & kOpenCreate) && !(flags & kOpenReadWrite))
+ return INVALID_OPERATION; // create requires write
+
+ /* open the file */
+ const char* openflags;
+ if (flags & kOpenReadWrite) {
+ if (newArchive)
+ openflags = FILE_OPEN_RW_CREATE;
+ else
+ openflags = FILE_OPEN_RW;
+ } else {
+ openflags = FILE_OPEN_RO;
+ }
+ mZipFp = fdopen(fd, openflags);
+ if (mZipFp == NULL) {
+ int err = errno;
+ ALOGD("fdopen failed: %s\n", strerror(err));
+ return errnoToStatus(err);
+ }
+
+ status_t result;
+ if (!newArchive) {
+ /*
+ * Load the central directory. If that fails, then this probably
+ * isn't a Zip archive.
+ */
+ result = readCentralDir();
+ } else {
+ /*
+ * Newly-created. The EndOfCentralDir constructor actually
+ * sets everything to be the way we want it (all zeroes). We
+ * set mNeedCDRewrite so that we create *something* if the
+ * caller doesn't add any files. (We could also just unlink
+ * the file if it's brand new and nothing was added, but that's
+ * probably doing more than we really should -- the user might
+ * have a need for empty zip files.)
+ */
+ mNeedCDRewrite = true;
+ result = NO_ERROR;
+ }
+
+ if (flags & kOpenReadOnly)
+ mReadOnly = true;
+ else
+ assert(!mReadOnly);
+
+ return result;
+}
+
+/*
* Return the Nth entry in the archive.
*/
ZipEntry* ZipFile::getEntryByIndex(int idx) const
diff --git a/tools/aapt/ZipFile.h b/tools/aapt/ZipFile.h
index 78775502884..00e2ba9a574 100644
--- a/tools/aapt/ZipFile.h
+++ b/tools/aapt/ZipFile.h
@@ -46,7 +46,7 @@ public:
ZipFile(void)
: mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false)
{}
- ~ZipFile(void) {
+ virtual ~ZipFile(void) {
if (!mReadOnly)
flush();
if (mZipFp != NULL)
@@ -64,6 +64,7 @@ public:
kOpenTruncate = 0x08, // if it exists, empty it
};
status_t open(const char* zipFileName, int flags);
+ status_t openfd(int fd, int flags);
/*
* Add a file to the end of the archive. Specify whether you want the
@@ -161,8 +162,8 @@ public:
*
* This will return an entry that is pending deletion.
*/
- int getNumEntries(void) const { return mEntries.size(); }
- ZipEntry* getEntryByIndex(int idx) const;
+ virtual int getNumEntries(void) const { return mEntries.size(); }
+ virtual ZipEntry* getEntryByIndex(int idx) const;
private:
/* these are private and not defined */
diff --git a/tools/aapt/tests/ZipReading_test.cpp b/tools/aapt/tests/ZipReading_test.cpp
new file mode 100644
index 00000000000..4b4f2da178a
--- /dev/null
+++ b/tools/aapt/tests/ZipReading_test.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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 <gmock/gmock.h>
+#include <utils/KeyedVector.h>
+
+#include "mocks/MockZipFile.h"
+#include "mocks/MockZipEntry.h"
+
+#include "AaptConfig.h"
+#include "ConfigDescription.h"
+#include "TestHelper.h"
+
+#include "AaptAssets.h"
+
+using android::String8;
+using namespace testing;
+
+// A path to an apk that would be considered valid
+#define VALID_APK_FILE "/valid/valid.apk"
+
+// Internal zip path to a dir that aapt is being asked to compile
+#define COMPILING_OVERLAY_DIR "/assets/overlays/com.interesting.app"
+
+// Internal zip path to a valid resource. aapt is expected to compile this resource.
+#define COMPILING_OVERLAY_FILE COMPILING_OVERLAY_DIR "/res/drawable-xxhdpi/foo.png";
+
+// Internal zip path to another overlay dir that is NOT being compiled
+#define NOT_COMPILING_OVERLAY_DIR "/assets/overlays/com.boring.app"
+
+// Internal zip path to a resource for an overlay that is NOT compiling. aapt is expected to ignore
+#define NOT_COMPILING_OVERLAY_FILE COMPILING_OVERLAY_DIR "/assets/overlays/com.boring.app"
+
+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(ZipReadingTest, TestValidZipEntryIsAdded) {
+ MockZipFile zip;
+ MockZipEntry entry1;
+ const char* zipFile = VALID_APK_FILE;
+ const char* validFilename = COMPILING_OVERLAY_FILE;
+
+ EXPECT_CALL(entry1, getFileName())
+ .WillRepeatedly(Return(validFilename));
+
+ EXPECT_CALL(zip, getNumEntries())
+ .Times(1)
+ .WillRepeatedly(Return(1));
+
+ EXPECT_CALL(zip, getEntryByIndex(_))
+ .Times(1)
+ .WillOnce(Return(&entry1));
+
+ sp<AaptAssets> assets = new AaptAssets();
+ Bundle bundle;
+ bundle.setInternalZipPath(COMPILING_OVERLAY_DIR);
+ ssize_t count = assets->slurpResourceZip(&bundle, &zip, zipFile);
+
+ Vector<sp<AaptDir> > dirs = assets->resDirs();
+ EXPECT_EQ(1, dirs.size());
+ EXPECT_EQ(1, count);
+}
+
+TEST(ZipReadingTest, TestDifferentThemeEntryNotAdded) {
+ MockZipFile zip;
+ MockZipEntry entry1;
+ const char* zipFile = VALID_APK_FILE;
+ const char* invalidFile = NOT_COMPILING_OVERLAY_FILE;
+
+ EXPECT_CALL(entry1, getFileName())
+ .WillRepeatedly(Return(invalidFile));
+
+ EXPECT_CALL(zip, getNumEntries())
+ .WillRepeatedly(Return(1));
+
+ EXPECT_CALL(zip, getEntryByIndex(_))
+ .Times(1)
+ .WillOnce(Return(&entry1));
+
+ sp<AaptAssets> assets = new AaptAssets();
+ Bundle bundle;
+ bundle.setInternalZipPath(COMPILING_OVERLAY_DIR);
+ ssize_t count = assets->slurpResourceZip(&bundle, &zip, zipFile);
+
+ Vector<sp<AaptDir> > dirs = assets->resDirs();
+ EXPECT_EQ(0, dirs.size());
+ EXPECT_EQ(0, count);
+}
+
+TEST(ZipReadingTest, TestOutsideEntryMarkedInvalid) {
+ Bundle bundle;
+ bundle.setInternalZipPath("VALID_OVERLAY_DIR");
+ MockZipEntry invalidEntry;
+ const char* invalidFile = NOT_COMPILING_OVERLAY_FILE;
+
+ EXPECT_CALL(invalidEntry, getFileName())
+ .WillRepeatedly(Return(invalidFile));
+
+ sp<AaptAssets> assets = new AaptAssets();
+ bool result = assets->isEntryValid(&bundle, &invalidEntry);
+
+ EXPECT_FALSE(result);
+}
+
+TEST(ZipReadingTest, TestNullEntryIsInvalid) {
+ Bundle bundle;
+ bundle.setInternalZipPath(COMPILING_OVERLAY_DIR);
+ MockZipEntry invalidEntry;
+ const char* invalidFile = NOT_COMPILING_OVERLAY_FILE;
+
+ EXPECT_CALL(invalidEntry, getFileName())
+ .WillRepeatedly(Return(invalidFile));
+
+ sp<AaptAssets> assets = new AaptAssets();
+ bool result = assets->isEntryValid(&bundle, NULL);
+
+ EXPECT_FALSE(result);
+}
+
+TEST(ZipReadingTest, TestDirectoryEntryMarkedInvalid) {
+ Bundle bundle;
+ bundle.setInternalZipPath(COMPILING_OVERLAY_DIR);
+ MockZipEntry invalidEntry2;
+ // Add a "/" signifying this is a dir entry not a file entry.
+ const char* dir2 = COMPILING_OVERLAY_DIR"/";
+ EXPECT_CALL(invalidEntry2, getFileName())
+ .WillRepeatedly(Return(dir2));
+
+ sp<AaptAssets> assets = new AaptAssets();
+ bool result2 = assets->isEntryValid(&bundle, &invalidEntry2);
+
+ EXPECT_FALSE(result2);
+}
diff --git a/tools/aapt/tests/mocks/MockZipEntry.h b/tools/aapt/tests/mocks/MockZipEntry.h
new file mode 100644
index 00000000000..eef07f9314d
--- /dev/null
+++ b/tools/aapt/tests/mocks/MockZipEntry.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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 ANDROID_MOCK_ZIP_ENTRY
+#define ANDROID_MOCK_ZIP_ENTRY
+
+#include "ZipFile.h"
+#include "gmock/gmock.h"
+
+class MockZipEntry : public android::ZipEntry {
+public:
+ MOCK_CONST_METHOD0(getCompressionMethod, int());
+ MOCK_CONST_METHOD0(getFileName, const char* ());
+};
+
+#endif // ANDROID_MOCK_ZIP_ENTRY
diff --git a/tools/aapt/tests/mocks/MockZipFile.h b/tools/aapt/tests/mocks/MockZipFile.h
new file mode 100644
index 00000000000..0394ce730a0
--- /dev/null
+++ b/tools/aapt/tests/mocks/MockZipFile.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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 ANDROID_MOCK_ZIP_FILE
+#define ANDROID_MOCK_ZIP_FILE
+
+#include "ZipFile.h"
+#include "gmock/gmock.h"
+
+class MockZipFile : public android::ZipFile {
+public:
+ MOCK_CONST_METHOD0(getNumEntries, int());
+ MOCK_CONST_METHOD1(getEntryByIndex, android::ZipEntry* (int idx));
+};
+
+#endif // ANDROID_MOCK_ZIP_FILE