diff --git a/Android.mk b/Android.mk index 22cc27b6be878..23d62ae2a8e4c 100644 --- a/Android.mk +++ b/Android.mk @@ -137,6 +137,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 28c2172294bad..52ac47d859a4f 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) \ No newline at end of file diff --git a/cmds/bootanimation/Android.mk b/cmds/bootanimation/Android.mk index d6ecbe31f5e80..863515c5ec968 100644 --- a/cmds/bootanimation/Android.mk +++ b/cmds/bootanimation/Android.mk @@ -21,7 +21,8 @@ LOCAL_SHARED_LIBRARIES := \ libEGL \ libGLESv1_CM \ libgui \ - libtinyalsa + libtinyalsa \ + libmedia LOCAL_MODULE:= bootanimation diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index b2474f2f18bcb..5650b3bb4efda 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2007 The Android Open Source Project + * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +24,8 @@ #include #include #include +#include +#include #include @@ -49,12 +52,29 @@ #include #include +#include +#include +#include + #include "BootAnimation.h" #include "AudioPlayer.h" #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" + +#define OEM_SHUTDOWN_MUSIC_FILE "/oem/media/shutdown.wav" +#define SYSTEM_SHUTDOWN_MUSIC_FILE "/system/media/shutdown.wav" + #define EXIT_PROP_NAME "service.bootanim.exit" extern "C" int clock_nanosleep(clockid_t clock_id, int flags, @@ -67,6 +87,87 @@ static const int ANIM_ENTRY_NAME_MAX = 256; // --------------------------------------------------------------------------- +static pthread_mutex_t mp_lock; +static pthread_cond_t mp_cond; +static bool isMPlayerPrepared = false; +static bool isMPlayerCompleted = false; + +class MPlayerListener : public MediaPlayerListener +{ + void notify(int msg, int ext1, int ext2, const Parcel *obj) + { + switch (msg) { + case MEDIA_NOP: // interface test message + break; + case MEDIA_PREPARED: + pthread_mutex_lock(&mp_lock); + isMPlayerPrepared = true; + pthread_cond_signal(&mp_cond); + pthread_mutex_unlock(&mp_lock); + break; + case MEDIA_PLAYBACK_COMPLETE: + pthread_mutex_lock(&mp_lock); + isMPlayerCompleted = true; + pthread_cond_signal(&mp_cond); + pthread_mutex_unlock(&mp_lock); + break; + default: + break; + } + } +}; + +static long getFreeMemory(void) +{ + int fd = open("/proc/meminfo", O_RDONLY); + const char* const sums[] = { "MemFree:", "Cached:", NULL }; + const int sumsLen[] = { strlen("MemFree:"), strlen("Cached:"), 0 }; + int num = 2; + + if (fd < 0) { + ALOGW("Unable to open /proc/meminfo"); + return -1; + } + + char buffer[256]; + const int len = read(fd, buffer, sizeof(buffer)-1); + close(fd); + + if (len < 0) { + ALOGW("Unable to read /proc/meminfo"); + return -1; + } + buffer[len] = 0; + + size_t numFound = 0; + long mem = 0; + + char* p = buffer; + while (*p && numFound < num) { + int i = 0; + while (sums[i]) { + if (strncmp(p, sums[i], sumsLen[i]) == 0) { + p += sumsLen[i]; + while (*p == ' ') p++; + char* num = p; + while (*p >= '0' && *p <= '9') p++; + if (*p != 0) { + *p = 0; + p++; + if (*p == 0) p--; + } + mem += atoll(num); + numFound++; + break; + } + i++; + } + p++; + } + + return numFound > 0 ? mem : -1; +} + BootAnimation::BootAnimation() : Thread(false), mZip(NULL) { mSession = new SurfaceComposerClient(); @@ -160,12 +261,12 @@ status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets, return NO_ERROR; } -status_t BootAnimation::initTexture(const Animation::Frame& frame) +status_t BootAnimation::initTexture(void* buffer, size_t len) { //StopWatch watch("blah"); SkBitmap bitmap; - SkMemoryStream stream(frame.map->getDataPtr(), frame.map->getDataLength()); + SkMemoryStream stream(buffer, len); SkImageDecoder* codec = SkImageDecoder::Factory(&stream); if (codec) { codec->setDitherImage(false); @@ -175,11 +276,6 @@ status_t BootAnimation::initTexture(const Animation::Frame& frame) delete codec; } - // FileMap memory is never released until application exit. - // Release it now as the texture is already loaded and the memory used for - // the packed resource can be released. - frame.map->release(); - // ensure we can call getPixels(). No need to call unlock, since the // bitmap will go out of scope when we return from this method. bitmap.lockPixels(); @@ -286,18 +382,23 @@ status_t BootAnimation::readyToRun() { char decrypt[PROPERTY_VALUE_MAX]; property_get("vold.decrypt", decrypt, ""); + // Use customized resources for boot and showdown animation + // instead of system predefined boot animation files. bool encryptedAnimation = atoi(decrypt) != 0 || !strcmp("trigger_restart_min_framework", decrypt); ZipFileRO* zipFile = NULL; if ((encryptedAnimation && - (access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0) && - ((zipFile = ZipFileRO::open(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE)) != NULL)) || + (access(getAnimationFileName(IMG_ENC), R_OK) == 0) && + ((zipFile = ZipFileRO::open(getAnimationFileName(IMG_ENC))) != NULL)) || + + ((access(getAnimationFileName(IMG_DATA), R_OK) == 0) && + ((zipFile = ZipFileRO::open(getAnimationFileName(IMG_DATA))) != NULL)) || - ((access(OEM_BOOTANIMATION_FILE, R_OK) == 0) && - ((zipFile = ZipFileRO::open(OEM_BOOTANIMATION_FILE)) != NULL)) || + ((access(getAnimationFileName(IMG_THEME), R_OK) == 0) && + ((zipFile = ZipFileRO::open(getAnimationFileName(IMG_THEME))) != NULL)) || - ((access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) && - ((zipFile = ZipFileRO::open(SYSTEM_BOOTANIMATION_FILE)) != NULL))) { + ((access(getAnimationFileName(IMG_SYS), R_OK) == 0) && + ((zipFile = ZipFileRO::open(getAnimationFileName(IMG_SYS))) != NULL))) { mZip = zipFile; } @@ -451,6 +552,7 @@ bool BootAnimation::readFile(const char* name, String8& outString) bool BootAnimation::movie() { + char value[PROPERTY_VALUE_MAX]; String8 desString; if (!readFile("desc.txt", desString)) { @@ -581,11 +683,37 @@ bool BootAnimation::movie() Region clearReg(Rect(mWidth, mHeight)); clearReg.subtractSelf(Rect(xc, yc, xc+animation.width, yc+animation.height)); + pthread_mutex_init(&mp_lock, NULL); + pthread_cond_init(&mp_cond, NULL); + + property_get("persist.sys.silent", value, "null"); + if (strncmp(value, "1", 1) != 0) { + playBackgroundMusic(); + } for (size_t i=0 ; i 0) { + if (r > 0 && !needSaveMem) { glBindTexture(GL_TEXTURE_2D, frame.tid); } else { - if (part.count != 1) { + if (!needSaveMem && part.count != 1) { glGenTextures(1, &frame.tid); glBindTexture(GL_TEXTURE_2D, frame.tid); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } - initTexture(frame); + initTexture( + frame.map->getDataPtr(), + frame.map->getDataLength()); } if (!clearReg.isEmpty()) { @@ -659,17 +789,160 @@ bool BootAnimation::movie() } // free the textures for this part - if (part.count != 1) { + if (!needSaveMem && part.count != 1) { for (size_t j=0 ; j mp = new MediaPlayer(); + sp mListener = new MPlayerListener(); + if (mp != NULL) { + ALOGD("starting to play %s", fileName); + mp->setListener(mListener); + + if (mp->setDataSource(NULL, fileName, NULL) == NO_ERROR) { + mp->setAudioStreamType(AUDIO_STREAM_ENFORCED_AUDIBLE); + mp->prepare(); + } else { + ALOGE("failed to setDataSource for %s", fileName); + return NULL; + } + + //waiting for media player is prepared. + pthread_mutex_lock(&mp_lock); + while (!isMPlayerPrepared) { + pthread_cond_wait(&mp_cond, &mp_lock); + } + pthread_mutex_unlock(&mp_lock); + + audio_devices_t device = AudioSystem::getDevicesForStream(AUDIO_STREAM_ENFORCED_AUDIBLE); + AudioSystem::initStreamVolume(AUDIO_STREAM_ENFORCED_AUDIBLE,0,7); + AudioSystem::setStreamVolumeIndex(AUDIO_STREAM_ENFORCED_AUDIBLE, 7, device); + + AudioSystem::getStreamVolumeIndex(AUDIO_STREAM_ENFORCED_AUDIBLE, &index, device); + if (index != 0) { + ALOGD("playing %s", fileName); + mp->seekTo(0); + mp->start(); + } else { + ALOGW("current volume is zero."); + } + } + return NULL; +} // --------------------------------------------------------------------------- } diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h index f968b255d37aa..eff81333b0131 100644 --- a/cmds/bootanimation/BootAnimation.h +++ b/cmds/bootanimation/BootAnimation.h @@ -82,17 +82,23 @@ class BootAnimation : public Thread, public IBinder::DeathRecipient }; status_t initTexture(Texture* texture, AssetManager& asset, const char* name); - status_t initTexture(const Animation::Frame& frame); + status_t initTexture(void* buffer, size_t len); bool android(); bool readFile(const char* name, String8& outString); bool movie(); + enum ImageID { IMG_DATA = 0, IMG_SYS = 1, IMG_ENC = 2, IMG_THEME = 3 }; + char *getAnimationFileName(ImageID image); + char *getBootRingtoneFileName(ImageID image); + void playBackgroundMusic(); + bool checkBootState(); void checkExit(); + void checkShowAndroid(); sp mSession; sp mAudioPlayer; AssetManager mAssets; - Texture mAndroid[2]; + Texture mAndroid[3]; int mWidth; int mHeight; EGLDisplay mDisplay; @@ -103,6 +109,7 @@ class BootAnimation : public Thread, public IBinder::DeathRecipient ZipFileRO *mZip; }; +static void* playMusic(void* arg); // --------------------------------------------------------------------------- }; // namespace android diff --git a/cmds/idmap/create.cpp b/cmds/idmap/create.cpp index 593a1973669a8..2052f26384fc4 100644 --- a/cmds/idmap/create.cpp +++ b/cmds/idmap/create.cpp @@ -14,6 +14,11 @@ using namespace android; namespace { int get_zip_entry_crc(const char *zip_path, const char *entry_name, uint32_t *crc) { + if (crc == NULL) { + // As this function used to get the crc, but the address is NULL, do nothing. + return -1; + } + UniquePtr zip(ZipFileRO::open(zip_path)); if (zip.get() == NULL) { return -1; @@ -22,9 +27,12 @@ namespace { if (entry == NULL) { return -1; } - if (!zip->getEntryInfo(entry, NULL, NULL, NULL, NULL, NULL, (long*)crc)) { + long tmp; + if (!zip->getEntryInfo(entry, NULL, NULL, NULL, NULL, NULL, &tmp)) { return -1; } + // As the crc of the apk only contains 32bits, so the convert action is safe. + *crc = (uint32_t) tmp; zip->releaseEntry(entry); return 0; } @@ -149,26 +157,26 @@ namespace { } 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)) { @@ -180,7 +188,8 @@ namespace { 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; } @@ -195,6 +204,7 @@ namespace { } 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)) { @@ -207,7 +217,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); @@ -215,8 +226,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 90cfa2c610f00..cc3f231824a97 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 f507dd8530ac3..6a9c5ef788130 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 b9ac8a59de02c..d8e4eb72eeecf 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 1153f38a9c022..d5271a4bb5c6a 100644 --- a/cmds/idmap/scan.cpp +++ b/cmds/idmap/scan.cpp @@ -187,6 +187,62 @@ namespace { dataMap->release(); return priority; } + + int idmap_scan(const char *overlay_dir, const char *target_package_name, + const char *target_apk_path, const char *idmap_dir, + SortedVector& overlayVector) + { + DIR *dir = opendir(overlay_dir); + if (dir == NULL) { + return EXIT_FAILURE; + } + + struct dirent *dirent; + while ((dirent = readdir(dir)) != NULL) { + struct stat st; + char overlay_apk_path[PATH_MAX + 1]; + snprintf(overlay_apk_path, PATH_MAX, "%s/%s", overlay_dir, dirent->d_name); + if (stat(overlay_apk_path, &st) < 0) { + continue; + } + if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) { + continue; + } + + if (S_ISDIR(st.st_mode)) { + String8 dir_name = String8(overlay_apk_path).getPathLeaf(); + if (dir_name == "." || dir_name == "..") { + // Skip the "." and ".." dir. + continue; + } + idmap_scan(overlay_apk_path, target_package_name, target_apk_path, idmap_dir, + overlayVector); + } else { + int priority = parse_apk(overlay_apk_path, target_package_name); + if (priority < 0) { + continue; + } + + String8 idmap_path(idmap_dir); + idmap_path.appendPath(flatten_path(overlay_apk_path + 1)); + idmap_path.append("@idmap"); + + 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()); + continue; + } + + Overlay overlay(String8(overlay_apk_path), idmap_path, priority); + overlayVector.add(overlay); + } + } + + closedir(dir); + + return EXIT_SUCCESS; + } } int idmap_scan(const char *overlay_dir, const char *target_package_name, @@ -198,48 +254,13 @@ int idmap_scan(const char *overlay_dir, const char *target_package_name, return EXIT_FAILURE; } - DIR *dir = opendir(overlay_dir); - if (dir == NULL) { - return EXIT_FAILURE; - } - SortedVector overlayVector; - struct dirent *dirent; - while ((dirent = readdir(dir)) != NULL) { - struct stat st; - char overlay_apk_path[PATH_MAX + 1]; - snprintf(overlay_apk_path, PATH_MAX, "%s/%s", overlay_dir, dirent->d_name); - if (stat(overlay_apk_path, &st) < 0) { - continue; - } - if (!S_ISREG(st.st_mode)) { - continue; - } - - int priority = parse_apk(overlay_apk_path, target_package_name); - if (priority < 0) { - continue; - } - - String8 idmap_path(idmap_dir); - idmap_path.appendPath(flatten_path(overlay_apk_path + 1)); - idmap_path.append("@idmap"); - - if (idmap_create_path(target_apk_path, overlay_apk_path, 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()); - continue; - } + int res = idmap_scan(overlay_dir, target_package_name, target_apk_path, idmap_dir, + overlayVector); - Overlay overlay(String8(overlay_apk_path), idmap_path, priority); - overlayVector.add(overlay); - } - - closedir(dir); - - if (!writePackagesList(filename.string(), overlayVector)) { + if (res == EXIT_FAILURE || !writePackagesList(filename.string(), overlayVector)) { return EXIT_FAILURE; } - return EXIT_SUCCESS; -} + return res; +} \ No newline at end of file diff --git a/cmds/tm/Android.mk b/cmds/tm/Android.mk new file mode 100644 index 0000000000000..34a41ddce9f4d --- /dev/null +++ b/cmds/tm/Android.mk @@ -0,0 +1,15 @@ +# Copyright 2015 The CyanogenMod Project +# +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := $(call all-subdir-java-files) +LOCAL_MODULE := tm +include $(BUILD_JAVA_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := tm +LOCAL_SRC_FILES := tm +LOCAL_MODULE_CLASS := EXECUTABLES +LOCAL_MODULE_TAGS := optional +include $(BUILD_PREBUILT) diff --git a/cmds/tm/MODULE_LICENSE_APACHE2 b/cmds/tm/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/cmds/tm/NOTICE b/cmds/tm/NOTICE new file mode 100644 index 0000000000000..0820f6d9e2130 --- /dev/null +++ b/cmds/tm/NOTICE @@ -0,0 +1,190 @@ + + 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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/cmds/tm/src/com/android/commands/tm/Tm.java b/cmds/tm/src/com/android/commands/tm/Tm.java new file mode 100644 index 0000000000000..af1ac75e9e173 --- /dev/null +++ b/cmds/tm/src/com/android/commands/tm/Tm.java @@ -0,0 +1,201 @@ +/* +** +** Copyright 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.commands.tm; + +import android.content.pm.IPackageManager; +import android.content.pm.PackageInfo; +import android.content.pm.ParceledListSlice; +import android.content.pm.ThemeUtils; +import android.content.res.IThemeService; +import android.content.res.ThemeChangeRequest; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.util.AndroidException; +import com.android.internal.os.BaseCommand; + +import java.io.PrintStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Tm extends BaseCommand { + private static final String SYSTEM_THEME = "system"; + + IThemeService mTs; + IPackageManager mPm; + + /** + * Command-line entry point. + * + * @param args The command-line arguments + */ + public static void main(String[] args) { + (new Tm()).run(args); + } + + public void onShowUsage(PrintStream out) { + List components = ThemeUtils.getAllComponents(); + StringBuilder sb = new StringBuilder(); + sb.append("usage: tm [subcommand] [options]\n"); + sb.append(" tm list\n"); + sb.append(" tm apply [-r] [-c [-c ] ...]\n"); + sb.append(" tm rebuild\n"); + sb.append(" tm process \n"); + sb.append("\n"); + sb.append("tm list: return a list of theme packages.\n"); + sb.append("\n"); + sb.append("tm apply: applies the components for the theme specified by PACKAGE_NAME.\n"); + sb.append(" -r: remove per app themes\n"); + sb.append(" [-c [-c ] ...]\n"); + sb.append(" if no components are specified all components will be applied.\n"); + sb.append(" Valid components are:\n"); + for (String component : components) { + sb.append(" "); + sb.append(component); + sb.append("\n"); + } + sb.append("\n"); + sb.append("tm rebuild: rebuilds the resource cache.\n"); + sb.append("\n"); + sb.append("tm process: processes the theme resources for the theme specified by " + + "PACKAGE_NAME.\n"); + + out.println(sb.toString()); + } + + public void onRun() throws Exception { + mTs = IThemeService.Stub.asInterface(ServiceManager.getService("themes")); + if (mTs == null) { + System.err.println(NO_SYSTEM_ERROR_CODE); + throw new AndroidException("Can't connect to theme service; is the system running?"); + } + + mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); + if (mPm == null) { + System.err.println(NO_SYSTEM_ERROR_CODE); + throw new AndroidException("Can't connect to package manager; is the system running?"); + } + + String op = nextArgRequired(); + + if (op.equals("list")) { + runListThemePackages(); + } else if (op.equals("apply")) { + runApplyTheme(); + } else if (op.equals("rebuild")) { + runRebuildResourceCache(); + } else if (op.equals("process")) { + runProcessTheme(); + } else { + showError("Error: unknown command '" + op + "'"); + return; + } + } + + private void runListThemePackages() throws Exception { + List packages = getInstalledPackages(mPm, 0, UserHandle.USER_OWNER); + + // there is always a "system" theme available + System.out.println("package:system [theme]"); + for (PackageInfo info : packages) { + if (info.isThemeApk || info.isLegacyIconPackApk) { + System.out.print("package:"); + System.out.print(info.packageName); + if (info.isThemeApk) { + System.out.println(" [theme]"); + } else { + System.out.println(" [icon pack]"); + } + } + } + } + + private void runApplyTheme() throws Exception { + String pkgName = nextArg(); + if (pkgName == null) { + System.err.println("Error: didn't specify theme package to apply"); + return; + } + if (!SYSTEM_THEME.equals(pkgName)) { + PackageInfo info = mPm.getPackageInfo(pkgName, 0, UserHandle.USER_OWNER); + if (info == null) { + System.err.println("Error: invalid package name"); + return; + } + if (!(info.isThemeApk || info.isLegacyIconPackApk)) { + System.err.println("Error: package is not a theme or icon pack"); + return; + } + } + + boolean removePerAppThemes = false; + + ThemeChangeRequest.Builder builder = new ThemeChangeRequest.Builder(); + String opt; + while ((opt=nextOption()) != null) { + if (opt.equals("-c")) { + builder.setComponent(nextArgRequired(), pkgName); + } else if (opt.equals("-r")) { + removePerAppThemes = true; + } + } + + // No components specified so let's just try and apply EVERYTHING! + Map componentMap = builder.build().getThemeComponentsMap(); + if (componentMap.size() == 0) { + List components = ThemeUtils.getAllComponents(); + for (String component : components) { + builder.setComponent(component, pkgName); + } + } + mTs.requestThemeChange(builder.build(), removePerAppThemes); + } + + private void runRebuildResourceCache() throws Exception { + mTs.rebuildResourceCache(); + } + + private void runProcessTheme() throws Exception { + String pkgName = nextArg(); + if (pkgName == null) { + System.err.println("Error: didn't specify theme package to apply"); + return; + } + PackageInfo info = mPm.getPackageInfo(pkgName, 0, UserHandle.USER_OWNER); + if (info == null) { + System.err.println("Error: invalid package name"); + return; + } + if (!info.isThemeApk) { + System.err.println("Error: package is not a theme"); + return; + } + + mTs.processThemeResources(pkgName); + } + + @SuppressWarnings("unchecked") + private List getInstalledPackages(IPackageManager pm, int flags, int userId) + throws RemoteException { + ParceledListSlice slice = pm.getInstalledPackages(flags, userId); + return slice.getList(); + } + +} diff --git a/cmds/tm/tm b/cmds/tm/tm new file mode 100755 index 0000000000000..dc95b6ffc59f1 --- /dev/null +++ b/cmds/tm/tm @@ -0,0 +1,6 @@ +# Script to start "tm" on the device, which has a very rudimentary +# shell. +# +base=/system +export CLASSPATH=$base/framework/tm.jar +exec app_process $base/bin com.android.commands.tm.Tm "$@" diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index c6e9b251c5ea1..939876b805617 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -796,7 +796,9 @@ private static final class ManagedCursor { SharedElementCallback mEnterTransitionListener = SharedElementCallback.NULL_CALLBACK; SharedElementCallback mExitTransitionListener = SharedElementCallback.NULL_CALLBACK; - /** Return the activity handler instance. */ + /** Return the activity handler instance. + * @hide + */ public Handler getHandler() { return mHandler; } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 37e8aa4939ca7..01ea22918d074 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -38,6 +38,7 @@ 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.util.DisplayMetrics; import android.util.Size; import android.util.Slog; + import org.xmlpull.v1.XmlSerializer; import java.io.FileDescriptor; @@ -2206,6 +2208,16 @@ public List getRunningExternalApplications() { 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 void setExcludeFromRecents(boolean exclude) { } } } + + /** + * @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 dd49009281f3a..37f5360d1c66a 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -43,6 +43,7 @@ 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.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.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 @@ Configuration applyConfigCompatMainThread(int displayDensity, Configuration conf */ 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 @@ private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compat 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 @@ private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compat new WeakReference(packageInfo)); } } + if (packageInfo.mResources != null + && !packageInfo.mResources.getAssets().isUpToDate()) { + packageInfo.mResources = null; + } return packageInfo; } } @@ -4088,8 +4104,10 @@ static void freeTextLayoutCachesIfNeeded(int configDiff) { 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 hasFontConfigChange = ((configDiff & ActivityInfo.CONFIG_THEME_FONT) != 0); + if (hasLocaleConfigChange || hasFontConfigChange) { 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 1e1a6130bb186..4facb6debc317 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -18,6 +18,7 @@ import android.content.ComponentName; import android.content.ContentResolver; +import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; @@ -930,7 +931,7 @@ public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) { 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 +966,48 @@ public Resources getResourcesForApplicationAsUser(String appPackageName, int use 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 { @@ -1761,4 +1804,30 @@ private UserInfo getUserIfProfile(int userHandle) { = new ArrayMap>(); private static ArrayMap> sStringCache = new ArrayMap>(); + + /** + * @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 0000000000000..8a1bab52d8c3a --- /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 0000000000000..7fab85241b1ec --- /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 CREATOR + = new Creator() { + @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 7fafc380ddb86..b476cc860ce53 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -19,6 +19,8 @@ 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; @@ -762,6 +764,14 @@ public Object createService(ContextImpl ctx) { IBinder b = ServiceManager.getService(APPWIDGET_SERVICE); return new AppWidgetManager(ctx, IAppWidgetService.Stub.asInterface(b)); }}); + + 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) { @@ -2093,13 +2103,19 @@ private void warnIfCallingFromSystemProcess() { @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; } @@ -2112,24 +2128,30 @@ public Context createApplicationContext(ApplicationInfo application, int flags) @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; } @@ -2201,7 +2223,7 @@ public int getUserId() { 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; @@ -2210,7 +2232,7 @@ static ContextImpl createSystemContext(ActivityThread mainThread) { 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, @@ -2218,12 +2240,19 @@ static ContextImpl createActivityContext(ActivityThread mainThread, 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; @@ -2253,15 +2282,19 @@ private ContextImpl(ContextImpl container, ActivityThread mainThread, 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 3b5900b4a5d74..3117c2d438822 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 991b2bc924b5f..b217318291da5 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 0000000000000..057633f89ee0b --- /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 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(); + 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 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 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 getIconResMapFromXml(Resources res, String packageName) { + XmlPullParser parser = null; + InputStream inputStream = null; + Map iconPackResources = new HashMap(); + + 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 mMatrixList; + + public Builder() { + mMatrixList = new ArrayList(); + } + + 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 aa98e973855ce..c8aa72053eb02 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -531,7 +531,8 @@ public AssetManager getAssets(ActivityThread mainThread) { 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 1691d8e28f5e6..6100d58a05313 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -18,21 +18,36 @@ 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(); CompatibilityInfo mResCompatibilityInfo; + static IPackageManager sPackageManager; Configuration mResConfiguration; final Configuration mTmpConfig = new Configuration(); @@ -150,10 +166,14 @@ public boolean applyCompatConfiguration(int displayDensity, * @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; + final ThemeConfig themeConfig = getThemeConfig(); + ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale, + isThemeable, themeConfig, token); Resources r; synchronized (this) { // Resources is app scale dependent. @@ -178,6 +198,8 @@ public Resources getTopLevelResources(String resDir, String[] splitResDirs, //} 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 +219,7 @@ public Resources getTopLevelResources(String resDir, String[] splitResDirs, if (overlayDirs != null) { for (String idmapPath : overlayDirs) { - assets.addOverlayPath(idmapPath); + assets.addOverlayPath(idmapPath, null, null, null, null); } } @@ -226,7 +248,29 @@ public Resources getTopLevelResources(String resDir, String[] splitResDirs, } 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 +293,115 @@ public Resources getTopLevelResources(String resDir, String[] splitResDirs, } } + /** + * 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 iconResources = new SparseArray(); + 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 +446,22 @@ public final boolean applyConfigurationToResourcesLocked(Configuration config, 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 +478,9 @@ public final boolean applyConfigurationToResourcesLocked(Configuration config, } else { r.updateConfiguration(config, dm, compat); } + if (themeChanged) { + r.updateStringCache(); + } //Slog.i(TAG, "Updated app resources " + v.getKey() // + " " + r + ": " + r.getConfiguration()); } else { @@ -320,4 +492,242 @@ public final boolean applyConfigurationToResourcesLocked(Configuration config, 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 = piTheme.packageName; + 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(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 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 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); + } + + private ThemeConfig getThemeConfig() { + Configuration config = getConfiguration(); + if (config != null) { + return config.themeConfig; + } + return null; + } } diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java index a40b29a25d7d8..d7c4467b3af11 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -540,11 +540,6 @@ public class SearchManager private final Context mContext; - /** - * The package associated with this seach manager. - */ - private String mAssociatedPackage; - // package private since they are used by the inner class SearchManagerCallback /* package */ final Handler mHandler; /* package */ OnDismissListener mDismissListener = null; @@ -742,10 +737,6 @@ public ComponentName getWebSearchActivity() { public void triggerSearch(String query, ComponentName launchActivity, Bundle appSearchData) { - if (!mAssociatedPackage.equals(launchActivity.getPackageName())) { - throw new IllegalArgumentException("invoking app search on a different package " + - "not associated with this search manager"); - } if (query == null || TextUtils.getTrimmedLength(query) == 0) { Log.w(TAG, "triggerSearch called with empty query, ignoring."); return; diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 8bfe6d3859ae0..1e52d86ff76bf 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -32,6 +32,7 @@ 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 @@ static class Globals extends IWallpaperManagerCallback.Stub { 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 void onWallpaperChanged() { } } + public void onKeyguardWallpaperChanged() { + synchronized (this) { + mKeyguardWallpaper = null; + } + } + public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) { synchronized (this) { if (mWallpaper != null) { @@ -272,6 +280,23 @@ public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) { } } + /** + * @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 @@ private Bitmap getCurrentWallpaperLocked(Context context) { } 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 @@ private Bitmap getDefaultWallpaperLocked(Context context) { } 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 Drawable getFastDrawable() { 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. @@ -610,6 +682,13 @@ public Bitmap getBitmap() { return sGlobals.peekWallpaperBitmap(mContext, true); } + /** + * @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 @@ -770,6 +849,35 @@ public void setBitmap(Bitmap bitmap) throws IOException { } } + /** + * @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 @@ -809,6 +917,33 @@ public void setStream(InputStream data) throws IOException { } } + /** + * @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 void setWallpaperOffsetSteps(float xStep, float yStep) { 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 void clearWallpaperOffsets(IBinder windowToken) { * 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 c9b7d0a5dca52..883872590321e 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2692,6 +2692,16 @@ public abstract boolean startInstrumentation(@NonNull ComponentName className, */ public static final String BATTERY_SERVICE = "batterymanager"; + /** + * 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. @@ -3355,6 +3365,26 @@ public abstract Context createPackageContextAsUser( public abstract Context createApplicationContext(ApplicationInfo application, 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 ad7c350aa24e1..837208619d726 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -668,7 +668,20 @@ public Context createPackageContextAsUser(String packageName, int flags, UserHan /** @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 af6f1816ae8c1..2a121d3bfd6de 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 static Intent createChooser(Intent target, CharSequence title) { */ @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 @@ -2609,6 +2619,21 @@ public static Intent createChooser(Intent target, CharSequence title) { public static final String ACTION_QUICK_CLOCK = "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 @@ -2704,6 +2729,19 @@ public static Intent createChooser(Intent target, CharSequence title) { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 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. + * + *

This is a protected intent that can only be sent + * by the system.

+ * + * @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} @@ -2922,6 +2960,14 @@ public static Intent createChooser(Intent target, CharSequence title) { @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()). @@ -3431,6 +3477,26 @@ public static Intent createChooser(Intent target, CharSequence title) { /** {@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 641f843ea2d59..b92f0621683ae 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 @@ -539,6 +540,16 @@ public class ActivityInfo extends ComponentInfo * {@link android.R.attr#configChanges} attribute. */ public static final int CONFIG_LAYOUT_DIRECTION = 0x2000; + /** + * Bit in {@link #configChanges} that indicates a theme change occurred + * @hide + */ + public static final int CONFIG_THEME_RESOURCE = 0x100000; + /** + * Bit in {@link #configChanges} that indicates a font change occurred + * @hide + */ + public static final int CONFIG_THEME_FONT = 0x200000; /** * Bit in {@link #configChanges} that indicates that the activity * can itself handle changes to the font scaling factor. Set from the diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index e07edba8892c0..5033f4f412065 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. @@ -587,6 +588,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; + public void dump(Printer pw, String prefix) { super.dumpFront(pw, prefix); if (className != null) { @@ -709,6 +716,7 @@ public ApplicationInfo(ApplicationInfo orig) { descriptionRes = orig.descriptionRes; uiOptions = orig.uiOptions; backupAgentName = orig.backupAgentName; + isThemeable = orig.isThemeable; } @@ -759,6 +767,7 @@ public void writeToParcel(Parcel dest, int parcelableFlags) { dest.writeString(backupAgentName); dest.writeInt(descriptionRes); dest.writeInt(uiOptions); + dest.writeInt(isThemeable ? 1 : 0); } public static final Parcelable.Creator CREATOR @@ -808,6 +817,7 @@ private ApplicationInfo(Parcel source) { backupAgentName = source.readString(); descriptionRes = source.readInt(); uiOptions = source.readInt(); + 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 0000000000000..8ece42d1933d3 --- /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 CREATOR + = new Parcelable.Creator() { + 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 c37534aa0334c..2e1bf85597c88 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; @@ -455,4 +456,9 @@ interface IPackageManager { KeySet getSigningKeySet(String packageName); boolean isPackageSignedByKeySet(String packageName, in KeySet ks); boolean isPackageSignedByKeySetExactly(String packageName, in KeySet ks); + + /** 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 b66bd01727580..ed6d734d5a48e 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 mOverlayTargets; + + // Is Legacy Icon Apk + /** + * {@hide} + */ + public boolean isLegacyIconPackApk = false; + + // ThemeInfo + /** + * {@hide} + */ + public ThemeInfo themeInfo; + /** @hide */ public boolean requiredForAllUsers; @@ -301,6 +335,13 @@ public void writeToParcel(Parcel dest, int parcelableFlags) { 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 CREATOR @@ -346,5 +387,12 @@ private PackageInfo(Parcel source) { 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 e336c5f752ab9..3622df4d17408 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 void writeToParcel(Parcel dest, int parcelableFlags) { 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 @@ private PackageInfoLite(Parcel source) { 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 cacdf8e36250a..cbd6198607bf9 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 PackageItemInfo(PackageItemInfo orig) { logo = orig.logo; metaData = orig.metaData; showUserIcon = orig.showUserIcon; + themedIcon = orig.themedIcon; } /** @@ -292,8 +300,9 @@ public void writeToParcel(Parcel dest, int parcelableFlags) { 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 @@ protected PackageItemInfo(Parcel source) { 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 e5191630fa60b..0602569b2907c 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -770,6 +770,38 @@ public NameNotFoundException(String name) { /** {@hide} */ 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. @@ -3096,6 +3128,18 @@ public abstract Resources getResourcesForApplication(String appPackageName) 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 @@ -4092,4 +4136,22 @@ public void onPackageDeleted(String basePackageName, int returnCode, String msg) } } } + + /** + * 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 ca4ff6a49c6c5..f69dd13e5b178 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.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.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 static class PackageLite { 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 static class PackageLite { 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 PackageLite(String codePath, ApkLite baseApk, String[] splitNames, this.splitCodePaths = splitCodePaths; this.coreApp = baseApk.coreApp; this.multiArch = baseApk.multiArch; + this.isTheme = baseApk.isTheme; } public List getAllCodePaths() { @@ -302,10 +322,11 @@ public static class ApkLite { 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 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 ApkLite(String codePath, String packageName, String splitName, int versio this.signatures = signatures; this.coreApp = coreApp; this.multiArch = multiArch; + this.isTheme = isTheme; } } @@ -413,6 +435,14 @@ public static PackageInfo generatePackageInfo(PackageParser.Package p, 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 @@ private Package parseBaseApk(File apkFile, AssetManager assets, int flags) 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 overlayTargets = scanPackageOverlays(apkFile); + for(String overlay : overlayTargets) { + pkg.mOverlayTargets.add(overlay); + } + + pkg.hasIconPack = packageHasIconPack(apkFile); + } + return pkg; } catch (PackageParserException e) { @@ -996,6 +1038,68 @@ private Package parseSplitApk(Package pkg, Resources res, XmlResourceParser pars return pkg; } + + private ArrayList scanPackageOverlays(File originalFile) { + Set overlayTargets = new HashSet(); + ZipFile privateZip = null; + try { + privateZip = new ZipFile(originalFile.getPath()); + final Enumeration 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(); + } finally { + if (privateZip != null) { + try { + privateZip.close(); + } catch (Exception e) { + //Ignore + } + } + } + + ArrayList overlays = new ArrayList(); + overlays.addAll(overlayTargets); + return overlays; + } + + private boolean packageHasIconPack(File originalFile) { + ZipFile privateZip = null; + try { + privateZip = new ZipFile(originalFile.getPath()); + final Enumeration 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); + } finally { + if (privateZip != null) { + try { + privateZip.close(); + } catch (Exception e) { + //Ignore + } + } + } + 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 +1379,9 @@ private static ApkLite parseApkLite(String codePath, Resources res, XmlPullParse // Only search the tree when the tag is directly below int type; final int searchDepth = parser.getDepth() + 1; + // Search for category and actions inside + final int iconPackSearchDepth = parser.getDepth() + 4; + boolean isTheme = false; final List verifiers = new ArrayList(); while ((type = parser.next()) != XmlPullParser.END_DOCUMENT @@ -1299,10 +1406,52 @@ private static ApkLite parseApkLite(String codePath, Resources res, XmlPullParse } } } + + 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 +1503,8 @@ private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags, } final Package pkg = new Package(pkgName); + Bundle metaDataBundle = new Bundle(); + boolean foundApp = false; TypedArray sa = res.obtainAttributes(attrs, @@ -1759,6 +1910,11 @@ private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags, 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 : " + parser.getName(); @@ -1849,6 +2005,9 @@ private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags, >= 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 attribute if @@ -1861,6 +2020,14 @@ private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags, } } + //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 +2556,9 @@ private boolean parseBaseApplication(Package owner, Resources res, 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 +3345,26 @@ private Activity parseActivity(Package owner, Resources res, 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 +4436,17 @@ public final static class Package { // 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 mOverlayTargets = new ArrayList(0); + public Map> mPackageRedirections + = new HashMap>(); + + // 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 +4492,8 @@ public final static class Package { 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 0000000000000..acbc85e9c8b98 --- /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 0000000000000..ab798db7b0064 --- /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: + * + * + * + * + * @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 CREATOR + = new Parcelable.Creator() { + 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 0000000000000..a5a523c74f9d3 --- /dev/null +++ b/core/java/android/content/pm/ThemeUtils.java @@ -0,0 +1,733 @@ +/* + * 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; + + // Package name for any app which does not have a specific theme applied + private static final String DEFAULT_PKG = "default"; + + 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 getAllComponents() { + List components = new ArrayList(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 getSupportedComponents(Context context, String pkgName) { + List supportedComponents = new ArrayList(); + + 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 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 getDefaultComponents(Context context) { + String defaultThemePkg = getDefaultThemePackageName(context); + List defaultComponents = null; + List systemComponents = getSupportedComponents(context, SYSTEM_DEFAULT); + if (!SYSTEM_DEFAULT.equals(defaultThemePkg)) { + defaultComponents = getSupportedComponents(context, defaultThemePkg); + } + + Map componentMap = new HashMap(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 componentMap) { + if (componentMap == null) return; + + Map 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; + } + + /** + * Convenience method to determine if a theme component is a per app theme and not a standard + * component. + * @param component + * @return + */ + public static boolean isPerAppThemeComponent(String component) { + return !(DEFAULT_PKG.equals(component) + || ThemeConfig.SYSTEMUI_STATUS_BAR_PKG.equals(component) + || ThemeConfig.SYSTEMUI_NAVBAR_PKG.equals(component)); + } +} diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index e57882240e567..74a906ffb4e5c 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -21,9 +21,11 @@ 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 mRefStacks; + private String mAppName; + + private boolean mThemeSupport; + private String mThemePackageName; + private String mIconPackageName; + private String mCommonResPackageName; + private ArrayList mThemeCookies = new ArrayList(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 void close() { } } + /*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(); @@ -612,7 +630,9 @@ protected void finalize() throws Throwable public final int addAssetPath(String path) { synchronized (this) { int res = addAssetPathNative(path); - makeStringBlocks(mStringBlocks); + if (mStringBlocks != null) { + makeStringBlocks(mStringBlocks); + } return res; } } @@ -627,7 +647,72 @@ public final int addAssetPath(String path) { * * {@hide} */ - public native 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) { + 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 0; + } + } + + 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 @@ -649,6 +734,126 @@ public final int[] addAssetPaths(String[] paths) { return cookies; } + /** + * 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 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 @@ -776,6 +981,26 @@ private native final int loadResourceBagValue(int ident, int bagEntryId, TypedVa /*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 da35ee92267e4..47d5d0551ee53 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 CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw, } 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 boolean equals(Object o) { 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 int hashCode() { 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 void writeToParcel(Parcel dest, int flags) { dest.writeInt(applicationDensity); dest.writeFloat(applicationScale); dest.writeFloat(applicationInvertedScale); + dest.writeInt(isThemeable ? 1 : 0); } public static final Parcelable.Creator CREATOR @@ -597,5 +608,6 @@ private CompatibilityInfo(Parcel source) { 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 27bbb242f575a..cc8c65995d5e3 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. @@ -81,6 +82,11 @@ public final class Configuration implements Parcelable, Comparable