diff options
198 files changed, 6607 insertions, 3329 deletions
diff --git a/api/current.txt b/api/current.txt index ed9ebbfbdb1b..1b68ab539a02 100644 --- a/api/current.txt +++ b/api/current.txt @@ -894,7 +894,7 @@ package android { field public static final int permissionFlags = 16843719; // 0x10103c7 field public static final int permissionGroup = 16842762; // 0x101000a field public static final int permissionGroupFlags = 16843717; // 0x10103c5 - field public static final int persistable = 16843823; // 0x101042f + field public static final int persistableMode = 16843823; // 0x101042f field public static final int persistent = 16842765; // 0x101000d field public static final int persistentDrawingCache = 16842990; // 0x10100ee field public static final deprecated int phoneNumber = 16843111; // 0x1010167 @@ -5194,6 +5194,7 @@ package android.app.admin { method public java.lang.CharSequence onDisableRequested(android.content.Context, android.content.Intent); method public void onDisabled(android.content.Context, android.content.Intent); method public void onEnabled(android.content.Context, android.content.Intent); + method public void onLockTaskModeChanged(android.content.Context, android.content.Intent, boolean, java.lang.String); method public void onPasswordChanged(android.content.Context, android.content.Intent); method public void onPasswordExpiring(android.content.Context, android.content.Intent); method public void onPasswordFailed(android.content.Context, android.content.Intent); @@ -5203,6 +5204,7 @@ package android.app.admin { field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED"; field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED"; field public static final java.lang.String ACTION_DEVICE_ADMIN_ENABLED = "android.app.action.DEVICE_ADMIN_ENABLED"; + field public static final java.lang.String ACTION_LOCK_TASK_CHANGED = "android.app.action.ACTION_LOCK_TASK_CHANGED"; field public static final java.lang.String ACTION_PASSWORD_CHANGED = "android.app.action.ACTION_PASSWORD_CHANGED"; field public static final java.lang.String ACTION_PASSWORD_EXPIRING = "android.app.action.ACTION_PASSWORD_EXPIRING"; field public static final java.lang.String ACTION_PASSWORD_FAILED = "android.app.action.ACTION_PASSWORD_FAILED"; @@ -5210,6 +5212,8 @@ package android.app.admin { field public static final java.lang.String ACTION_PROFILE_PROVISIONING_COMPLETE = "android.app.action.ACTION_PROFILE_PROVISIONING_COMPLETE"; field public static final java.lang.String DEVICE_ADMIN_META_DATA = "android.app.device_admin"; field public static final java.lang.String EXTRA_DISABLE_WARNING = "android.app.extra.DISABLE_WARNING"; + field public static final java.lang.String EXTRA_LOCK_TASK_ENTERING = "android.app.extra.LOCK_TASK_ENTERING"; + field public static final java.lang.String EXTRA_LOCK_TASK_PACKAGE = "android.app.extra.LOCK_TASK_PACKAGE"; } public class DevicePolicyManager { @@ -5217,7 +5221,7 @@ package android.app.admin { method public void addPersistentPreferredActivity(android.content.ComponentName, android.content.IntentFilter, android.content.ComponentName); method public void addUserRestriction(android.content.ComponentName, java.lang.String); method public void clearCrossProfileIntentFilters(android.content.ComponentName); - method public void clearDeviceOwnerApp(); + method public void clearDeviceOwnerApp(java.lang.String); method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String); method public void clearUserRestriction(android.content.ComponentName, java.lang.String); method public android.os.UserHandle createAndInitializeUser(android.content.ComponentName, java.lang.String, java.lang.String, android.content.ComponentName, android.os.Bundle); @@ -5249,7 +5253,7 @@ package android.app.admin { method public boolean isAdminActive(android.content.ComponentName); method public boolean isApplicationBlocked(android.content.ComponentName, java.lang.String); method public boolean isDeviceOwnerApp(java.lang.String); - method public boolean isLockTaskPermitted(android.content.ComponentName); + method public boolean isLockTaskPermitted(java.lang.String); method public boolean isMasterVolumeMuted(android.content.ComponentName); method public boolean isProfileOwnerApp(java.lang.String); method public void lockNow(); @@ -5263,7 +5267,7 @@ package android.app.admin { method public void setCameraDisabled(android.content.ComponentName, boolean); method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String); method public void setKeyguardDisabledFeatures(android.content.ComponentName, int); - method public void setLockTaskComponents(android.content.ComponentName[]) throws java.lang.SecurityException; + method public void setLockTaskPackages(java.lang.String[]) throws java.lang.SecurityException; method public void setMasterVolumeMuted(android.content.ComponentName, boolean); method public void setMaximumFailedPasswordsForWipe(android.content.ComponentName, int); method public void setMaximumTimeToLock(android.content.ComponentName, long); @@ -8062,6 +8066,7 @@ package android.content.pm { field public static final int DOCUMENT_LAUNCH_INTO_EXISTING = 1; // 0x1 field public static final int DOCUMENT_LAUNCH_NEVER = 3; // 0x3 field public static final int DOCUMENT_LAUNCH_NONE = 0; // 0x0 + field public static final int DO_NOT_PERSIST = 1; // 0x1 field public static final int FLAG_ALLOW_TASK_REPARENTING = 64; // 0x40 field public static final int FLAG_ALWAYS_RETAIN_TASK_STATE = 8; // 0x8 field public static final int FLAG_AUTO_REMOVE_FROM_RECENTS = 8192; // 0x2000 @@ -8073,13 +8078,14 @@ package android.content.pm { field public static final int FLAG_IMMERSIVE = 2048; // 0x800 field public static final int FLAG_MULTIPROCESS = 1; // 0x1 field public static final int FLAG_NO_HISTORY = 128; // 0x80 - field public static final int FLAG_PERSISTABLE = 4096; // 0x1000 field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000 field public static final int FLAG_STATE_NOT_NEEDED = 16; // 0x10 field public static final int LAUNCH_MULTIPLE = 0; // 0x0 field public static final int LAUNCH_SINGLE_INSTANCE = 3; // 0x3 field public static final int LAUNCH_SINGLE_TASK = 2; // 0x2 field public static final int LAUNCH_SINGLE_TOP = 1; // 0x1 + field public static final int PERSIST_ACROSS_REBOOTS = 2; // 0x2 + field public static final int PERSIST_ROOT_ONLY = 0; // 0x0 field public static final int SCREEN_ORIENTATION_BEHIND = 3; // 0x3 field public static final int SCREEN_ORIENTATION_FULL_SENSOR = 10; // 0xa field public static final int SCREEN_ORIENTATION_FULL_USER = 13; // 0xd @@ -8104,6 +8110,7 @@ package android.content.pm { field public int maxRecents; field public java.lang.String parentActivityName; field public java.lang.String permission; + field public int persistableMode; field public int screenOrientation; field public int softInputMode; field public java.lang.String targetActivity; @@ -8161,6 +8168,8 @@ package android.content.pm { field public int requiresSmallestWidthDp; field public java.lang.String[] sharedLibraryFiles; field public java.lang.String sourceDir; + field public java.lang.String[] splitPublicSourceDirs; + field public java.lang.String[] splitSourceDirs; field public int targetSdkVersion; field public java.lang.String taskAffinity; field public int theme; @@ -8229,6 +8238,8 @@ package android.content.pm { field public boolean handleProfiling; field public java.lang.String publicSourceDir; field public java.lang.String sourceDir; + field public java.lang.String[] splitPublicSourceDirs; + field public java.lang.String[] splitSourceDirs; field public java.lang.String targetPackage; } @@ -8258,15 +8269,26 @@ package android.content.pm { } public class LauncherApps { + method public void addOnAppsChangedCallback(android.content.pm.LauncherApps.OnAppsChangedCallback); method public void addOnAppsChangedListener(android.content.pm.LauncherApps.OnAppsChangedListener); method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle); method public boolean isActivityEnabledForProfile(android.content.ComponentName, android.os.UserHandle); method public boolean isPackageEnabledForProfile(java.lang.String, android.os.UserHandle); + method public void removeOnAppsChangedCallback(android.content.pm.LauncherApps.OnAppsChangedCallback); method public void removeOnAppsChangedListener(android.content.pm.LauncherApps.OnAppsChangedListener); method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle); method public void startActivityForProfile(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle); } + public static abstract class LauncherApps.OnAppsChangedCallback { + ctor public LauncherApps.OnAppsChangedCallback(); + method public abstract void onPackageAdded(java.lang.String, android.os.UserHandle); + method public abstract void onPackageChanged(java.lang.String, android.os.UserHandle); + method public abstract void onPackageRemoved(java.lang.String, android.os.UserHandle); + method public abstract void onPackagesAvailable(java.lang.String[], android.os.UserHandle, boolean); + method public abstract void onPackagesUnavailable(java.lang.String[], android.os.UserHandle, boolean); + } + public static abstract interface LauncherApps.OnAppsChangedListener { method public abstract void onPackageAdded(android.os.UserHandle, java.lang.String); method public abstract void onPackageChanged(android.os.UserHandle, java.lang.String); @@ -15829,6 +15851,7 @@ package android.media.tv { } public static final class TvContract.Channels implements android.media.tv.TvContract.BaseTvColumns { + method public static final java.lang.String getVideoResolution(java.lang.String); field public static final java.lang.String COLUMN_BROWSABLE = "browsable"; field public static final java.lang.String COLUMN_DESCRIPTION = "description"; field public static final java.lang.String COLUMN_DISPLAY_NAME = "display_name"; @@ -15842,6 +15865,7 @@ package android.media.tv { field public static final java.lang.String COLUMN_TRANSPORT_STREAM_ID = "transport_stream_id"; field public static final java.lang.String COLUMN_TYPE = "type"; field public static final java.lang.String COLUMN_VERSION_NUMBER = "version_number"; + field public static final java.lang.String COLUMN_VIDEO_FORMAT = "video_format"; field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/channel"; field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/channel"; field public static final android.net.Uri CONTENT_URI; @@ -15873,6 +15897,22 @@ package android.media.tv { field public static final int TYPE_SECAM = 3; // 0x3 field public static final int TYPE_S_DMB = 393472; // 0x60100 field public static final int TYPE_T_DMB = 393216; // 0x60000 + field public static final java.lang.String VIDEO_FORMAT_1080I = "VIDEO_FORMAT_1080I"; + field public static final java.lang.String VIDEO_FORMAT_1080P = "VIDEO_FORMAT_1080P"; + field public static final java.lang.String VIDEO_FORMAT_2160P = "VIDEO_FORMAT_2160P"; + field public static final java.lang.String VIDEO_FORMAT_240P = "VIDEO_FORMAT_240P"; + field public static final java.lang.String VIDEO_FORMAT_360P = "VIDEO_FORMAT_360P"; + field public static final java.lang.String VIDEO_FORMAT_4320P = "VIDEO_FORMAT_4320P"; + field public static final java.lang.String VIDEO_FORMAT_480I = "VIDEO_FORMAT_480I"; + field public static final java.lang.String VIDEO_FORMAT_480P = "VIDEO_FORMAT_480P"; + field public static final java.lang.String VIDEO_FORMAT_576I = "VIDEO_FORMAT_576I"; + field public static final java.lang.String VIDEO_FORMAT_576P = "VIDEO_FORMAT_576P"; + field public static final java.lang.String VIDEO_FORMAT_720P = "VIDEO_FORMAT_720P"; + field public static final java.lang.String VIDEO_RESOLUTION_ED = "VIDEO_RESOLUTION_ED"; + field public static final java.lang.String VIDEO_RESOLUTION_FHD = "VIDEO_RESOLUTION_FHD"; + field public static final java.lang.String VIDEO_RESOLUTION_HD = "VIDEO_RESOLUTION_HD"; + field public static final java.lang.String VIDEO_RESOLUTION_SD = "VIDEO_RESOLUTION_SD"; + field public static final java.lang.String VIDEO_RESOLUTION_UHD = "VIDEO_RESOLUTION_UHD"; } public static final class TvContract.Channels.Logo { @@ -15893,6 +15933,8 @@ package android.media.tv { field public static final java.lang.String COLUMN_THUMBNAIL_URI = "thumbnail_uri"; field public static final java.lang.String COLUMN_TITLE = "title"; field public static final java.lang.String COLUMN_VERSION_NUMBER = "version_number"; + field public static final java.lang.String COLUMN_VIDEO_HEIGHT = "video_height"; + field public static final java.lang.String COLUMN_VIDEO_WIDTH = "video_width"; field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/program"; field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/program"; field public static final android.net.Uri CONTENT_URI; @@ -21559,7 +21601,7 @@ package android.os { method public void setUserRestrictions(android.os.Bundle, android.os.UserHandle); field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user"; field public static final java.lang.String DISALLOW_ADJUST_VOLUME = "no_adjust_volume"; - field public static final java.lang.String DISALLOW_CONFIG_APPS = "no_config_apps"; + field public static final java.lang.String DISALLOW_APPS_CONTROL = "no_control_apps"; field public static final java.lang.String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth"; field public static final java.lang.String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts"; field public static final java.lang.String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials"; @@ -26033,7 +26075,6 @@ package android.service.dreams { package android.service.fingerprint { public class FingerprintManager { - ctor public FingerprintManager(android.content.Context, android.service.fingerprint.IFingerprintService); method public void enroll(long); method public void enrollCancel(); method public boolean enrolledAndEnabled(); @@ -26074,22 +26115,6 @@ package android.service.fingerprint { method public static boolean removeFingerprintIdForUser(int, android.content.ContentResolver, int); } - public abstract interface IFingerprintService implements android.os.IInterface { - method public abstract void enroll(android.os.IBinder, long, int) throws android.os.RemoteException; - method public abstract void enrollCancel(android.os.IBinder, int) throws android.os.RemoteException; - method public abstract void remove(android.os.IBinder, int, int) throws android.os.RemoteException; - method public abstract void startListening(android.os.IBinder, android.service.fingerprint.IFingerprintServiceReceiver, int) throws android.os.RemoteException; - method public abstract void stopListening(android.os.IBinder, int) throws android.os.RemoteException; - } - - public abstract interface IFingerprintServiceReceiver implements android.os.IInterface { - method public abstract void onAcquired(int) throws android.os.RemoteException; - method public abstract void onEnrollResult(int, int) throws android.os.RemoteException; - method public abstract void onError(int) throws android.os.RemoteException; - method public abstract void onProcessed(int) throws android.os.RemoteException; - method public abstract void onRemoved(int) throws android.os.RemoteException; - } - } package android.service.notification { diff --git a/cmds/idmap/create.cpp b/cmds/idmap/create.cpp index ae35f7b0a8c1..593a1973669a 100644 --- a/cmds/idmap/create.cpp +++ b/cmds/idmap/create.cpp @@ -105,7 +105,7 @@ fail: uint32_t cached_target_crc, cached_overlay_crc; String8 cached_target_path, cached_overlay_path; - if (!ResTable::getIdmapInfo(buf, N, &cached_target_crc, &cached_overlay_crc, + if (!ResTable::getIdmapInfo(buf, N, NULL, &cached_target_crc, &cached_overlay_crc, &cached_target_path, &cached_overlay_path)) { return true; } diff --git a/cmds/idmap/idmap.cpp b/cmds/idmap/idmap.cpp index 46c0edc03523..90cfa2c610f0 100644 --- a/cmds/idmap/idmap.cpp +++ b/cmds/idmap/idmap.cpp @@ -66,26 +66,32 @@ EXAMPLES \n\ Display an idmap file: \n\ \n\ $ adb shell idmap --inspect /data/resource-cache/vendor@overlay@overlay.apk@idmap \n\ - SECTION ENTRY VALUE OFFSET COMMENT \n\ - IDMAP HEADER magic 0x706d6469 0x0 \n\ - base crc 0x484aa77f 0x1 \n\ - overlay crc 0x03c66fa5 0x2 \n\ - base path .......... 0x03-0x42 /system/app/target.apk \n\ - overlay path .......... 0x43-0x82 /vendor/overlay/overlay.apk \n\ - DATA HEADER types count 0x00000003 0x83 \n\ - padding 0x00000000 0x84 \n\ - type offset 0x00000004 0x85 absolute offset 0x87, xml \n\ - type offset 0x00000007 0x86 absolute offset 0x8a, string \n\ - DATA BLOCK entry count 0x00000001 0x87 \n\ - entry offset 0x00000000 0x88 \n\ - entry 0x7f020000 0x89 xml/integer \n\ - DATA BLOCK entry count 0x00000002 0x8a \n\ - entry offset 0x00000000 0x8b \n\ - entry 0x7f030000 0x8c string/str \n\ - entry 0x7f030001 0x8d string/str2 \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\ \n\ In this example, the overlay package provides three alternative resource values:\n\ - xml/integer, string/str and string/str2.\n\ + drawable/drawable, xml/integer, and raw/lorem_ipsum \n\ \n\ NOTES \n\ This tool and its expected invocation from installd is modelled on dexopt."; diff --git a/cmds/idmap/inspect.cpp b/cmds/idmap/inspect.cpp index a59f5d31cab8..b9ac8a59de02 100644 --- a/cmds/idmap/inspect.cpp +++ b/cmds/idmap/inspect.cpp @@ -10,92 +10,108 @@ using namespace android; -#define NEXT(b, i, o) do { if (buf.next(&i, &o) < 0) { return -1; } } while (0) - namespace { - static const uint32_t IDMAP_MAGIC = 0x706d6469; + static const uint32_t IDMAP_MAGIC = 0x504D4449; static const size_t PATH_LENGTH = 256; - static const uint32_t IDMAP_HEADER_SIZE = (3 + 2 * (PATH_LENGTH / sizeof(uint32_t))); void printe(const char *fmt, ...); class IdmapBuffer { private: - char *buf_; + const char* buf_; size_t len_; - mutable size_t pos_; + size_t pos_; public: - IdmapBuffer() : buf_((char *)MAP_FAILED), len_(0), pos_(0) {} + IdmapBuffer() : buf_((const char *)MAP_FAILED), len_(0), pos_(0) {} ~IdmapBuffer() { if (buf_ != MAP_FAILED) { - munmap(buf_, len_); + munmap(const_cast<char*>(buf_), len_); } } - int init(const char *idmap_path) - { + status_t init(const char *idmap_path) { struct stat st; int fd; if (stat(idmap_path, &st) < 0) { printe("failed to stat idmap '%s': %s\n", idmap_path, strerror(errno)); - return -1; + return UNKNOWN_ERROR; } len_ = st.st_size; if ((fd = TEMP_FAILURE_RETRY(open(idmap_path, O_RDONLY))) < 0) { printe("failed to open idmap '%s': %s\n", idmap_path, strerror(errno)); - return -1; + return UNKNOWN_ERROR; } - if ((buf_ = (char*)mmap(NULL, len_, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) { + if ((buf_ = (const char*)mmap(NULL, len_, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) { close(fd); printe("failed to mmap idmap: %s\n", strerror(errno)); - return -1; + return UNKNOWN_ERROR; } close(fd); - return 0; + return NO_ERROR; } - int next(uint32_t *i, uint32_t *offset) const - { + status_t nextUint32(uint32_t* i) { if (!buf_) { printe("failed to read next uint32_t: buffer not initialized\n"); - return -1; + return UNKNOWN_ERROR; } - if (pos_ + 4 > len_) { + + if (pos_ + sizeof(uint32_t) > len_) { printe("failed to read next uint32_t: end of buffer reached at pos=0x%08x\n", pos_); - return -1; + return UNKNOWN_ERROR; + } + + if ((reinterpret_cast<uintptr_t>(buf_ + pos_) & 0x3) != 0) { + printe("failed to read next uint32_t: not aligned on 4-byte boundary\n"); + return UNKNOWN_ERROR; } - *offset = pos_ / sizeof(uint32_t); - char a = buf_[pos_++]; - char b = buf_[pos_++]; - char c = buf_[pos_++]; - char d = buf_[pos_++]; - *i = (d << 24) | (c << 16) | (b << 8) | a; - return 0; + + *i = dtohl(*reinterpret_cast<const uint32_t*>(buf_ + pos_)); + pos_ += sizeof(uint32_t); + return NO_ERROR; } - int nextPath(char *b, uint32_t *offset_start, uint32_t *offset_end) const - { + status_t nextUint16(uint16_t* i) { + if (!buf_) { + printe("failed to read next uint16_t: buffer not initialized\n"); + return UNKNOWN_ERROR; + } + + if (pos_ + sizeof(uint16_t) > len_) { + printe("failed to read next uint16_t: end of buffer reached at pos=0x%08x\n", + pos_); + return UNKNOWN_ERROR; + } + + if ((reinterpret_cast<uintptr_t>(buf_ + pos_) & 0x1) != 0) { + printe("failed to read next uint32_t: not aligned on 2-byte boundary\n"); + return UNKNOWN_ERROR; + } + + *i = dtohs(*reinterpret_cast<const uint16_t*>(buf_ + pos_)); + pos_ += sizeof(uint16_t); + return NO_ERROR; + } + + status_t nextPath(char *b) { if (!buf_) { printe("failed to read next path: buffer not initialized\n"); - return -1; + return UNKNOWN_ERROR; } if (pos_ + PATH_LENGTH > len_) { printe("failed to read next path: end of buffer reached at pos=0x%08x\n", pos_); - return -1; + return UNKNOWN_ERROR; } memcpy(b, buf_ + pos_, PATH_LENGTH); - *offset_start = pos_ / sizeof(uint32_t); pos_ += PATH_LENGTH; - *offset_end = pos_ / sizeof(uint32_t) - 1; - return 0; + return NO_ERROR; } }; - void printe(const char *fmt, ...) - { + void printe(const char *fmt, ...) { va_list ap; va_start(ap, fmt); @@ -104,44 +120,37 @@ namespace { va_end(ap); } - void print_header() - { - printf("SECTION ENTRY VALUE OFFSET COMMENT\n"); + void print_header() { + printf("SECTION ENTRY VALUE COMMENT\n"); } - void print(const char *section, const char *subsection, uint32_t value, uint32_t offset, - const char *fmt, ...) - { + void print(const char *section, const char *subsection, uint32_t value, const char *fmt, ...) { va_list ap; va_start(ap, fmt); - printf("%-12s %-12s 0x%08x 0x%-4x ", section, subsection, value, offset); + printf("%-12s %-12s 0x%08x ", section, subsection, value); vprintf(fmt, ap); printf("\n"); va_end(ap); } - void print_path(const char *section, const char *subsection, uint32_t offset_start, - uint32_t offset_end, const char *fmt, ...) - { + void print_path(const char *section, const char *subsection, const char *fmt, ...) { va_list ap; va_start(ap, fmt); - printf("%-12s %-12s .......... 0x%02x-0x%02x ", section, subsection, offset_start, - offset_end); + printf("%-12s %-12s .......... ", section, subsection); vprintf(fmt, ap); printf("\n"); va_end(ap); } - int resource_metadata(const AssetManager& am, uint32_t res_id, - String8 *package, String8 *type, String8 *name) - { + status_t resource_metadata(const AssetManager& am, uint32_t res_id, + String8 *package, String8 *type, String8 *name) { const ResTable& rt = am.getResources(); struct ResTable::resource_name data; if (!rt.getResourceName(res_id, false, &data)) { printe("failed to get resource name id=0x%08x\n", res_id); - return -1; + return UNKNOWN_ERROR; } if (package) { *package = String8(String16(data.package, data.packageLen)); @@ -152,140 +161,150 @@ namespace { if (name) { *name = String8(String16(data.name, data.nameLen)); } - return 0; - } - - int package_id(const AssetManager& am) - { - return (am.getResources().getBasePackageId(0)) << 24; + return NO_ERROR; } - int parse_idmap_header(const IdmapBuffer& buf, AssetManager& am) - { - uint32_t i, o, e; + status_t parse_idmap_header(IdmapBuffer& buf, AssetManager& am) { + uint32_t i; char path[PATH_LENGTH]; - NEXT(buf, i, o); + status_t err = buf.nextUint32(&i); + if (err != NO_ERROR) { + return err; + } + if (i != IDMAP_MAGIC) { printe("not an idmap file: actual magic constant 0x%08x does not match expected magic " "constant 0x%08x\n", i, IDMAP_MAGIC); - return -1; + return UNKNOWN_ERROR; } + print_header(); - print("IDMAP HEADER", "magic", i, o, ""); + print("IDMAP HEADER", "magic", i, ""); + + err = buf.nextUint32(&i); + if (err != NO_ERROR) { + return err; + } + print("", "version", i, ""); - NEXT(buf, i, o); - print("", "base crc", i, o, ""); + err = buf.nextUint32(&i); + if (err != NO_ERROR) { + return err; + } + print("", "base crc", i, ""); - NEXT(buf, i, o); - print("", "overlay crc", i, o, ""); + err = buf.nextUint32(&i); + if (err != NO_ERROR) { + return err; + } + print("", "overlay crc", i, ""); - if (buf.nextPath(path, &o, &e) < 0) { + err = buf.nextPath(path); + if (err != NO_ERROR) { // printe done from IdmapBuffer::nextPath - return -1; + return err; } - print_path("", "base path", o, e, "%s", path); + print_path("", "base path", "%s", path); + if (!am.addAssetPath(String8(path), NULL)) { printe("failed to add '%s' as asset path\n", path); - return -1; + return UNKNOWN_ERROR; } - if (buf.nextPath(path, &o, &e) < 0) { + err = buf.nextPath(path); + if (err != NO_ERROR) { // printe done from IdmapBuffer::nextPath - return -1; + return err; } - print_path("", "overlay path", o, e, "%s", path); + print_path("", "overlay path", "%s", path); - return 0; + return NO_ERROR; } - int parse_data_header(const IdmapBuffer& buf, const AssetManager& am, Vector<uint32_t>& types) - { - uint32_t i, o; - const uint32_t numeric_package = package_id(am); + status_t parse_data(IdmapBuffer& buf, const AssetManager& am) { + const uint32_t packageId = am.getResources().getBasePackageId(0); - NEXT(buf, i, o); - print("DATA HEADER", "types count", i, o, ""); - const uint32_t N = i; + uint16_t data16; + status_t err = buf.nextUint16(&data16); + if (err != NO_ERROR) { + return err; + } + print("DATA HEADER", "target pkg", static_cast<uint32_t>(data16), ""); - for (uint32_t j = 0; j < N; ++j) { - NEXT(buf, i, o); - if (i == 0) { - print("", "padding", i, o, ""); - } else { - String8 type; - const uint32_t numeric_type = (j + 1) << 16; - const uint32_t res_id = numeric_package | numeric_type; - if (resource_metadata(am, res_id, NULL, &type, NULL) < 0) { - // printe done from resource_metadata - return -1; - } - print("", "type offset", i, o, "absolute offset 0x%02x, %s", - i + IDMAP_HEADER_SIZE, type.string()); - types.add(numeric_type); - } + err = buf.nextUint16(&data16); + if (err != NO_ERROR) { + return err; } + print("", "types count", static_cast<uint32_t>(data16), ""); - return 0; - } + uint32_t typeCount = static_cast<uint32_t>(data16); + while (typeCount > 0) { + typeCount--; + + err = buf.nextUint16(&data16); + if (err != NO_ERROR) { + return err; + } + const uint32_t targetTypeId = static_cast<uint32_t>(data16); + print("DATA BLOCK", "target type", targetTypeId, ""); - int parse_data_block(const IdmapBuffer& buf, const AssetManager& am, size_t numeric_type) - { - uint32_t i, o, n, id_offset; - const uint32_t numeric_package = package_id(am); - - NEXT(buf, i, o); - print("DATA BLOCK", "entry count", i, o, ""); - n = i; - - NEXT(buf, i, o); - print("", "entry offset", i, o, ""); - id_offset = i; - - for ( ; n > 0; --n) { - String8 type, name; - - NEXT(buf, i, o); - if (i == 0) { - print("", "padding", i, o, ""); - } else { - uint32_t res_id = numeric_package | numeric_type | id_offset; - if (resource_metadata(am, res_id, NULL, &type, &name) < 0) { - // printe done from resource_metadata - return -1; + err = buf.nextUint16(&data16); + if (err != NO_ERROR) { + return err; + } + print("", "overlay type", static_cast<uint32_t>(data16), ""); + + err = buf.nextUint16(&data16); + if (err != NO_ERROR) { + return err; + } + const uint32_t entryCount = static_cast<uint32_t>(data16); + print("", "entry count", entryCount, ""); + + err = buf.nextUint16(&data16); + if (err != NO_ERROR) { + return err; + } + const uint32_t entryOffset = static_cast<uint32_t>(data16); + print("", "entry offset", entryOffset, ""); + + for (uint32_t i = 0; i < entryCount; i++) { + uint32_t data32; + err = buf.nextUint32(&data32); + if (err != NO_ERROR) { + return err; } - print("", "entry", i, o, "%s/%s", type.string(), name.string()); + + uint32_t resID = (packageId << 24) | (targetTypeId << 16) | (entryOffset + i); + String8 type; + String8 name; + err = resource_metadata(am, resID, NULL, &type, &name); + if (err != NO_ERROR) { + return err; + } + print("", "entry", data32, "%s/%s", type.string(), name.string()); } - ++id_offset; } - return 0; + return NO_ERROR; } } -int idmap_inspect(const char *idmap_path) -{ +int idmap_inspect(const char *idmap_path) { IdmapBuffer buf; if (buf.init(idmap_path) < 0) { // printe done from IdmapBuffer::init return EXIT_FAILURE; } AssetManager am; - if (parse_idmap_header(buf, am) < 0) { + if (parse_idmap_header(buf, am) != NO_ERROR) { // printe done from parse_idmap_header return EXIT_FAILURE; } - Vector<uint32_t> types; - if (parse_data_header(buf, am, types) < 0) { + if (parse_data(buf, am) != NO_ERROR) { // printe done from parse_data_header return EXIT_FAILURE; } - const size_t N = types.size(); - for (size_t i = 0; i < N; ++i) { - if (parse_data_block(buf, am, types.itemAt(i)) < 0) { - // printe done from parse_data_block - return EXIT_FAILURE; - } - } return EXIT_SUCCESS; } diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index 47047b86a6ec..f85a7dc89876 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -58,11 +58,13 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.WeakHashMap; + import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import com.android.internal.content.PackageHelper; +import com.android.internal.util.ArrayUtils; public final class Pm { IPackageManager mPm; @@ -1548,6 +1550,12 @@ public final class Pm { if (info != null && info.applicationInfo != null) { System.out.print("package:"); System.out.println(info.applicationInfo.sourceDir); + if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) { + for (String splitSourceDir : info.applicationInfo.splitSourceDirs) { + System.out.print("package:"); + System.out.println(splitSourceDir); + } + } } } catch (RemoteException e) { System.err.println(e.toString()); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index f6883e2f5339..91328832794e 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -929,7 +929,8 @@ public class Activity extends ContextThemeWrapper /** * Same as {@link #onCreate(android.os.Bundle)} but called for those activities created with - * the attribute {@link android.R.attr#persistable} set true. + * the attribute {@link android.R.attr#persistableMode} set to + * <code>persistAcrossReboots</code>. * * @param savedInstanceState if the activity is being re-initialized after * previously being shut down then this Bundle contains the data it most @@ -1012,8 +1013,9 @@ public class Activity extends ContextThemeWrapper /** * This is the same as {@link #onRestoreInstanceState(Bundle)} but is called for activities - * created with the attribute {@link android.R.attr#persistable}. The {@link - * android.os.PersistableBundle} passed came from the restored PersistableBundle first + * created with the attribute {@link android.R.attr#persistableMode} set to + * <code>persistAcrossReboots</code>. The {@link android.os.PersistableBundle} passed + * came from the restored PersistableBundle first * saved in {@link #onSaveInstanceState(Bundle, PersistableBundle)}. * * <p>This method is called between {@link #onStart} and @@ -1111,7 +1113,8 @@ public class Activity extends ContextThemeWrapper /** * This is the same as {@link #onPostCreate(Bundle)} but is called for activities - * created with the attribute {@link android.R.attr#persistable}. + * created with the attribute {@link android.R.attr#persistableMode} set to + * <code>persistAcrossReboots</code>. * * @param savedInstanceState The data most recently supplied in {@link #onSaveInstanceState} * @param persistentState The data caming from the PersistableBundle first @@ -1352,10 +1355,10 @@ public class Activity extends ContextThemeWrapper /** * This is the same as {@link #onSaveInstanceState} but is called for activities - * created with the attribute {@link android.R.attr#persistable}. The {@link - * android.os.PersistableBundle} passed in will be saved and presented in - * {@link #onCreate(Bundle, PersistableBundle)} the first time that this activity - * is restarted following the next device reboot. + * created with the attribute {@link android.R.attr#persistableMode} set to + * <code>persistAcrossReboots</code>. The {@link android.os.PersistableBundle} passed + * in will be saved and presented in {@link #onCreate(Bundle, PersistableBundle)} + * the first time that this activity is restarted following the next device reboot. * * @param outState Bundle in which to place your saved state. * @param outPersistentState State which will be saved across reboots. @@ -5320,13 +5323,15 @@ public class Activity extends ContextThemeWrapper * drawn and it is safe to make this Activity translucent again. * @param options activity options delivered to the activity below this one. The options * are retrieved using {@link #getActivityOptions}. + * @return <code>true</code> if Window was opaque and will become translucent or + * <code>false</code> if window was translucent and no change needed to be made. * * @see #convertFromTranslucent() * @see TranslucentConversionListener * * @hide */ - public void convertToTranslucent(TranslucentConversionListener callback, + public boolean convertToTranslucent(TranslucentConversionListener callback, ActivityOptions options) { boolean drawComplete; try { @@ -5343,6 +5348,7 @@ public class Activity extends ContextThemeWrapper // Window is already translucent. mTranslucentCallback.onTranslucentConversionComplete(drawComplete); } + return mChangeCanvasToTranslucent; } /** @hide */ diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index ea4604466a6b..f5514f8c197c 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -191,11 +191,13 @@ public final class ActivityThread { /** Reference to singleton {@link ActivityThread} */ private static ActivityThread sCurrentActivityThread; Instrumentation mInstrumentation; + String mInstrumentationPackageName = null; String mInstrumentationAppDir = null; - String mInstrumentationAppLibraryDir = null; - String mInstrumentationAppPackage = null; + String[] mInstrumentationSplitAppDirs = null; + String mInstrumentationLibDir = null; String mInstrumentedAppDir = null; - String mInstrumentedAppLibraryDir = null; + String[] mInstrumentedSplitAppDirs = null; + String mInstrumentedLibDir = null; boolean mSystemThread = false; boolean mJitEnabled = false; @@ -317,7 +319,7 @@ public final class ActivityThread { } public boolean isPersistable() { - return (activityInfo.flags & ActivityInfo.FLAG_PERSISTABLE) != 0; + return activityInfo.persistableMode == ActivityInfo.PERSIST_ACROSS_REBOOTS; } public String toString() { @@ -1585,11 +1587,11 @@ public final class ActivityThread { /** * Creates the top level resources for the given package. */ - Resources getTopLevelResources(String resDir, String[] overlayDirs, String[] libDirs, - int displayId, Configuration overrideConfiguration, + Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs, + String[] libDirs, int displayId, Configuration overrideConfiguration, LoadedApk pkgInfo) { - return mResourcesManager.getTopLevelResources(resDir, overlayDirs, libDirs, displayId, - overrideConfiguration, pkgInfo.getCompatibilityInfo(), null); + return mResourcesManager.getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs, + displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo(), null); } final Handler getHandler() { @@ -4315,16 +4317,20 @@ public final class ActivityThread { + data.instrumentationName); } + mInstrumentationPackageName = ii.packageName; mInstrumentationAppDir = ii.sourceDir; - mInstrumentationAppLibraryDir = ii.nativeLibraryDir; - mInstrumentationAppPackage = ii.packageName; + mInstrumentationSplitAppDirs = ii.splitSourceDirs; + mInstrumentationLibDir = ii.nativeLibraryDir; mInstrumentedAppDir = data.info.getAppDir(); - mInstrumentedAppLibraryDir = data.info.getLibDir(); + mInstrumentedSplitAppDirs = data.info.getSplitAppDirs(); + mInstrumentedLibDir = data.info.getLibDir(); ApplicationInfo instrApp = new ApplicationInfo(); instrApp.packageName = ii.packageName; instrApp.sourceDir = ii.sourceDir; instrApp.publicSourceDir = ii.publicSourceDir; + instrApp.splitSourceDirs = ii.splitSourceDirs; + instrApp.splitPublicSourceDirs = ii.splitPublicSourceDirs; instrApp.dataDir = ii.dataDir; instrApp.nativeLibraryDir = ii.nativeLibraryDir; LoadedApk pi = getPackageInfo(instrApp, data.compatInfo, diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 84673d9e238e..de0396e478d7 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -823,8 +823,10 @@ final class ApplicationPackageManager extends PackageManager { if (app.packageName.equals("system")) { return mContext.mMainThread.getSystemContext().getResources(); } + final boolean sameUid = (app.uid == Process.myUid()); Resources r = mContext.mMainThread.getTopLevelResources( - app.uid == Process.myUid() ? app.sourceDir : app.publicSourceDir, + sameUid ? app.sourceDir : app.publicSourceDir, + sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs, app.resourceDirs, null, Display.DEFAULT_DISPLAY, null, mContext.mPackageInfo); if (r != null) { return r; diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index a42bd3b92730..425a14018a3a 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -513,6 +513,9 @@ class ContextImpl extends Context { public Object createService(ContextImpl ctx) { IBinder b = ServiceManager.getService(POWER_SERVICE); IPowerManager service = IPowerManager.Stub.asInterface(b); + if (service == null) { + Log.wtf(TAG, "Failed to get power manager service."); + } return new PowerManager(ctx.getOuterContext(), service, ctx.mMainThread.getHandler()); }}); @@ -694,8 +697,8 @@ class ContextImpl extends Context { registerService(FINGERPRINT_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { - IBinder b = ServiceManager.getService(FINGERPRINT_SERVICE); - IFingerprintService service = IFingerprintService.Stub.asInterface(b); + IBinder binder = ServiceManager.getService(FINGERPRINT_SERVICE); + IFingerprintService service = IFingerprintService.Stub.asInterface(binder); return new FingerprintManager(ctx.getOuterContext(), service); } }); @@ -2190,10 +2193,10 @@ class ContextImpl extends Context { || overrideConfiguration != null || (compatInfo != null && compatInfo.applicationScale != resources.getCompatibilityInfo().applicationScale)) { - resources = mResourcesManager.getTopLevelResources( - packageInfo.getResDir(), packageInfo.getOverlayDirs(), - packageInfo.getApplicationInfo().sharedLibraryFiles, - displayId, overrideConfiguration, compatInfo, activityToken); + resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(), + packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(), + packageInfo.getApplicationInfo().sharedLibraryFiles, displayId, + overrideConfiguration, compatInfo, activityToken); } } mResources = resources; diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index 365cc8e8e0cd..1d7a0ec8c98b 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -55,6 +55,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { private boolean mIsExitTransitionComplete; private boolean mIsReadyForTransition; private Bundle mSharedElementsBundle; + private boolean mWasOpaque; public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver, ArrayList<String> sharedElementNames, boolean isReturning) { @@ -191,7 +192,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { protected void prepareEnter() { mActivity.overridePendingTransition(0, 0); if (!mIsReturning) { - mActivity.convertToTranslucent(null, null); + mWasOpaque = mActivity.convertToTranslucent(null, null); Drawable background = getDecor().getBackground(); if (background != null) { getWindow().setBackgroundDrawable(null); @@ -376,7 +377,9 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { private void makeOpaque() { if (!mHasStopped && mActivity != null) { - mActivity.convertFromTranslucent(); + if (mWasOpaque) { + mActivity.convertFromTranslucent(); + } mActivity = null; } } diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 3ae8bfcb4f3f..065e88db320d 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -16,8 +16,8 @@ package android.app; +import android.text.TextUtils; import android.util.ArrayMap; -import com.android.internal.util.ArrayUtils; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -52,6 +52,8 @@ import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; final class IntentReceiverLeaked extends AndroidRuntimeException { @@ -79,6 +81,8 @@ public final class LoadedApk { final String mPackageName; private final String mAppDir; private final String mResDir; + private final String[] mSplitAppDirs; + private final String[] mSplitResDirs; private final String[] mOverlayDirs; private final String[] mSharedLibraries; private final String mDataDir; @@ -116,13 +120,14 @@ public final class LoadedApk { public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo, CompatibilityInfo compatInfo, ClassLoader baseLoader, boolean securityViolation, boolean includeCode) { + final int myUid = Process.myUid(); mActivityThread = activityThread; mApplicationInfo = aInfo; mPackageName = aInfo.packageName; mAppDir = aInfo.sourceDir; - final int myUid = Process.myUid(); - mResDir = aInfo.uid == myUid ? aInfo.sourceDir - : aInfo.publicSourceDir; + mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir; + mSplitAppDirs = aInfo.splitSourceDirs; + mSplitResDirs = aInfo.uid == myUid ? aInfo.splitSourceDirs : aInfo.splitPublicSourceDirs; mOverlayDirs = aInfo.resourceDirs; if (!UserHandle.isSameUser(aInfo.uid, myUid) && !Process.isIsolated()) { aInfo.dataDir = PackageManager.getDataDirForUser(UserHandle.getUserId(myUid), @@ -149,6 +154,8 @@ public final class LoadedApk { mPackageName = "android"; mAppDir = null; mResDir = null; + mSplitAppDirs = null; + mSplitResDirs = null; mOverlayDirs = null; mSharedLibraries = null; mDataDir = null; @@ -214,53 +221,6 @@ public final class LoadedApk { return ai.sharedLibraryFiles; } - /** - * Combines two arrays (of library names) such that they are - * concatenated in order but are devoid of duplicates. The - * result is a single string with the names of the libraries - * separated by colons, or <code>null</code> if both lists - * were <code>null</code> or empty. - * - * @param list1 null-ok; the first list - * @param list2 null-ok; the second list - * @return null-ok; the combination - */ - private static String combineLibs(String[] list1, String[] list2) { - StringBuilder result = new StringBuilder(300); - boolean first = true; - - if (list1 != null) { - for (String s : list1) { - if (first) { - first = false; - } else { - result.append(':'); - } - result.append(s); - } - } - - // Only need to check for duplicates if list1 was non-empty. - boolean dupCheck = !first; - - if (list2 != null) { - for (String s : list2) { - if (dupCheck && ArrayUtils.contains(list1, s)) { - continue; - } - - if (first) { - first = false; - } else { - result.append(':'); - } - result.append(s); - } - } - - return result.toString(); - } - public ClassLoader getClassLoader() { synchronized (this) { if (mClassLoader != null) { @@ -268,8 +228,15 @@ public final class LoadedApk { } if (mIncludeCode && !mPackageName.equals("android")) { - String zip = mAppDir; - String libraryPath = mLibDir; + final ArrayList<String> zipPaths = new ArrayList<>(); + final ArrayList<String> libPaths = new ArrayList<>(); + + zipPaths.add(mAppDir); + if (mSplitAppDirs != null) { + Collections.addAll(zipPaths, mSplitAppDirs); + } + + libPaths.add(mLibDir); /* * The following is a bit of a hack to inject @@ -280,50 +247,70 @@ public final class LoadedApk { * concatenation of both apps' shared library lists. */ - String instrumentationAppDir = - mActivityThread.mInstrumentationAppDir; - String instrumentationAppLibraryDir = - mActivityThread.mInstrumentationAppLibraryDir; - String instrumentationAppPackage = - mActivityThread.mInstrumentationAppPackage; - String instrumentedAppDir = - mActivityThread.mInstrumentedAppDir; - String instrumentedAppLibraryDir = - mActivityThread.mInstrumentedAppLibraryDir; + String instrumentationPackageName = mActivityThread.mInstrumentationPackageName; + String instrumentationAppDir = mActivityThread.mInstrumentationAppDir; + String[] instrumentationSplitAppDirs = mActivityThread.mInstrumentationSplitAppDirs; + String instrumentationLibDir = mActivityThread.mInstrumentationLibDir; + + String instrumentedAppDir = mActivityThread.mInstrumentedAppDir; + String[] instrumentedSplitAppDirs = mActivityThread.mInstrumentedSplitAppDirs; + String instrumentedLibDir = mActivityThread.mInstrumentedLibDir; String[] instrumentationLibs = null; if (mAppDir.equals(instrumentationAppDir) || mAppDir.equals(instrumentedAppDir)) { - zip = instrumentationAppDir + ":" + instrumentedAppDir; - libraryPath = instrumentationAppLibraryDir + ":" + instrumentedAppLibraryDir; - if (! instrumentedAppDir.equals(instrumentationAppDir)) { - instrumentationLibs = - getLibrariesFor(instrumentationAppPackage); + zipPaths.clear(); + zipPaths.add(instrumentationAppDir); + if (instrumentationSplitAppDirs != null) { + Collections.addAll(zipPaths, instrumentationSplitAppDirs); + } + zipPaths.add(instrumentedAppDir); + if (instrumentedSplitAppDirs != null) { + Collections.addAll(zipPaths, instrumentedSplitAppDirs); + } + + libPaths.clear(); + libPaths.add(instrumentationLibDir); + libPaths.add(instrumentedLibDir); + + if (!instrumentedAppDir.equals(instrumentationAppDir)) { + instrumentationLibs = getLibrariesFor(instrumentationPackageName); } } - if ((mSharedLibraries != null) || - (instrumentationLibs != null)) { - zip = - combineLibs(mSharedLibraries, instrumentationLibs) - + ':' + zip; + if (mSharedLibraries != null) { + for (String lib : mSharedLibraries) { + if (!zipPaths.contains(lib)) { + zipPaths.add(0, lib); + } + } } + if (instrumentationLibs != null) { + for (String lib : instrumentationLibs) { + if (!zipPaths.contains(lib)) { + zipPaths.add(0, lib); + } + } + } + + final String zip = TextUtils.join(File.pathSeparator, zipPaths); + final String lib = TextUtils.join(File.pathSeparator, libPaths); + /* * With all the combination done (if necessary, actually * create the class loader. */ if (ActivityThread.localLOGV) - Slog.v(ActivityThread.TAG, "Class path: " + zip + ", JNI path: " + libraryPath); + Slog.v(ActivityThread.TAG, "Class path: " + zip + ", JNI path: " + lib); // Temporarily disable logging of disk reads on the Looper thread // as this is early and necessary. StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); - mClassLoader = - ApplicationLoaders.getDefault().getClassLoader( - zip, libraryPath, mBaseClassLoader); + mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, lib, + mBaseClassLoader); initializeJavaContextClassLoader(); StrictMode.setThreadPolicy(oldPolicy); @@ -469,6 +456,14 @@ public final class LoadedApk { return mResDir; } + public String[] getSplitAppDirs() { + return mSplitAppDirs; + } + + public String[] getSplitResDirs() { + return mSplitResDirs; + } + public String[] getOverlayDirs() { return mOverlayDirs; } @@ -487,7 +482,7 @@ public final class LoadedApk { public Resources getResources(ActivityThread mainThread) { if (mResources == null) { - mResources = mainThread.getTopLevelResources(mResDir, mOverlayDirs, + mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs, mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this); } return mResources; diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index a67faa09be86..3c1311537848 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -149,9 +149,9 @@ public class ResourcesManager { * @param compatInfo the compability info. Must not be null. * @param token the application token for determining stack bounds. */ - public Resources getTopLevelResources(String resDir, String[] overlayDirs, String[] libDirs, - int displayId, Configuration overrideConfiguration, CompatibilityInfo compatInfo, - IBinder token) { + public Resources getTopLevelResources(String resDir, String[] splitResDirs, + String[] overlayDirs, String[] libDirs, int displayId, + Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) { final float scale = compatInfo.applicationScale; ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale, token); Resources r; @@ -182,6 +182,14 @@ public class ResourcesManager { return null; } + if (splitResDirs != null) { + for (String splitResDir : splitResDirs) { + if (assets.addAssetPath(splitResDir) == 0) { + return null; + } + } + } + if (overlayDirs != null) { for (String idmapPath : overlayDirs) { assets.addOverlayPath(idmapPath); diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index 45a262505785..ca4043677a70 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -166,6 +166,40 @@ public class DeviceAdminReceiver extends BroadcastReceiver { = "android.app.action.ACTION_PASSWORD_EXPIRING"; /** + * Action sent to a device administrator to notify that the device is entering + * or exiting lock task mode from an authorized package. The extra + * {@link #EXTRA_LOCK_TASK_ENTERING} will describe whether entering or exiting + * the mode. If entering, the extra {@link #EXTRA_LOCK_TASK_PACKAGE} will describe + * the authorized package using lock task mode. + * + * @see DevicePolicyManager#isLockTaskPermitted + * + * <p>The calling device admin must be the device owner or profile + * owner to receive this broadcast. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_LOCK_TASK_CHANGED + = "android.app.action.ACTION_LOCK_TASK_CHANGED"; + + /** + * A boolean describing whether the device is currently entering or exiting + * lock task mode. + * + * @see #ACTION_LOCK_TASK_CHANGED + */ + public static final String EXTRA_LOCK_TASK_ENTERING = + "android.app.extra.LOCK_TASK_ENTERING"; + + /** + * A boolean describing whether the device is currently entering or exiting + * lock task mode. + * + * @see #ACTION_LOCK_TASK_CHANGED + */ + public static final String EXTRA_LOCK_TASK_PACKAGE = + "android.app.extra.LOCK_TASK_PACKAGE"; + + /** * Broadcast Action: This broadcast is sent to indicate that provisioning of a managed profile * or managed device has completed successfully. * @@ -341,6 +375,19 @@ public class DeviceAdminReceiver extends BroadcastReceiver { } /** + * Called when a device is entering or exiting lock task mode by a package + * authorized by {@link DevicePolicyManager#isLockTaskPermitted(String)} + * + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @param isEnteringLockTask Whether the device is entering or exiting lock task mode. + * @param pkg If entering, the authorized package using lock task mode, otherwise null. + */ + public void onLockTaskModeChanged(Context context, Intent intent, boolean isEnteringLockTask, + String pkg) { + } + + /** * Intercept standard device administrator broadcasts. Implementations * should not override this method; it is better to implement the * convenience callbacks for each action. @@ -369,6 +416,10 @@ public class DeviceAdminReceiver extends BroadcastReceiver { onPasswordExpiring(context, intent); } else if (ACTION_PROFILE_PROVISIONING_COMPLETE.equals(action)) { onProfileProvisioningComplete(context, intent); + } else if (ACTION_LOCK_TASK_CHANGED.equals(action)) { + boolean isEntering = intent.getBooleanExtra(EXTRA_LOCK_TASK_ENTERING, false); + String pkg = intent.getStringExtra(EXTRA_LOCK_TASK_PACKAGE); + onLockTaskModeChanged(context, intent, isEntering, pkg); } } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index e80c7616204f..af653a380025 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -18,6 +18,7 @@ package android.app.admin; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -1839,11 +1840,13 @@ public class DevicePolicyManager { * This function should be used cautiously as once it is called it cannot * be undone. The device owner can only be set as a part of device setup * before setup completes. + * + * @param packageName The package name of the device owner. */ - public void clearDeviceOwnerApp() { + public void clearDeviceOwnerApp(String packageName) { if (mService != null) { try { - mService.clearDeviceOwner(mContext.getPackageName()); + mService.clearDeviceOwner(packageName); } catch (RemoteException re) { Log.w(TAG, "Failed to clear device owner"); } @@ -2340,15 +2343,20 @@ public class DevicePolicyManager { } /** - * Sets which components may enter lock task mode. + * Sets which packages may enter lock task mode. + * + * <p>Any packages that shares uid with an allowed package will also be allowed + * to activate lock task. * * This function can only be called by the device owner or the profile owner. - * @param components The list of components allowed to enter lock task mode + * @param packages The list of packages allowed to enter lock task mode + * + * @see Activity#startLockTask() */ - public void setLockTaskComponents(ComponentName[] components) throws SecurityException { + public void setLockTaskPackages(String[] packages) throws SecurityException { if (mService != null) { try { - mService.setLockTaskComponents(components); + mService.setLockTaskPackages(packages); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -2356,13 +2364,13 @@ public class DevicePolicyManager { } /** - * This function returns the list of components allowed to start the lock task mode. + * This function returns the list of packages allowed to start the lock task mode. * @hide */ - public ComponentName[] getLockTaskComponents() { + public String[] getLockTaskPackages() { if (mService != null) { try { - return mService.getLockTaskComponents(); + return mService.getLockTaskPackages(); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -2373,12 +2381,12 @@ public class DevicePolicyManager { /** * This function lets the caller know whether the given component is allowed to start the * lock task mode. - * @param component The component to check + * @param pkg The package to check */ - public boolean isLockTaskPermitted(ComponentName component) { + public boolean isLockTaskPermitted(String pkg) { if (mService != null) { try { - return mService.isLockTaskPermitted(component); + return mService.isLockTaskPermitted(pkg); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index a1caa2199b1d..e7b77d82da30 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -142,13 +142,15 @@ interface IDevicePolicyManager { void setAccountManagementDisabled(in ComponentName who, in String accountType, in boolean disabled); String[] getAccountTypesWithManagementDisabled(); - void setLockTaskComponents(in ComponentName[] components); - ComponentName[] getLockTaskComponents(); - boolean isLockTaskPermitted(in ComponentName component); + void setLockTaskPackages(in String[] packages); + String[] getLockTaskPackages(); + boolean isLockTaskPermitted(in String pkg); void setGlobalSetting(in ComponentName who, in String setting, in String value); void setSecureSetting(in ComponentName who, in String setting, in String value); void setMasterVolumeMuted(in ComponentName admin, boolean on); boolean isMasterVolumeMuted(in ComponentName admin); + + void notifyLockTaskModeChanged(boolean isEnabled, String pkg, int userId); } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 6e53a6fb5969..3dfa78b4804c 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -7375,6 +7375,7 @@ public class Intent implements Parcelable, Cloneable { for (int categoryNdx = mCategories.size() - 1; categoryNdx >= 0; --categoryNdx) { out.attribute(null, ATTR_CATEGORY, mCategories.valueAt(categoryNdx)); } + out.endTag(null, TAG_CATEGORIES); } } diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 791e5aa46c7f..abc8cde2c9eb 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -104,6 +104,28 @@ public class ActivityInfo extends ComponentInfo public int documentLaunchMode; /** + * Constant corresponding to <code>persistRootOnly</code> in + * the {@link android.R.attr#persistableMode} attribute. + */ + public static final int PERSIST_ROOT_ONLY = 0; + /** + * Constant corresponding to <code>doNotPersist</code> in + * the {@link android.R.attr#persistableMode} attribute. + */ + public static final int DO_NOT_PERSIST = 1; + /** + * Constant corresponding to <code>persistAcrossReboots</code> in + * the {@link android.R.attr#persistableMode} attribute. + */ + public static final int PERSIST_ACROSS_REBOOTS = 2; + /** + * Value indicating how this activity is to be persisted across + * reboots for restoring in the Recents list. + * {@link android.R.attr#persistableMode} + */ + public int persistableMode; + + /** * The maximum number of tasks rooted at this activity that can be in the recent task list. * Refer to {@link android.R.attr#maxRecents}. */ @@ -230,12 +252,6 @@ public class ActivityInfo extends ComponentInfo */ public static final int FLAG_IMMERSIVE = 0x0800; /** - * Bit in {@link #flags} indicating that this activity is to be persisted across - * reboots for display in the Recents list. - * {@link android.R.attr#persistable} - */ - public static final int FLAG_PERSISTABLE = 0x1000; - /** * Bit in {@link #flags} indicating that tasks started with this activity are to be * removed from the recent list of tasks when the last activity in the task is finished. * {@link android.R.attr#autoRemoveFromRecents} @@ -641,13 +657,23 @@ public class ActivityInfo extends ComponentInfo return theme != 0 ? theme : applicationInfo.theme; } + private String persistableModeToString() { + switch(persistableMode) { + case PERSIST_ROOT_ONLY: return "PERSIST_ROOT_ONLY"; + case DO_NOT_PERSIST: return "DO_NOT_PERSIST"; + case PERSIST_ACROSS_REBOOTS: return "PERSIST_ACROSS_REBOOTS"; + default: return "UNKNOWN=" + persistableMode; + } + } + public void dump(Printer pw, String prefix) { super.dumpFront(pw, prefix); if (permission != null) { pw.println(prefix + "permission=" + permission); } pw.println(prefix + "taskAffinity=" + taskAffinity - + " targetActivity=" + targetActivity); + + " targetActivity=" + targetActivity + + " persistableMode=" + persistableModeToString()); if (launchMode != 0 || flags != 0 || theme != 0) { pw.println(prefix + "launchMode=" + launchMode + " flags=0x" + Integer.toHexString(flags) @@ -688,6 +714,7 @@ public class ActivityInfo extends ComponentInfo dest.writeInt(softInputMode); dest.writeInt(uiOptions); dest.writeString(parentActivityName); + dest.writeInt(persistableMode); } public static final Parcelable.Creator<ActivityInfo> CREATOR @@ -713,5 +740,6 @@ public class ActivityInfo extends ComponentInfo softInputMode = source.readInt(); uiOptions = source.readInt(); parentActivityName = source.readString(); + persistableMode = source.readInt(); } } diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 6b44a115cd42..06f4019df06d 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -23,8 +23,12 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.Printer; +import com.android.internal.util.ArrayUtils; + import java.text.Collator; +import java.util.Arrays; import java.util.Comparator; +import java.util.Objects; /** * Information you can retrieve about a particular application. This @@ -398,17 +402,30 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public int largestWidthLimitDp = 0; /** - * Full path to the location of this package. + * Full path to the base APK for this application. */ public String sourceDir; /** - * Full path to the location of the publicly available parts of this - * package (i.e. the primary resource package and manifest). For - * non-forward-locked apps this will be the same as {@link #sourceDir). + * Full path to the publicly available parts of {@link #sourceDir}, + * including resources and manifest. This may be different from + * {@link #sourceDir} if an application is forward locked. */ public String publicSourceDir; - + + /** + * Full paths to zero or more split APKs that, when combined with the base + * APK defined in {@link #sourceDir}, form a complete application. + */ + public String[] splitSourceDirs; + + /** + * Full path to the publicly available parts of {@link #splitSourceDirs}, + * including resources and manifest. This may be different from + * {@link #splitSourceDirs} if an application is forward locked. + */ + public String[] splitPublicSourceDirs; + /** * Full paths to the locations of extra resource packages this application * uses. This field is only used if there are extra resource packages, @@ -512,13 +529,16 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { + " compatibleWidthLimitDp=" + compatibleWidthLimitDp + " largestWidthLimitDp=" + largestWidthLimitDp); pw.println(prefix + "sourceDir=" + sourceDir); - if (sourceDir == null) { - if (publicSourceDir != null) { - pw.println(prefix + "publicSourceDir=" + publicSourceDir); - } - } else if (!sourceDir.equals(publicSourceDir)) { + if (!Objects.equals(sourceDir, publicSourceDir)) { pw.println(prefix + "publicSourceDir=" + publicSourceDir); } + if (!ArrayUtils.isEmpty(splitSourceDirs)) { + pw.println(prefix + "splitSourceDirs=" + Arrays.toString(splitSourceDirs)); + } + if (!ArrayUtils.isEmpty(splitPublicSourceDirs) + && !Arrays.equals(splitSourceDirs, splitPublicSourceDirs)) { + pw.println(prefix + "splitPublicSourceDirs=" + Arrays.toString(splitPublicSourceDirs)); + } if (resourceDirs != null) { pw.println(prefix + "resourceDirs=" + resourceDirs); } @@ -591,6 +611,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { largestWidthLimitDp = orig.largestWidthLimitDp; sourceDir = orig.sourceDir; publicSourceDir = orig.publicSourceDir; + splitSourceDirs = orig.splitSourceDirs; + splitPublicSourceDirs = orig.splitPublicSourceDirs; nativeLibraryDir = orig.nativeLibraryDir; cpuAbi = orig.cpuAbi; resourceDirs = orig.resourceDirs; @@ -633,6 +655,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeInt(largestWidthLimitDp); dest.writeString(sourceDir); dest.writeString(publicSourceDir); + dest.writeStringArray(splitSourceDirs); + dest.writeStringArray(splitPublicSourceDirs); dest.writeString(nativeLibraryDir); dest.writeString(cpuAbi); dest.writeStringArray(resourceDirs); @@ -674,6 +698,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { largestWidthLimitDp = source.readInt(); sourceDir = source.readString(); publicSourceDir = source.readString(); + splitSourceDirs = source.readStringArray(); + splitPublicSourceDirs = source.readStringArray(); nativeLibraryDir = source.readString(); cpuAbi = source.readString(); resourceDirs = source.readStringArray(); diff --git a/core/java/android/content/pm/InstrumentationInfo.java b/core/java/android/content/pm/InstrumentationInfo.java index a977e41c3d69..dab0caffd57a 100644 --- a/core/java/android/content/pm/InstrumentationInfo.java +++ b/core/java/android/content/pm/InstrumentationInfo.java @@ -30,17 +30,32 @@ public class InstrumentationInfo extends PackageItemInfo implements Parcelable { * "package" attribute. */ public String targetPackage; - + /** - * Full path to the location of this package. + * Full path to the base APK for this application. */ public String sourceDir; - + /** - * Full path to the location of the publicly available parts of this package (i.e. the resources - * and manifest). For non-forward-locked apps this will be the same as {@link #sourceDir). + * Full path to the publicly available parts of {@link #sourceDir}, + * including resources and manifest. This may be different from + * {@link #sourceDir} if an application is forward locked. */ public String publicSourceDir; + + /** + * Full paths to zero or more split APKs that, when combined with the base + * APK defined in {@link #sourceDir}, form a complete application. + */ + public String[] splitSourceDirs; + + /** + * Full path to the publicly available parts of {@link #splitSourceDirs}, + * including resources and manifest. This may be different from + * {@link #splitSourceDirs} if an application is forward locked. + */ + public String[] splitPublicSourceDirs; + /** * Full path to a directory assigned to the package for its persistent * data. diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 69fa408c5871..6c10bb8fce7a 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -57,6 +57,65 @@ public class LauncherApps { private List<OnAppsChangedListener> mListeners = new ArrayList<OnAppsChangedListener>(); + private List<OnAppsChangedCallback> mCallbacks + = new ArrayList<OnAppsChangedCallback>(); + + /** + * Callbacks for package changes to this and related managed profiles. + */ + public static abstract class OnAppsChangedCallback { + /** + * Indicates that a package was removed from the specified profile. + * + * @param packageName The name of the package that was removed. + * @param user The UserHandle of the profile that generated the change. + */ + abstract public void onPackageRemoved(String packageName, UserHandle user); + + /** + * Indicates that a package was added to the specified profile. + * + * @param packageName The name of the package that was added. + * @param user The UserHandle of the profile that generated the change. + */ + abstract public void onPackageAdded(String packageName, UserHandle user); + + /** + * Indicates that a package was modified in the specified profile. + * + * @param packageName The name of the package that has changed. + * @param user The UserHandle of the profile that generated the change. + */ + abstract public void onPackageChanged(String packageName, UserHandle user); + + /** + * Indicates that one or more packages have become available. For + * example, this can happen when a removable storage card has + * reappeared. + * + * @param packageNames The names of the packages that have become + * available. + * @param user The UserHandle of the profile that generated the change. + * @param replacing Indicates whether these packages are replacing + * existing ones. + */ + abstract public void onPackagesAvailable(String[] packageNames, UserHandle user, + boolean replacing); + + /** + * Indicates that one or more packages have become unavailable. For + * example, this can happen when a removable storage card has been + * removed. + * + * @param packageNames The names of the packages that have become + * unavailable. + * @param user The UserHandle of the profile that generated the change. + * @param replacing Indicates whether the packages are about to be + * replaced with new versions. + */ + abstract public void onPackagesUnavailable(String[] packageNames, UserHandle user, + boolean replacing); + } /** * Callbacks for package changes to this and related managed profiles. @@ -270,7 +329,7 @@ public class LauncherApps { synchronized (this) { if (listener != null && !mListeners.contains(listener)) { mListeners.add(listener); - if (mListeners.size() == 1) { + if (mListeners.size() == 1 && mCallbacks.size() == 0) { try { mService.addOnAppsChangedListener(mAppsChangedListener); } catch (RemoteException re) { @@ -289,7 +348,44 @@ public class LauncherApps { public void removeOnAppsChangedListener(OnAppsChangedListener listener) { synchronized (this) { mListeners.remove(listener); - if (mListeners.size() == 0) { + if (mListeners.size() == 0 && mCallbacks.size() == 0) { + try { + mService.removeOnAppsChangedListener(mAppsChangedListener); + } catch (RemoteException re) { + } + } + } + } + + /** + * Adds a callback for changes to packages in current and managed profiles. + * + * @param callback The callback to add. + */ + public void addOnAppsChangedCallback(OnAppsChangedCallback callback) { + synchronized (this) { + if (callback != null && !mCallbacks.contains(callback)) { + mCallbacks.add(callback); + if (mCallbacks.size() == 1 && mListeners.size() == 0) { + try { + mService.addOnAppsChangedListener(mAppsChangedListener); + } catch (RemoteException re) { + } + } + } + } + } + + /** + * Removes a callback that was previously added. + * + * @param callback The callback to remove. + * @see #addOnAppsChangedListener(OnAppsChangedCallback) + */ + public void removeOnAppsChangedCallback(OnAppsChangedCallback callback) { + synchronized (this) { + mListeners.remove(callback); + if (mListeners.size() == 0 && mCallbacks.size() == 0) { try { mService.removeOnAppsChangedListener(mAppsChangedListener); } catch (RemoteException re) { @@ -309,6 +405,9 @@ public class LauncherApps { for (OnAppsChangedListener listener : mListeners) { listener.onPackageRemoved(user, packageName); } + for (OnAppsChangedCallback callback : mCallbacks) { + callback.onPackageRemoved(packageName, user); + } } } @@ -321,6 +420,9 @@ public class LauncherApps { for (OnAppsChangedListener listener : mListeners) { listener.onPackageChanged(user, packageName); } + for (OnAppsChangedCallback callback : mCallbacks) { + callback.onPackageChanged(packageName, user); + } } } @@ -333,6 +435,9 @@ public class LauncherApps { for (OnAppsChangedListener listener : mListeners) { listener.onPackageAdded(user, packageName); } + for (OnAppsChangedCallback callback : mCallbacks) { + callback.onPackageAdded(packageName, user); + } } } @@ -346,6 +451,9 @@ public class LauncherApps { for (OnAppsChangedListener listener : mListeners) { listener.onPackagesAvailable(user, packageNames, replacing); } + for (OnAppsChangedCallback callback : mCallbacks) { + callback.onPackagesAvailable(packageNames, user, replacing); + } } } @@ -359,7 +467,10 @@ public class LauncherApps { for (OnAppsChangedListener listener : mListeners) { listener.onPackagesUnavailable(user, packageNames, replacing); } - } + for (OnAppsChangedCallback callback : mCallbacks) { + callback.onPackagesUnavailable(packageNames, user, replacing); + } + } } }; } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 8d9b8d9e0ade..5d55b0a11bd4 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2870,15 +2870,12 @@ public abstract class PackageManager { * */ public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags) { - PackageParser packageParser = new PackageParser(archiveFilePath); - DisplayMetrics metrics = new DisplayMetrics(); - metrics.setToDefaults(); - final File sourceFile = new File(archiveFilePath); + final PackageParser parser = new PackageParser(); + final File apkFile = new File(archiveFilePath); try { - PackageParser.Package pkg = packageParser.parseMonolithicPackage(sourceFile, metrics, - 0); + PackageParser.Package pkg = parser.parseMonolithicPackage(apkFile, 0); if ((flags & GET_SIGNATURES) != 0) { - packageParser.collectCertificates(pkg, 0); + parser.collectCertificates(pkg, 0); } PackageUserState state = new PackageUserState(); return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 91895ff5d68e..c8acd66f3b2d 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -16,9 +16,14 @@ package android.content.pm; +import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME; +import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING; +import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; +import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES; +import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION; import android.content.ComponentName; import android.content.Intent; @@ -33,6 +38,8 @@ import android.os.Bundle; import android.os.PatternMatcher; import android.os.UserHandle; import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.AttributeSet; import android.util.Base64; import android.util.DisplayMetrics; @@ -41,12 +48,18 @@ import android.util.Pair; import android.util.Slog; import android.util.TypedValue; -import java.io.BufferedInputStream; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.XmlUtils; + +import libcore.io.IoUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; -import java.lang.ref.WeakReference; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; @@ -58,21 +71,19 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; 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 com.android.internal.util.ArrayUtils; -import com.android.internal.util.XmlUtils; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - /** * Package archive parsing * @@ -153,17 +164,21 @@ public class PackageParser { android.os.Build.VERSION_CODES.JELLY_BEAN) }; + /** + * @deprecated callers should move to explicitly passing around source path. + */ + @Deprecated private String mArchiveSourcePath; + private String[] mSeparateProcesses; private boolean mOnlyCoreApps; + private DisplayMetrics mMetrics; + private static final int SDK_VERSION = Build.VERSION.SDK_INT; private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES; private int mParseError = PackageManager.INSTALL_SUCCEEDED; - private static final Object mSync = new Object(); - private static WeakReference<byte[]> mReadBuffer; - private static boolean sCompatibilityModeEnabled = true; private static final int PARSE_DEFAULT_INSTALL_LOCATION = PackageInfo.INSTALL_LOCATION_UNSPECIFIED; @@ -247,12 +262,9 @@ public class PackageParser { private static final String TAG = "PackageParser"; - public PackageParser(String archiveSourcePath) { - mArchiveSourcePath = archiveSourcePath; - } - - public PackageParser(File archiveSource) { - this(archiveSource.getAbsolutePath()); + public PackageParser() { + mMetrics = new DisplayMetrics(); + mMetrics.setToDefaults(); } public void setSeparateProcesses(String[] procs) { @@ -263,6 +275,10 @@ public class PackageParser { mOnlyCoreApps = onlyCoreApps; } + public void setDisplayMetrics(DisplayMetrics metrics) { + mMetrics = metrics; + } + private static final boolean isPackageFilename(File file) { return isPackageFilename(file.getName()); } @@ -480,23 +496,21 @@ public class PackageParser { return pi; } - private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry je, - byte[] readBuffer) { + private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry entry) + throws PackageParserException { + InputStream is = null; try { // We must read the stream for the JarEntry to retrieve // its certificates. - InputStream is = new BufferedInputStream(jarFile.getInputStream(je)); - while (is.read(readBuffer, 0, readBuffer.length) != -1) { - // not using - } - is.close(); - return je != null ? jarFile.getCertificateChains(je) : null; - } catch (IOException e) { - Slog.w(TAG, "Exception reading " + je.getName() + " in " + jarFile, e); - } catch (RuntimeException e) { - Slog.w(TAG, "Exception reading " + je.getName() + " in " + jarFile, e); + is = jarFile.getInputStream(entry); + readFullyIgnoringContents(is); + return jarFile.getCertificateChains(entry); + } catch (IOException | RuntimeException e) { + throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, + "Failed reading " + entry.getName() + " in " + jarFile, e); + } finally { + IoUtils.closeQuietly(is); } - return null; } public final static int PARSE_IS_SYSTEM = 1<<0; @@ -508,67 +522,116 @@ public class PackageParser { public final static int PARSE_IS_SYSTEM_DIR = 1<<6; public final static int PARSE_IS_PRIVILEGED = 1<<7; public final static int PARSE_GET_SIGNATURES = 1<<8; + public final static int PARSE_TRUSTED_OVERLAY = 1<<9; + + private static final Comparator<String> sSplitNameComparator = new SplitNameComparator(); /** - * Parse all APK files under the given directory as a single package. + * Used to sort a set of APKs based on their split names, always placing the + * base APK (with {@code null} split name) first. */ - public Package parseSplitPackage(File apkDir, DisplayMetrics metrics, int flags, - boolean trustedOverlay) throws PackageParserException { + private static class SplitNameComparator implements Comparator<String> { + @Override + public int compare(String lhs, String rhs) { + if (lhs == null) { + return -1; + } else if (rhs == null) { + return 1; + } else { + return lhs.compareTo(rhs); + } + } + } + + /** + * Parse all APKs contained in the given directory, treating them as a + * single package. This also performs sanity checking, such as requiring + * identical package name and version codes, a single base APK, and unique + * split names. + * <p> + * Note that this <em>does not</em> perform signature verification; that + * must be done separately in {@link #collectCertificates(Package, int)}. + */ + public Package parseSplitPackage(File apkDir, int flags) throws PackageParserException { final File[] files = apkDir.listFiles(); if (ArrayUtils.isEmpty(files)) { throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, "No packages found in split"); } - File baseFile = null; + String packageName = null; + int versionCode = 0; + + final ArrayMap<String, File> apks = new ArrayMap<>(); for (File file : files) { if (file.isFile() && isPackageFilename(file)) { - ApkLite lite = parseApkLite(file.getAbsolutePath(), 0); - if (lite == null) { - throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, - "Invalid package file: " + file); + final ApkLite lite = parseApkLite(file, 0); + + // Assert that all package names and version codes are + // consistent with the first one we encounter. + if (packageName == null) { + packageName = lite.packageName; + versionCode = lite.versionCode; + } else { + if (!packageName.equals(lite.packageName)) { + throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, + "Inconsistent package " + lite.packageName + " in " + file + + "; expected " + packageName); + } + if (versionCode != lite.versionCode) { + throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, + "Inconsistent version " + lite.versionCode + " in " + file + + "; expected " + versionCode); + } } - if (TextUtils.isEmpty(lite.splitName)) { - baseFile = file; - break; + // Assert that each split is defined only once + if (apks.put(lite.splitName, file) != null) { + throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, + "Split name " + lite.splitName + + " defined more than once; most recent was " + file); } } } + final File baseFile = apks.remove(null); if (baseFile == null) { - throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, + throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, "Missing base APK in " + apkDir); } - final Package pkg = parseBaseApk(baseFile, metrics, flags, trustedOverlay); + // Always apply deterministic ordering based on splitName + final int size = apks.size(); + + final String[] splitNames = apks.keySet().toArray(new String[size]); + Arrays.sort(splitNames, sSplitNameComparator); + + final File[] splitFiles = new File[size]; + for (int i = 0; i < size; i++) { + splitFiles[i] = apks.get(splitNames[i]); + } + + final Package pkg = parseBaseApk(baseFile, flags); if (pkg == null) { throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, "Failed to parse base APK: " + baseFile); } - for (File file : files) { - if (file.isFile() && isPackageFilename(file) && !file.equals(baseFile)) { - parseSplitApk(pkg, file, metrics, flags, trustedOverlay); - } - } - - // Always use a well-defined sort order - if (pkg.splitCodePaths != null) { - Arrays.sort(pkg.splitCodePaths); + for (File splitFile : splitFiles) { + parseSplitApk(pkg, splitFile, flags); } return pkg; } - public Package parseMonolithicPackage(File apkFile, DisplayMetrics metrics, int flags) - throws PackageParserException { - return parseMonolithicPackage(apkFile, metrics, flags, false); - } - - public Package parseMonolithicPackage(File apkFile, DisplayMetrics metrics, int flags, - boolean trustedOverlay) throws PackageParserException { - final Package pkg = parseBaseApk(apkFile, metrics, flags, trustedOverlay); + /** + * Parse the given APK file, treating it as as a single monolithic package. + * <p> + * Note that this <em>does not</em> perform signature verification; that + * must be done separately in {@link #collectCertificates(Package, int)}. + */ + public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException { + final Package pkg = parseBaseApk(apkFile, flags); if (pkg != null) { return pkg; } else { @@ -576,13 +639,15 @@ public class PackageParser { } } - private Package parseBaseApk(File apkFile, DisplayMetrics metrics, int flags, - boolean trustedOverlay) { + private Package parseBaseApk(File apkFile, int flags) { + final boolean trustedOverlay = (flags & PARSE_TRUSTED_OVERLAY) != 0; + mParseError = PackageManager.INSTALL_SUCCEEDED; + final String apkPath = apkFile.getAbsolutePath(); mArchiveSourcePath = apkFile.getAbsolutePath(); if (!apkFile.isFile()) { - Slog.w(TAG, "Skipping dir: " + mArchiveSourcePath); + Slog.w(TAG, "Skipping dir: " + apkPath); mParseError = PackageManager.INSTALL_PARSE_FAILED_NOT_APK; return null; } @@ -591,14 +656,14 @@ public class PackageParser { if ((flags&PARSE_IS_SYSTEM) == 0) { // We expect to have non-.apk files in the system dir, // so don't warn about them. - Slog.w(TAG, "Skipping non-package file: " + mArchiveSourcePath); + Slog.w(TAG, "Skipping non-package file: " + apkPath); } mParseError = PackageManager.INSTALL_PARSE_FAILED_NOT_APK; return null; } if (DEBUG_JAR) - Slog.d(TAG, "Scanning package: " + mArchiveSourcePath); + Slog.d(TAG, "Scanning package: " + apkPath); XmlResourceParser parser = null; AssetManager assmgr = null; @@ -606,19 +671,18 @@ public class PackageParser { boolean assetError = true; try { assmgr = new AssetManager(); - int cookie = assmgr.addAssetPath(mArchiveSourcePath); + int cookie = assmgr.addAssetPath(apkPath); if (cookie != 0) { - res = new Resources(assmgr, metrics, null); + res = new Resources(assmgr, mMetrics, null); assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); parser = assmgr.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); assetError = false; } else { - Slog.w(TAG, "Failed adding asset path:"+mArchiveSourcePath); + Slog.w(TAG, "Failed adding asset path:" + apkPath); } } catch (Exception e) { - Slog.w(TAG, "Unable to read AndroidManifest.xml of " - + mArchiveSourcePath, e); + Slog.w(TAG, "Unable to read AndroidManifest.xml of " + apkPath, e); } if (assetError) { if (assmgr != null) assmgr.close(); @@ -641,9 +705,9 @@ public class PackageParser { // just means to skip this app so don't make a fuss about it. if (!mOnlyCoreApps || mParseError != PackageManager.INSTALL_SUCCEEDED) { if (errorException != null) { - Slog.w(TAG, mArchiveSourcePath, errorException); + Slog.w(TAG, apkPath, errorException); } else { - Slog.w(TAG, mArchiveSourcePath + " (at " + Slog.w(TAG, apkPath + " (at " + parser.getPositionDescription() + "): " + errorText[0]); } @@ -659,14 +723,16 @@ public class PackageParser { parser.close(); assmgr.close(); - pkg.codePath = mArchiveSourcePath; + pkg.codePath = apkPath; pkg.mSignatures = null; return pkg; } - private void parseSplitApk(Package pkg, File apkFile, DisplayMetrics metrics, int flags, - boolean trustedOverlay) throws PackageParserException { + private void parseSplitApk(Package pkg, File apkFile, int flags) throws PackageParserException { + final String apkPath = apkFile.getAbsolutePath(); + mArchiveSourcePath = apkFile.getAbsolutePath(); + // TODO: expand split APK parsing pkg.splitCodePaths = ArrayUtils.appendElement(String.class, pkg.splitCodePaths, apkFile.getAbsolutePath()); @@ -678,8 +744,9 @@ public class PackageParser { * {@code AndroidManifest.xml}, {@code true} is returned. */ public void collectManifestDigest(Package pkg) throws PackageParserException { + // TODO: extend to gather digest for split APKs try { - final StrictJarFile jarFile = new StrictJarFile(mArchiveSourcePath); + final StrictJarFile jarFile = new StrictJarFile(pkg.codePath); try { final ZipEntry je = jarFile.findEntry(ANDROID_MANIFEST_FILENAME); if (je != null) { @@ -688,186 +755,127 @@ public class PackageParser { } finally { jarFile.close(); } - } catch (IOException e) { + } catch (IOException | RuntimeException e) { throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, "Failed to collect manifest digest"); } } + /** + * Collect certificates from all the APKs described in the given package, + * populating {@link Package#mSignatures}. This also asserts that all APK + * contents are signed correctly and consistently. + */ public void collectCertificates(Package pkg, int flags) throws PackageParserException { - if (!collectCertificatesInternal(pkg, flags)) { - throw new PackageParserException(mParseError, "Failed to collect certificates"); - } - } - - private boolean collectCertificatesInternal(Package pkg, int flags) { + pkg.mCertificates = null; pkg.mSignatures = null; + pkg.mSigningKeys = null; - WeakReference<byte[]> readBufferRef; - byte[] readBuffer = null; - synchronized (mSync) { - readBufferRef = mReadBuffer; - if (readBufferRef != null) { - mReadBuffer = null; - readBuffer = readBufferRef.get(); - } - if (readBuffer == null) { - readBuffer = new byte[8192]; - readBufferRef = new WeakReference<byte[]>(readBuffer); + collectCertificates(pkg, new File(pkg.codePath), flags); + + if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) { + for (String splitCodePath : pkg.splitCodePaths) { + collectCertificates(pkg, new File(splitCodePath), flags); } } + } + + private static void collectCertificates(Package pkg, File apkFile, int flags) + throws PackageParserException { + final String apkPath = apkFile.getAbsolutePath(); + StrictJarFile jarFile = null; try { - StrictJarFile jarFile = new StrictJarFile(mArchiveSourcePath); - - Certificate[][] certs = null; - - if ((flags&PARSE_IS_SYSTEM) != 0) { - // If this package comes from the system image, then we - // can trust it... we'll just use the AndroidManifest.xml - // to retrieve its signatures, not validating all of the - // files. - ZipEntry jarEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME); - certs = loadCertificates(jarFile, jarEntry, readBuffer); - if (certs == null) { - Slog.e(TAG, "Package " + pkg.packageName - + " has no certificates at entry " - + jarEntry.getName() + "; ignoring!"); - jarFile.close(); - mParseError = PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES; - return false; - } - if (DEBUG_JAR) { - Slog.i(TAG, "File " + mArchiveSourcePath + ": entry=" + jarEntry - + " certs=" + (certs != null ? certs.length : 0)); - if (certs != null) { - final int N = certs.length; - for (int i=0; i<N; i++) { - Slog.i(TAG, " Public key: " - + certs[i][0].getPublicKey().getEncoded() - + " " + certs[i][0].getPublicKey()); - } - } - } - } else { - Iterator<ZipEntry> entries = jarFile.iterator(); - while (entries.hasNext()) { - final ZipEntry je = entries.next(); - if (je.isDirectory()) continue; + jarFile = new StrictJarFile(apkPath); - final String name = je.getName(); + // Always verify manifest, regardless of source + final ZipEntry manifestEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME); + if (manifestEntry == null) { + throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, + "Package " + apkPath + " has no manifest"); + } - if (name.startsWith("META-INF/")) - continue; + final List<ZipEntry> toVerify = new ArrayList<>(); + toVerify.add(manifestEntry); - if (ANDROID_MANIFEST_FILENAME.equals(name)) { - pkg.manifestDigest = - ManifestDigest.fromInputStream(jarFile.getInputStream(je)); - } + // If we're parsing an untrusted package, verify all contents + if ((flags & PARSE_IS_SYSTEM) == 0) { + final Iterator<ZipEntry> i = jarFile.iterator(); + while (i.hasNext()) { + final ZipEntry entry = i.next(); - final Certificate[][] localCerts = loadCertificates(jarFile, je, readBuffer); - if (DEBUG_JAR) { - Slog.i(TAG, "File " + mArchiveSourcePath + " entry " + je.getName() - + ": certs=" + certs + " (" - + (certs != null ? certs.length : 0) + ")"); - } + if (entry.isDirectory()) continue; + if (entry.getName().startsWith("META-INF/")) continue; + if (entry.getName().equals(ANDROID_MANIFEST_FILENAME)) continue; - if (localCerts == null) { - Slog.e(TAG, "Package " + pkg.packageName - + " has no certificates at entry " - + je.getName() + "; ignoring!"); - jarFile.close(); - mParseError = PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES; - return false; - } else if (certs == null) { - certs = localCerts; - } else { - // Ensure all certificates match. - for (int i=0; i<certs.length; i++) { - boolean found = false; - for (int j=0; j<localCerts.length; j++) { - if (certs[i] != null && - certs[i].equals(localCerts[j])) { - found = true; - break; - } - } - if (!found || certs.length != localCerts.length) { - Slog.e(TAG, "Package " + pkg.packageName - + " has mismatched certificates at entry " - + je.getName() + "; ignoring!"); - jarFile.close(); - mParseError = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; - return false; - } - } - } + toVerify.add(entry); } } - jarFile.close(); - - synchronized (mSync) { - mReadBuffer = readBufferRef; - } - if (!ArrayUtils.isEmpty(certs)) { - pkg.mSignatures = convertToSignatures(certs); - } else { - Slog.e(TAG, "Package " + pkg.packageName - + " has no certificates; ignoring!"); - mParseError = PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES; - return false; - } + // Verify that entries are signed consistently with the first entry + // we encountered. Note that for splits, certificates may have + // already been populated during an earlier parse of a base APK. + for (ZipEntry entry : toVerify) { + final Certificate[][] entryCerts = loadCertificates(jarFile, entry); + if (ArrayUtils.isEmpty(entryCerts)) { + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "Package " + apkPath + " has no certificates at entry " + + entry.getName()); + } - // Add the signing KeySet to the system - pkg.mSigningKeys = new HashSet<PublicKey>(); - for (int i=0; i < certs.length; i++) { - pkg.mSigningKeys.add(certs[i][0].getPublicKey()); + if (pkg.mCertificates == null) { + pkg.mCertificates = entryCerts; + pkg.mSignatures = convertToSignatures(entryCerts); + pkg.mSigningKeys = new ArraySet<>(); + for (int i = 0; i < entryCerts.length; i++) { + pkg.mSigningKeys.add(entryCerts[i][0].getPublicKey()); + } + } else { + final boolean certsMatch = (pkg.mCertificates.length == entryCerts.length) + && ArrayUtils.containsAll(pkg.mCertificates, entryCerts) + && ArrayUtils.containsAll(entryCerts, pkg.mCertificates); + if (!certsMatch) { + throw new PackageParserException( + INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, "Package " + apkPath + + " has mismatched certificates at entry " + + entry.getName()); + } + } } - - } catch (CertificateEncodingException e) { - Slog.w(TAG, "Exception reading " + mArchiveSourcePath, e); - mParseError = PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING; - return false; - } catch (IOException e) { - Slog.w(TAG, "Exception reading " + mArchiveSourcePath, e); - mParseError = PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING; - return false; - } catch (SecurityException e) { - Slog.w(TAG, "Exception reading " + mArchiveSourcePath, e); - mParseError = PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING; - return false; - } catch (RuntimeException e) { - Slog.w(TAG, "Exception reading " + mArchiveSourcePath, e); - mParseError = PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION; - return false; + } catch (GeneralSecurityException | IOException | RuntimeException e) { + throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING, + "Failed to collect certificates from " + apkPath, e); + } finally { + closeQuietly(jarFile); } - - return true; } /** * Only collect certificates on the manifest; does not validate signatures * across remainder of package. */ - private static Signature[] collectCertificates(String packageFilePath) { + private static Signature[] collectManifestCertificates(File apkFile) + throws PackageParserException { + final String apkPath = apkFile.getAbsolutePath(); try { - final StrictJarFile jarFile = new StrictJarFile(packageFilePath); + final StrictJarFile jarFile = new StrictJarFile(apkPath); try { final ZipEntry jarEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME); - if (jarEntry != null) { - final Certificate[][] certs = loadCertificates(jarFile, jarEntry, null); - return convertToSignatures(certs); + if (jarEntry == null) { + throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, + "Package " + apkPath + " has no manifest"); } + + final Certificate[][] certs = loadCertificates(jarFile, jarEntry); + return convertToSignatures(certs); + } finally { jarFile.close(); } - } catch (GeneralSecurityException e) { - Slog.w(TAG, "Failed to collect certs from " + packageFilePath + ": " + e); - } catch (IOException e) { - Slog.w(TAG, "Failed to collect certs from " + packageFilePath + ": " + e); + } catch (GeneralSecurityException | IOException | RuntimeException e) { + throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING, + "Failed to collect certificates from " + apkPath, e); } - return null; } private static Signature[] convertToSignatures(Certificate[][] certs) @@ -879,67 +887,55 @@ public class PackageParser { return res; } - /* - * Utility method that retrieves just the package name and install - * location from the apk location at the given file path. - * @param packageFilePath file location of the apk - * @param flags Special parse flags - * @return PackageLite object with package information or null on failure. + /** + * Utility method that retrieves lightweight details about a single APK + * file, including package name, split name, and install location. + * + * @param apkFile path to a single APK + * @param flags optional parse flags, such as {@link #PARSE_GET_SIGNATURES} */ - public static ApkLite parseApkLite(String packageFilePath, int flags) { + public static ApkLite parseApkLite(File apkFile, int flags) + throws PackageParserException { + final String apkPath = apkFile.getAbsolutePath(); + AssetManager assmgr = null; - final XmlResourceParser parser; - final Resources res; + XmlResourceParser parser = null; try { assmgr = new AssetManager(); assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); - int cookie = assmgr.addAssetPath(packageFilePath); + int cookie = assmgr.addAssetPath(apkPath); if (cookie == 0) { - return null; + throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, + "Failed to parse " + apkPath); } final DisplayMetrics metrics = new DisplayMetrics(); metrics.setToDefaults(); - res = new Resources(assmgr, metrics, null); + + final Resources res = new Resources(assmgr, metrics, null); parser = assmgr.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); - } catch (Exception e) { - if (assmgr != null) assmgr.close(); - Slog.w(TAG, "Unable to read AndroidManifest.xml of " - + packageFilePath, e); - return null; - } - // Only collect certificates on the manifest; does not validate - // signatures across remainder of package. - final Signature[] signatures; - if ((flags & PARSE_GET_SIGNATURES) != 0) { - signatures = collectCertificates(packageFilePath); - } else { - signatures = null; - } + // Only collect certificates on the manifest; does not validate + // signatures across remainder of package. + final Signature[] signatures; + if ((flags & PARSE_GET_SIGNATURES) != 0) { + signatures = collectManifestCertificates(apkFile); + } else { + signatures = null; + } - final AttributeSet attrs = parser; - final String errors[] = new String[1]; - ApkLite packageLite = null; - try { - packageLite = parseApkLite(res, parser, attrs, flags, signatures, errors); - } catch (PackageParserException e) { - Slog.w(TAG, packageFilePath, e); - } catch (IOException e) { - Slog.w(TAG, packageFilePath, e); - } catch (XmlPullParserException e) { - Slog.w(TAG, packageFilePath, e); + final AttributeSet attrs = parser; + return parseApkLite(res, parser, attrs, flags, signatures); + + } catch (XmlPullParserException | IOException | RuntimeException e) { + throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, + "Failed to parse " + apkPath, e); } finally { if (parser != null) parser.close(); if (assmgr != null) assmgr.close(); } - if (packageLite == null) { - Slog.e(TAG, "parsePackageLite error: " + errors[0]); - return null; - } - return packageLite; } private static String validateName(String name, boolean requiresSeparator) { @@ -995,12 +991,16 @@ public class PackageParser { } } - final String splitName = attrs.getAttributeValue(null, "split"); + String splitName = attrs.getAttributeValue(null, "split"); if (splitName != null) { - final String error = validateName(splitName, true); - if (error != null) { - throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME, - "Invalid manifest split: " + error); + if (splitName.length() == 0) { + splitName = null; + } else { + final String error = validateName(splitName, true); + if (error != null) { + throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME, + "Invalid manifest split: " + error); + } } } @@ -1009,8 +1009,8 @@ public class PackageParser { } private static ApkLite parseApkLite(Resources res, XmlPullParser parser, - AttributeSet attrs, int flags, Signature[] signatures, String[] outError) - throws IOException, XmlPullParserException, PackageParserException { + AttributeSet attrs, int flags, Signature[] signatures) throws IOException, + XmlPullParserException, PackageParserException { final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs, flags); int installLocation = PARSE_DEFAULT_INSTALL_LOCATION; @@ -1043,7 +1043,7 @@ public class PackageParser { } if (parser.getDepth() == searchDepth && "package-verifier".equals(parser.getName())) { - final VerifierInfo verifier = parseVerifier(res, parser, attrs, flags, outError); + final VerifierInfo verifier = parseVerifier(res, parser, attrs, flags); if (verifier != null) { verifiers.add(verifier); } @@ -1793,7 +1793,7 @@ public class PackageParser { } } - owner.mKeySetMapping = new HashMap<String, Set<PublicKey>>(); + owner.mKeySetMapping = new ArrayMap<String, ArraySet<PublicKey>>(); for (Map.Entry<PublicKey, Set<String>> e : definedKeySets.entrySet()) { PublicKey key = e.getKey(); Set<String> keySetNames = e.getValue(); @@ -1801,7 +1801,7 @@ public class PackageParser { if (owner.mKeySetMapping.containsKey(alias)) { owner.mKeySetMapping.get(alias).add(key); } else { - Set<PublicKey> keys = new HashSet<PublicKey>(); + ArraySet<PublicKey> keys = new ArraySet<PublicKey>(); keys.add(key); owner.mKeySetMapping.put(alias, keys); } @@ -2607,10 +2607,9 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestActivity_windowSoftInputMode, 0); - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestActivity_persistable, false)) { - a.info.flags |= ActivityInfo.FLAG_PERSISTABLE; - } + a.info.persistableMode = sa.getInteger( + com.android.internal.R.styleable.AndroidManifestActivity_persistableMode, + ActivityInfo.PERSIST_ROOT_ONLY); if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestActivity_allowEmbedded, @@ -3428,8 +3427,7 @@ public class PackageParser { } private static VerifierInfo parseVerifier(Resources res, XmlPullParser parser, - AttributeSet attrs, int flags, String[] outError) throws XmlPullParserException, - IOException { + AttributeSet attrs, int flags) { final TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestPackageVerifier); @@ -3672,7 +3670,10 @@ public class PackageParser { public String packageName; // TODO: work towards making these paths invariant + + /** Base APK */ public String codePath; + /** Split APKs, ordered by parsed splitName */ public String[] splitCodePaths; // For now we only support one application per package. @@ -3718,7 +3719,8 @@ public class PackageParser { public int mSharedUserLabel; // Signatures that were read from the package. - public Signature mSignatures[]; + public Signature[] mSignatures; + public Certificate[][] mCertificates; // For use by package manager service for quick lookup of // preferred up order. @@ -3780,8 +3782,8 @@ public class PackageParser { /** * Data used to feed the KeySetManager */ - public Set<PublicKey> mSigningKeys; - public Map<String, Set<PublicKey>> mKeySetMapping; + public ArraySet<PublicKey> mSigningKeys; + public ArrayMap<String, ArraySet<PublicKey>> mKeySetMapping; public Package(String packageName) { this.packageName = packageName; @@ -3789,6 +3791,15 @@ public class PackageParser { applicationInfo.uid = -1; } + public Collection<String> getAllCodePaths() { + ArrayList<String> paths = new ArrayList<>(); + paths.add(codePath); + if (!ArrayUtils.isEmpty(splitCodePaths)) { + Collections.addAll(paths, splitCodePaths); + } + return paths; + } + public void setPackageName(String newName) { packageName = newName; applicationInfo.packageName = newName; @@ -4391,6 +4402,33 @@ public class PackageParser { sCompatibilityModeEnabled = compatibilityModeEnabled; } + private static AtomicReference<byte[]> sBuffer = new AtomicReference<byte[]>(); + + public static long readFullyIgnoringContents(InputStream in) throws IOException { + byte[] buffer = sBuffer.getAndSet(null); + if (buffer == null) { + buffer = new byte[4096]; + } + + int n = 0; + int count = 0; + while ((n = in.read(buffer, 0, buffer.length)) != -1) { + count += n; + } + + sBuffer.set(buffer); + return count; + } + + public static void closeQuietly(StrictJarFile jarFile) { + if (jarFile != null) { + try { + jarFile.close(); + } catch (Exception ignored) { + } + } + } + public static class PackageParserException extends Exception { public final int error; @@ -4398,5 +4436,10 @@ public class PackageParser { super(detailMessage); this.error = error; } + + public PackageParserException(int error, String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + this.error = error; + } } } diff --git a/core/java/android/hardware/hdmi/HdmiCec.java b/core/java/android/hardware/hdmi/HdmiCec.java index 8ad94632fbd7..d86dd5e7b207 100644 --- a/core/java/android/hardware/hdmi/HdmiCec.java +++ b/core/java/android/hardware/hdmi/HdmiCec.java @@ -185,6 +185,7 @@ public final class HdmiCec { public static final int RESULT_TARGET_NOT_AVAILABLE = 3; public static final int RESULT_ALREADY_IN_PROGRESS = 4; public static final int RESULT_EXCEPTION = 5; + public static final int RESULT_INCORRECT_MODE = 6; private static final int[] ADDRESS_TO_TYPE = { DEVICE_TV, // ADDR_TV diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java index 535bbe2415ef..81426704cc0f 100644 --- a/core/java/android/net/MobileDataStateTracker.java +++ b/core/java/android/net/MobileDataStateTracker.java @@ -699,15 +699,15 @@ public class MobileDataStateTracker extends BaseNetworkStateTracker { break; } - try { - if (enable) { - return mPhoneService.enableApnType(apnType); - } else { - return mPhoneService.disableApnType(apnType); - } - } catch (RemoteException e) { - if (retry == 0) getPhoneService(true); - } +// try { +// if (enable) { +// return mPhoneService.enableApnType(apnType); +// } else { +// return mPhoneService.disableApnType(apnType); +// } +// } catch (RemoteException e) { +// if (retry == 0) getPhoneService(true); +// } } loge("Could not " + (enable ? "enable" : "disable") + " APN type \"" + apnType + "\""); diff --git a/core/java/android/os/BatteryManagerInternal.java b/core/java/android/os/BatteryManagerInternal.java new file mode 100644 index 000000000000..f3a95b90d5c1 --- /dev/null +++ b/core/java/android/os/BatteryManagerInternal.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +/** + * Battery manager local system service interface. + * + * @hide Only for use within the system server. + */ +public abstract class BatteryManagerInternal { + /** + * Returns true if the device is plugged into any of the specified plug types. + */ + public abstract boolean isPowered(int plugTypeSet); + + /** + * Returns the current plug type. + */ + public abstract int getPlugType(); + + /** + * Returns battery level as a percentage. + */ + public abstract int getBatteryLevel(); + + /** + * Returns whether we currently consider the battery level to be low. + */ + public abstract boolean getBatteryLevelLow(); + + /** + * Returns a non-zero value if an unsupported charger is attached. + */ + public abstract int getInvalidCharger(); +} diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java index 69b828f82ac8..08a15eb7dfa1 100644 --- a/core/java/android/os/PowerManagerInternal.java +++ b/core/java/android/os/PowerManagerInternal.java @@ -16,8 +16,6 @@ package android.os; -import android.view.WindowManagerPolicy; - /** * Power manager local system service interface. * @@ -57,12 +55,9 @@ public abstract class PowerManagerInternal { public abstract boolean getLowPowerModeEnabled(); + public abstract void registerLowPowerModeObserver(LowPowerModeListener listener); + public interface LowPowerModeListener { public void onLowPowerModeChanged(boolean enabled); } - - public abstract void registerLowPowerModeObserver(LowPowerModeListener listener); - - // TODO: Remove this and retrieve as a local service instead. - public abstract void setPolicy(WindowManagerPolicy policy); } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 468cfe0229c9..757f38e065de 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -226,14 +226,14 @@ public class UserManager { public static final String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks"; /** - * Key for user restrictions. Specifies if a user is disallowed from configuring + * Key for user restrictions. Specifies if a user is disallowed from controlling * applications in Settings. The default value is <code>false</code>. * <p> * Type: Boolean * @see #setUserRestrictions(Bundle) * @see #getUserRestrictions() */ - public static final String DISALLOW_CONFIG_APPS = "no_config_apps"; + public static final String DISALLOW_APPS_CONTROL = "no_control_apps"; /** * Key for user restrictions. Specifies if a user is disallowed from mounting diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java index 04180491f137..23b1e2c00022 100644 --- a/core/java/android/preference/PreferenceActivity.java +++ b/core/java/android/preference/PreferenceActivity.java @@ -1194,7 +1194,14 @@ public abstract class PreferenceActivity extends ListActivity implements * @param args Optional arguments to supply to the fragment. */ public void switchToHeader(String fragmentName, Bundle args) { - setSelectedHeader(null); + Header selectedHeader = null; + for (int i = 0; i < mHeaders.size(); i++) { + if (fragmentName.equals(mHeaders.get(i).fragment)) { + selectedHeader = mHeaders.get(i); + break; + } + } + setSelectedHeader(selectedHeader); switchToHeaderInner(fragmentName, args); } diff --git a/core/java/android/service/fingerprint/FingerprintManager.java b/core/java/android/service/fingerprint/FingerprintManager.java index b6137d11b2cc..5fd597ba0ec4 100644 --- a/core/java/android/service/fingerprint/FingerprintManager.java +++ b/core/java/android/service/fingerprint/FingerprintManager.java @@ -96,6 +96,9 @@ public class FingerprintManager { } }; + /** + * @hide + */ public FingerprintManager(Context context, IFingerprintService service) { mContext = context; mService = service; diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index 508769d0ff29..e93623243cf3 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -673,7 +673,7 @@ public abstract class Transition implements Cloneable { startDelays.put(mAnimators.size(), delay); minStartDelay = Math.min(delay, minStartDelay); } - AnimationInfo info = new AnimationInfo(view, getName(), + AnimationInfo info = new AnimationInfo(view, getName(), this, sceneRoot.getWindowId(), infoValues); runningAnimators.put(animator, info); mAnimators.add(animator); @@ -1587,30 +1587,10 @@ public abstract class Transition implements Cloneable { AnimationInfo oldInfo = runningAnimators.get(anim); if (oldInfo != null && oldInfo.view != null && oldInfo.view.getContext() == sceneRoot.getContext()) { - boolean cancel = false; TransitionValues oldValues = oldInfo.values; View oldView = oldInfo.view; TransitionValues newValues = mEndValues.viewValues.get(oldView); - if (oldValues != null) { - // if oldValues null, then transition didn't care to stash values, - // and won't get canceled - if (newValues != null) { - for (String key : oldValues.values.keySet()) { - Object oldValue = oldValues.values.get(key); - Object newValue = newValues.values.get(key); - if (oldValue != null && newValue != null && - !oldValue.equals(newValue)) { - cancel = true; - if (DBG) { - Log.d(LOG_TAG, "Transition.playTransition: " + - "oldValue != newValue for " + key + - ": old, new = " + oldValue + ", " + newValue); - } - break; - } - } - } - } + boolean cancel = oldInfo.transition.areValuesChanged(oldValues, newValues); if (cancel) { if (anim.isRunning() || anim.isStarted()) { if (DBG) { @@ -1632,6 +1612,29 @@ public abstract class Transition implements Cloneable { runAnimators(); } + boolean areValuesChanged(TransitionValues oldValues, TransitionValues newValues) { + boolean valuesChanged = false; + // if oldValues null, then transition didn't care to stash values, + // and won't get canceled + if (oldValues != null && newValues != null) { + for (String key : oldValues.values.keySet()) { + Object oldValue = oldValues.values.get(key); + Object newValue = newValues.values.get(key); + if (oldValue != null && newValue != null && + !oldValue.equals(newValue)) { + valuesChanged = true; + if (DBG) { + Log.d(LOG_TAG, "Transition.playTransition: " + + "oldValue != newValue for " + key + + ": old, new = " + oldValue + ", " + newValue); + } + break; + } + } + } + return valuesChanged; + } + /** * This is a utility method used by subclasses to handle standard parts of * setting up and running an Animator: it sets the {@link #getDuration() @@ -2070,12 +2073,15 @@ public abstract class Transition implements Cloneable { String name; TransitionValues values; WindowId windowId; + Transition transition; - AnimationInfo(View view, String name, WindowId windowId, TransitionValues values) { + AnimationInfo(View view, String name, Transition transition, + WindowId windowId, TransitionValues values) { this.view = view; this.name = name; this.values = values; this.windowId = windowId; + this.transition = transition; } } diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java index c6c833772784..947e1a716c7e 100644 --- a/core/java/android/transition/Visibility.java +++ b/core/java/android/transition/Visibility.java @@ -331,16 +331,6 @@ public abstract class Visibility extends Transition { public void onAnimationEnd(Animator animation) { finalSceneRoot.getOverlay().remove(finalOverlayView); } - - @Override - public void onAnimationPause(Animator animation) { - finalSceneRoot.getOverlay().remove(finalOverlayView); - } - - @Override - public void onAnimationResume(Animator animation) { - finalSceneRoot.getOverlay().add(finalOverlayView); - } }); } return animator; @@ -409,6 +399,16 @@ public abstract class Visibility extends Transition { return overlayView; } + @Override + boolean areValuesChanged(TransitionValues oldValues, TransitionValues newValues) { + VisibilityInfo changeInfo = getVisibilityChangeInfo(oldValues, newValues); + if (oldValues == null && newValues == null) { + return false; + } + return changeInfo.visibilityChange && (changeInfo.startVisibility == View.VISIBLE || + changeInfo.endVisibility == View.VISIBLE); + } + /** * The default implementation of this method returns a null Animator. Subclasses should * override this method to make targets disappear with the desired transition. The diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index d7a913d6c380..181f77e2c886 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -599,6 +599,42 @@ public final class Display { } /** + * Gets the app VSYNC offset, in nanoseconds. This is a positive value indicating + * the phase offset of the VSYNC events provided by Choreographer relative to the + * display refresh. For example, if Choreographer reports that the refresh occurred + * at time N, it actually occurred at (N - appVsyncOffset). + * <p> + * Apps generally do not need to be aware of this. It's only useful for fine-grained + * A/V synchronization. + * @hide + */ + public long getAppVsyncOffsetNanos() { + synchronized (this) { + updateDisplayInfoLocked(); + return mDisplayInfo.appVsyncOffsetNanos; + } + } + + /** + * This is how far in advance a buffer must be queued for presentation at + * a given time. If you want a buffer to appear on the screen at + * time N, you must submit the buffer before (N - presentationDeadline). + * <p> + * The desired presentation time for GLES rendering may be set with + * {@link android.opengl.EGLExt#eglPresentationTimeANDROID}. For video decoding, use + * {@link android.media.MediaCodec#releaseOutputBuffer(int, long)}. Times are + * expressed in nanoseconds, using the system monotonic clock + * ({@link System#nanoTime}). + * @hide + */ + public long getPresentationDeadlineNanos() { + synchronized (this) { + updateDisplayInfoLocked(); + return mDisplayInfo.presentationDeadlineNanos; + } + } + + /** * Gets display metrics that describe the size and density of this display. * <p> * The size is adjusted based on the current rotation of the display. diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index b0fe0faf72f6..98696c7e54ec 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -180,6 +180,20 @@ public final class DisplayInfo implements Parcelable { public float physicalYDpi; /** + * This is a positive value indicating the phase offset of the VSYNC events provided by + * Choreographer relative to the display refresh. For example, if Choreographer reports + * that the refresh occurred at time N, it actually occurred at (N - appVsyncOffsetNanos). + */ + public long appVsyncOffsetNanos; + + /** + * This is how far in advance a buffer must be queued for presentation at + * a given time. If you want a buffer to appear on the screen at + * time N, you must submit the buffer before (N - bufferDeadlineNanos). + */ + public long presentationDeadlineNanos; + + /** * The state of the display, such as {@link android.view.Display#STATE_ON}. */ public int state; @@ -253,6 +267,8 @@ public final class DisplayInfo implements Parcelable { && logicalDensityDpi == other.logicalDensityDpi && physicalXDpi == other.physicalXDpi && physicalYDpi == other.physicalYDpi + && appVsyncOffsetNanos == other.appVsyncOffsetNanos + && presentationDeadlineNanos == other.presentationDeadlineNanos && state == other.state && ownerUid == other.ownerUid && Objects.equal(ownerPackageName, other.ownerPackageName); @@ -286,6 +302,8 @@ public final class DisplayInfo implements Parcelable { logicalDensityDpi = other.logicalDensityDpi; physicalXDpi = other.physicalXDpi; physicalYDpi = other.physicalYDpi; + appVsyncOffsetNanos = other.appVsyncOffsetNanos; + presentationDeadlineNanos = other.presentationDeadlineNanos; state = other.state; ownerUid = other.ownerUid; ownerPackageName = other.ownerPackageName; @@ -314,6 +332,8 @@ public final class DisplayInfo implements Parcelable { logicalDensityDpi = source.readInt(); physicalXDpi = source.readFloat(); physicalYDpi = source.readFloat(); + appVsyncOffsetNanos = source.readLong(); + presentationDeadlineNanos = source.readLong(); state = source.readInt(); ownerUid = source.readInt(); ownerPackageName = source.readString(); @@ -343,6 +363,8 @@ public final class DisplayInfo implements Parcelable { dest.writeInt(logicalDensityDpi); dest.writeFloat(physicalXDpi); dest.writeFloat(physicalYDpi); + dest.writeLong(appVsyncOffsetNanos); + dest.writeLong(presentationDeadlineNanos); dest.writeInt(state); dest.writeInt(ownerUid); dest.writeString(ownerPackageName); @@ -450,6 +472,10 @@ public final class DisplayInfo implements Parcelable { sb.append(physicalYDpi); sb.append(") dpi, layerStack "); sb.append(layerStack); + sb.append(", appVsyncOff "); + sb.append(appVsyncOffsetNanos); + sb.append(", presDeadline "); + sb.append(presentationDeadlineNanos); sb.append(", type "); sb.append(Display.typeToString(type)); if (address != null) { diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index 446bbc9732a0..68e2146c821b 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -804,20 +804,12 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawPicture(Picture picture) { - if (picture.createdFromStream) { - return; - } - picture.endRecording(); // TODO: Implement rendering } @Override public void drawPicture(Picture picture, Rect dst) { - if (picture.createdFromStream) { - return; - } - save(); translate(dst.left, dst.top); if (picture.getWidth() > 0 && picture.getHeight() > 0) { @@ -829,10 +821,6 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawPicture(Picture picture, RectF dst) { - if (picture.createdFromStream) { - return; - } - save(); translate(dst.left, dst.top); if (picture.getWidth() > 0 && picture.getHeight() > 0) { @@ -981,22 +969,24 @@ class GLES20Canvas extends HardwareCanvas { } nDrawTextOnPath(mRenderer, text, index, count, path.mNativePath, hOffset, vOffset, - paint.mBidiFlags, paint.mNativePaint); + paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); } private static native void nDrawTextOnPath(long renderer, char[] text, int index, int count, - long path, float hOffset, float vOffset, int bidiFlags, long nativePaint); + long path, float hOffset, float vOffset, int bidiFlags, long nativePaint, + long typeface); @Override public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) { if (text.length() == 0) return; nDrawTextOnPath(mRenderer, text, 0, text.length(), path.mNativePath, hOffset, vOffset, - paint.mBidiFlags, paint.mNativePaint); + paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); } private static native void nDrawTextOnPath(long renderer, String text, int start, int end, - long path, float hOffset, float vOffset, int bidiFlags, long nativePaint); + long path, float hOffset, float vOffset, int bidiFlags, long nativePaint, + long typeface); @Override public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount, diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 79f19b583bc5..94d8f703c53b 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -458,6 +458,8 @@ public class SurfaceControl { public float xDpi; public float yDpi; public boolean secure; + public long appVsyncOffsetNanos; + public long presentationDeadlineNanos; public PhysicalDisplayInfo() { } @@ -479,7 +481,9 @@ public class SurfaceControl { && density == other.density && xDpi == other.xDpi && yDpi == other.yDpi - && secure == other.secure; + && secure == other.secure + && appVsyncOffsetNanos == other.appVsyncOffsetNanos + && presentationDeadlineNanos == other.presentationDeadlineNanos; } @Override @@ -495,6 +499,8 @@ public class SurfaceControl { xDpi = other.xDpi; yDpi = other.yDpi; secure = other.secure; + appVsyncOffsetNanos = other.appVsyncOffsetNanos; + presentationDeadlineNanos = other.presentationDeadlineNanos; } // For debugging purposes @@ -502,7 +508,8 @@ public class SurfaceControl { public String toString() { return "PhysicalDisplayInfo{" + width + " x " + height + ", " + refreshRate + " fps, " + "density " + density + ", " + xDpi + " x " + yDpi + " dpi, secure " + secure - + "}"; + + ", appVsyncOffset " + appVsyncOffsetNanos + + ", bufferDeadline " + presentationDeadlineNanos + "}"; } } diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java index 9601d4a703df..9914800ec6ca 100644 --- a/core/java/android/widget/Spinner.java +++ b/core/java/android/widget/Spinner.java @@ -25,6 +25,7 @@ import android.content.res.TypedArray; import android.database.DataSetObserver; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; @@ -427,9 +428,15 @@ public class Spinner extends AbsSpinner implements OnClickListener { * {@link Adapter#getItemViewType(int) getItemViewType(int)} on the object * returned from {@link #getAdapter()} will always return 0. Calling * {@link Adapter#getViewTypeCount() getViewTypeCount()} will always return - * 1. + * 1. On API {@link Build.VERSION_CODES#L} and above, attempting to set an + * adapter with more than one view type will throw an + * {@link IllegalArgumentException}. + * + * @param adapter the adapter to set * * @see AbsSpinner#setAdapter(SpinnerAdapter) + * @throws IllegalArgumentException if the adapter has more than one view + * type */ @Override public void setAdapter(SpinnerAdapter adapter) { @@ -437,6 +444,12 @@ public class Spinner extends AbsSpinner implements OnClickListener { mRecycler.clear(); + final int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion; + if (targetSdkVersion >= Build.VERSION_CODES.L + && adapter != null && adapter.getViewTypeCount() != 1) { + throw new IllegalArgumentException("Spinner adapter view type count must be 1"); + } + if (mPopup != null) { mPopup.setAdapter(new DropDownAdapter(adapter)); } else { diff --git a/core/java/com/android/internal/policy/IFaceLockInterface.aidl b/core/java/com/android/internal/policy/IFaceLockInterface.aidl index 017801bbf886..bc1f0020f495 100644 --- a/core/java/com/android/internal/policy/IFaceLockInterface.aidl +++ b/core/java/com/android/internal/policy/IFaceLockInterface.aidl @@ -23,6 +23,7 @@ interface IFaceLockInterface { void startUi(IBinder containingWindowToken, int x, int y, int width, int height, boolean useLiveliness); void stopUi(); + void startWithoutUi(); void registerCallback(IFaceLockCallback cb); void unregisterCallback(IFaceLockCallback cb); } diff --git a/core/java/com/android/internal/widget/SwipeDismissLayout.java b/core/java/com/android/internal/widget/SwipeDismissLayout.java index 002573ebb068..97b1634f99cc 100644 --- a/core/java/com/android/internal/widget/SwipeDismissLayout.java +++ b/core/java/com/android/internal/widget/SwipeDismissLayout.java @@ -45,10 +45,9 @@ public class SwipeDismissLayout extends FrameLayout { /** * Called when the layout has been swiped and the position of the window should change. * - * @param progress A number in [-1, 1] representing how far to the left - * or right the window has been swiped. Negative values are swipes - * left, and positives are right. - * @param translate A number in [-w, w], where w is the width of the + * @param progress A number in [0, 1] representing how far to the + * right the window has been swiped + * @param translate A number in [0, w], where w is the width of the * layout. This is equivalent to progress * layout.getWidth(). */ void onSwipeProgressChanged(SwipeDismissLayout layout, float progress, float translate); @@ -207,7 +206,7 @@ public class SwipeDismissLayout extends FrameLayout { private void setProgress(float deltaX) { mTranslationX = deltaX; - if (mProgressListener != null) { + if (mProgressListener != null && deltaX >= 0) { mProgressListener.onSwipeProgressChanged(this, deltaX / getWidth(), deltaX); } } diff --git a/core/java/com/android/server/SystemServiceManager.java b/core/java/com/android/server/SystemServiceManager.java index 87a50a9940af..fda6479e2d56 100644 --- a/core/java/com/android/server/SystemServiceManager.java +++ b/core/java/com/android/server/SystemServiceManager.java @@ -50,8 +50,19 @@ public class SystemServiceManager { * @return The service instance. */ @SuppressWarnings("unchecked") - public SystemService startService(String className) throws ClassNotFoundException { - return startService((Class<SystemService>) Class.forName(className)); + public SystemService startService(String className) { + final Class<SystemService> serviceClass; + try { + serviceClass = (Class<SystemService>)Class.forName(className); + } catch (ClassNotFoundException ex) { + Slog.i(TAG, "Starting " + className); + throw new RuntimeException("Failed to create service " + className + + ": service class not found, usually indicates that the caller should " + + "have called PackageManager.hasSystemFeature() to check whether the " + + "feature is available on this device before trying to start the " + + "services that implement it", ex); + } + return startService(serviceClass); } /** diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 15dfed1bc76b..cb00062b3923 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -89,7 +89,7 @@ LOCAL_SRC_FILES:= \ android_util_Process.cpp \ android_util_StringBlock.cpp \ android_util_XmlBlock.cpp \ - android/graphics/AndroidPicture.cpp \ + android_graphics_Picture.cpp \ android/graphics/AutoDecodeCancel.cpp \ android/graphics/Bitmap.cpp \ android/graphics/BitmapFactory.cpp \ diff --git a/core/jni/android/graphics/AndroidPicture.cpp b/core/jni/android/graphics/AndroidPicture.cpp deleted file mode 100644 index 5977ab263a99..000000000000 --- a/core/jni/android/graphics/AndroidPicture.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "AndroidPicture.h" -#include "SkCanvas.h" -#include "SkStream.h" - -AndroidPicture::AndroidPicture(const AndroidPicture* src) { - if (NULL != src) { - mWidth = src->width(); - mHeight = src->height(); - if (NULL != src->mPicture.get()) { - mPicture.reset(SkRef(src->mPicture.get())); - } if (NULL != src->mRecorder.get()) { - mPicture.reset(src->makePartialCopy()); - } - } else { - mWidth = 0; - mHeight = 0; - } -} - -SkCanvas* AndroidPicture::beginRecording(int width, int height) { - mPicture.reset(NULL); - mRecorder.reset(new SkPictureRecorder); - mWidth = width; - mHeight = height; - return mRecorder->beginRecording(width, height, NULL, 0); -} - -void AndroidPicture::endRecording() { - if (NULL != mRecorder.get()) { - mPicture.reset(mRecorder->endRecording()); - mRecorder.reset(NULL); - } -} - -int AndroidPicture::width() const { - if (NULL != mPicture.get()) { - SkASSERT(mPicture->width() == mWidth); - SkASSERT(mPicture->height() == mHeight); - } - - return mWidth; -} - -int AndroidPicture::height() const { - if (NULL != mPicture.get()) { - SkASSERT(mPicture->width() == mWidth); - SkASSERT(mPicture->height() == mHeight); - } - - return mHeight; -} - -AndroidPicture* AndroidPicture::CreateFromStream(SkStream* stream) { - AndroidPicture* newPict = new AndroidPicture; - - newPict->mPicture.reset(SkPicture::CreateFromStream(stream)); - if (NULL != newPict->mPicture.get()) { - newPict->mWidth = newPict->mPicture->width(); - newPict->mHeight = newPict->mPicture->height(); - } - - return newPict; -} - -void AndroidPicture::serialize(SkWStream* stream) const { - if (NULL != mRecorder.get()) { - SkAutoTDelete<SkPicture> tempPict(this->makePartialCopy()); - tempPict->serialize(stream); - } else if (NULL != mPicture.get()) { - mPicture->serialize(stream); - } else { - SkPicture empty; - empty.serialize(stream); - } -} - -void AndroidPicture::draw(SkCanvas* canvas) { - if (NULL != mRecorder.get()) { - this->endRecording(); - SkASSERT(NULL != mPicture.get()); - } - if (NULL != mPicture.get()) { - // TODO: remove this const_cast once pictures are immutable - const_cast<SkPicture*>(mPicture.get())->draw(canvas); - } -} - -SkPicture* AndroidPicture::makePartialCopy() const { - SkASSERT(NULL != mRecorder.get()); - - SkPictureRecorder reRecorder; - - SkCanvas* canvas = reRecorder.beginRecording(mWidth, mHeight, NULL, 0); - mRecorder->partialReplay(canvas); - return reRecorder.endRecording(); -} diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp index 4584c464ffa0..69e149e94974 100644 --- a/core/jni/android/graphics/Canvas.cpp +++ b/core/jni/android/graphics/Canvas.cpp @@ -1123,29 +1123,82 @@ public: delete[] posPtr; } +#ifdef USE_MINIKIN + class DrawTextOnPathFunctor { + public: + DrawTextOnPathFunctor(const Layout& layout, SkCanvas* canvas, float hOffset, + float vOffset, SkPaint* paint, SkPath* path) + : layout(layout), canvas(canvas), hOffset(hOffset), vOffset(vOffset), + paint(paint), path(path) { + } + void operator()(size_t start, size_t end) { + uint16_t glyphs[1]; + for (size_t i = start; i < end; i++) { + glyphs[0] = layout.getGlyphId(i); + float x = hOffset + layout.getX(i); + float y = vOffset + layout.getY(i); + canvas->drawTextOnPathHV(glyphs, sizeof(glyphs), *path, x, y, *paint); + } + } + private: + const Layout& layout; + SkCanvas* canvas; + float hOffset; + float vOffset; + SkPaint* paint; + SkPath* path; + }; +#endif + + static void doDrawTextOnPath(SkPaint* paint, const jchar* text, int count, int bidiFlags, + float hOffset, float vOffset, SkPath* path, SkCanvas* canvas, TypefaceImpl* typeface) { +#ifdef USE_MINIKIN + Layout layout; + std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface); + layout.doLayout(text, 0, count, count, css); + hOffset += MinikinUtils::hOffsetForTextAlign(paint, layout, *path); + // Set align to left for drawing, as we don't want individual + // glyphs centered or right-aligned; the offset above takes + // care of all alignment. + SkPaint::Align align = paint->getTextAlign(); + paint->setTextAlign(SkPaint::kLeft_Align); + + DrawTextOnPathFunctor f(layout, canvas, hOffset, vOffset, paint, path); + MinikinUtils::forFontRun(layout, paint, f); + paint->setTextAlign(align); +#else + TextLayout::drawTextOnPath(paint, text, count, bidiFlags, hOffset, vOffset, path, canvas); +#endif + } + static void drawTextOnPath___CIIPathFFPaint(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, jint index, jint count, - jlong pathHandle, jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintHandle) { + jlong pathHandle, jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintHandle, + jlong typefaceHandle) { SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPath* path = reinterpret_cast<SkPath*>(pathHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); + TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle); jchar* textArray = env->GetCharArrayElements(text, NULL); - TextLayout::drawTextOnPath(paint, textArray + index, count, bidiFlags, hOffset, vOffset, - path, canvas); + doDrawTextOnPath(paint, textArray + index, count, bidiFlags, hOffset, vOffset, + path, canvas, typeface); env->ReleaseCharArrayElements(text, textArray, 0); } static void drawTextOnPath__StringPathFFPaint(JNIEnv* env, jobject, jlong canvasHandle, jstring text, jlong pathHandle, - jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintHandle) { + jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintHandle, + jlong typefaceHandle) { SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPath* path = reinterpret_cast<SkPath*>(pathHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); + TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle); + const jchar* text_ = env->GetStringChars(text, NULL); int count = env->GetStringLength(text); - TextLayout::drawTextOnPath(paint, text_, count, bidiFlags, hOffset, vOffset, - path, canvas); + doDrawTextOnPath(paint, text_, count, bidiFlags, hOffset, vOffset, + path, canvas, typeface); env->ReleaseStringChars(text, text_); } @@ -1271,9 +1324,9 @@ static JNINativeMethod gCanvasMethods[] = { (void*) SkCanvasGlue::drawPosText___CII_FPaint}, {"native_drawPosText","(JLjava/lang/String;[FJ)V", (void*) SkCanvasGlue::drawPosText__String_FPaint}, - {"native_drawTextOnPath","(J[CIIJFFIJ)V", + {"native_drawTextOnPath","(J[CIIJFFIJJ)V", (void*) SkCanvasGlue::drawTextOnPath___CIIPathFFPaint}, - {"native_drawTextOnPath","(JLjava/lang/String;JFFIJ)V", + {"native_drawTextOnPath","(JLjava/lang/String;JFFIJJ)V", (void*) SkCanvasGlue::drawTextOnPath__StringPathFFPaint}, {"freeCaches", "()V", (void*) SkCanvasGlue::freeCaches}, diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index a4337cccbcb5..2bd7a288836a 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -3,7 +3,6 @@ #include "jni.h" #include "JNIHelp.h" #include "GraphicsJNI.h" -#include "AndroidPicture.h" #include "SkCanvas.h" #include "SkDevice.h" @@ -346,17 +345,6 @@ android::TypefaceImpl* GraphicsJNI::getNativeTypeface(JNIEnv* env, jobject paint return p; } -AndroidPicture* GraphicsJNI::getNativePicture(JNIEnv* env, jobject picture) -{ - SkASSERT(env); - SkASSERT(picture); - SkASSERT(env->IsInstanceOf(picture, gPicture_class)); - jlong pictureHandle = env->GetLongField(picture, gPicture_nativeInstanceID); - AndroidPicture* p = reinterpret_cast<AndroidPicture*>(pictureHandle); - SkASSERT(p); - return p; -} - SkRegion* GraphicsJNI::getNativeRegion(JNIEnv* env, jobject region) { SkASSERT(env); diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index 2e2f920eb338..ad174f7e7a78 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -14,7 +14,6 @@ class SkBitmapRegionDecoder; class SkCanvas; class SkPaint; -class AndroidPicture; class GraphicsJNI { public: @@ -50,7 +49,6 @@ public: static SkPaint* getNativePaint(JNIEnv*, jobject paint); static android::TypefaceImpl* getNativeTypeface(JNIEnv*, jobject paint); static SkBitmap* getNativeBitmap(JNIEnv*, jobject bitmap); - static AndroidPicture* getNativePicture(JNIEnv*, jobject picture); static SkRegion* getNativeRegion(JNIEnv*, jobject region); // Given the 'native' long held by the Rasterizer.java object, return a diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp index a9360ea1ecbb..fc92d53193c0 100644 --- a/core/jni/android/graphics/MinikinUtils.cpp +++ b/core/jni/android/graphics/MinikinUtils.cpp @@ -19,6 +19,7 @@ #include <string> #include "SkPaint.h" +#include "SkPathMeasure.h" #include "minikin/Layout.h" #include "TypefaceImpl.h" #include "MinikinSkia.h" @@ -76,4 +77,20 @@ float MinikinUtils::xOffsetForTextAlign(SkPaint* paint, const Layout& layout) { return 0; } +float MinikinUtils::hOffsetForTextAlign(SkPaint* paint, const Layout& layout, const SkPath& path) { + float align = 0; + switch (paint->getTextAlign()) { + case SkPaint::kCenter_Align: + align = -0.5f; + break; + case SkPaint::kRight_Align: + align = -1; + break; + default: + return 0; + } + SkPathMeasure measure(path, false); + return align * (layout.getAdvance() - measure.getLength()); +} + } diff --git a/core/jni/android/graphics/MinikinUtils.h b/core/jni/android/graphics/MinikinUtils.h index a96c6b19eca9..b2662a15cebf 100644 --- a/core/jni/android/graphics/MinikinUtils.h +++ b/core/jni/android/graphics/MinikinUtils.h @@ -36,6 +36,7 @@ public: static float xOffsetForTextAlign(SkPaint* paint, const Layout& layout); + static float hOffsetForTextAlign(SkPaint* paint, const Layout& layout, const SkPath& path); // f is a functor of type void f(size_t start, size_t end); template <typename F> static void forFontRun(const Layout& layout, SkPaint* paint, F& f) { diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp index 0683f7386de8..bc0c25f3af1a 100644 --- a/core/jni/android/graphics/Picture.cpp +++ b/core/jni/android/graphics/Picture.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,123 +14,104 @@ * limitations under the License. */ -#include "jni.h" -#include "GraphicsJNI.h" -#include <android_runtime/AndroidRuntime.h> -#include "AndroidPicture.h" +#include "Picture.h" #include "SkCanvas.h" #include "SkStream.h" -#include "SkTemplates.h" -#include "CreateJavaOutputStreamAdaptor.h" namespace android { -class SkPictureGlue { -public: - static jlong newPicture(JNIEnv* env, jobject, jlong srcHandle) { - const AndroidPicture* src = reinterpret_cast<AndroidPicture*>(srcHandle); - return reinterpret_cast<jlong>(new AndroidPicture(src)); - } - - static jlong deserialize(JNIEnv* env, jobject, jobject jstream, - jbyteArray jstorage) { - AndroidPicture* picture = NULL; - SkStream* strm = CreateJavaInputStreamAdaptor(env, jstream, jstorage); - if (strm) { - picture = AndroidPicture::CreateFromStream(strm); - delete strm; +Picture::Picture(const Picture* src) { + if (NULL != src) { + mWidth = src->width(); + mHeight = src->height(); + if (NULL != src->mPicture.get()) { + mPicture.reset(SkRef(src->mPicture.get())); + } if (NULL != src->mRecorder.get()) { + mPicture.reset(src->makePartialCopy()); } - return reinterpret_cast<jlong>(picture); + } else { + mWidth = 0; + mHeight = 0; } +} - static void killPicture(JNIEnv* env, jobject, jlong pictureHandle) { - AndroidPicture* picture = reinterpret_cast<AndroidPicture*>(pictureHandle); - SkASSERT(picture); - delete picture; - } +SkCanvas* Picture::beginRecording(int width, int height) { + mPicture.reset(NULL); + mRecorder.reset(new SkPictureRecorder); + mWidth = width; + mHeight = height; + return mRecorder->beginRecording(width, height, NULL, 0); +} - static void draw(JNIEnv* env, jobject, jlong canvasHandle, - jlong pictureHandle) { - SkCanvas* canvas = GraphicsJNI::getNativeCanvas(canvasHandle); - AndroidPicture* picture = reinterpret_cast<AndroidPicture*>(pictureHandle); - SkASSERT(canvas); - SkASSERT(picture); - picture->draw(canvas); +void Picture::endRecording() { + if (NULL != mRecorder.get()) { + mPicture.reset(mRecorder->endRecording()); + mRecorder.reset(NULL); } +} - static jboolean serialize(JNIEnv* env, jobject, jlong pictureHandle, - jobject jstream, jbyteArray jstorage) { - AndroidPicture* picture = reinterpret_cast<AndroidPicture*>(pictureHandle); - SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage); - - if (NULL != strm) { - picture->serialize(strm); - delete strm; - return JNI_TRUE; - } - return JNI_FALSE; +int Picture::width() const { + if (NULL != mPicture.get()) { + SkASSERT(mPicture->width() == mWidth); + SkASSERT(mPicture->height() == mHeight); } - static jint getWidth(JNIEnv* env, jobject jpic) { - NPE_CHECK_RETURN_ZERO(env, jpic); - AndroidPicture* pict = GraphicsJNI::getNativePicture(env, jpic); - int width = pict->width(); - return static_cast<jint>(width); - } + return mWidth; +} - static jint getHeight(JNIEnv* env, jobject jpic) { - NPE_CHECK_RETURN_ZERO(env, jpic); - AndroidPicture* pict = GraphicsJNI::getNativePicture(env, jpic); - int height = pict->height(); - return static_cast<jint>(height); +int Picture::height() const { + if (NULL != mPicture.get()) { + SkASSERT(mPicture->width() == mWidth); + SkASSERT(mPicture->height() == mHeight); } - static jlong beginRecording(JNIEnv* env, jobject, jlong pictHandle, - jint w, jint h) { - AndroidPicture* pict = reinterpret_cast<AndroidPicture*>(pictHandle); - // beginRecording does not ref its return value, it just returns it. - SkCanvas* canvas = pict->beginRecording(w, h); - // the java side will wrap this guy in a Canvas.java, which will call - // unref in its finalizer, so we have to ref it here, so that both that - // Canvas.java and our picture can both be owners - canvas->ref(); - return reinterpret_cast<jlong>(canvas); - } + return mHeight; +} - static void endRecording(JNIEnv* env, jobject, jlong pictHandle) { - AndroidPicture* pict = reinterpret_cast<AndroidPicture*>(pictHandle); - pict->endRecording(); - } -}; +Picture* Picture::CreateFromStream(SkStream* stream) { + Picture* newPict = new Picture; -static JNINativeMethod gPictureMethods[] = { - {"getWidth", "()I", (void*) SkPictureGlue::getWidth}, - {"getHeight", "()I", (void*) SkPictureGlue::getHeight}, - {"nativeConstructor", "(J)J", (void*) SkPictureGlue::newPicture}, - {"nativeCreateFromStream", "(Ljava/io/InputStream;[B)J", (void*)SkPictureGlue::deserialize}, - {"nativeBeginRecording", "(JII)J", (void*) SkPictureGlue::beginRecording}, - {"nativeEndRecording", "(J)V", (void*) SkPictureGlue::endRecording}, - {"nativeDraw", "(JJ)V", (void*) SkPictureGlue::draw}, - {"nativeWriteToStream", "(JLjava/io/OutputStream;[B)Z", (void*)SkPictureGlue::serialize}, - {"nativeDestructor","(J)V", (void*) SkPictureGlue::killPicture} -}; + newPict->mPicture.reset(SkPicture::CreateFromStream(stream)); + if (NULL != newPict->mPicture.get()) { + newPict->mWidth = newPict->mPicture->width(); + newPict->mHeight = newPict->mPicture->height(); + } -#include <android_runtime/AndroidRuntime.h> + return newPict; +} -#define REG(env, name, array) \ - result = android::AndroidRuntime::registerNativeMethods(env, name, array, \ - SK_ARRAY_COUNT(array)); \ - if (result < 0) return result +void Picture::serialize(SkWStream* stream) const { + if (NULL != mRecorder.get()) { + SkAutoTDelete<SkPicture> tempPict(this->makePartialCopy()); + tempPict->serialize(stream); + } else if (NULL != mPicture.get()) { + mPicture->serialize(stream); + } else { + SkPicture empty; + empty.serialize(stream); + } +} -int register_android_graphics_Picture(JNIEnv* env) { - int result; +void Picture::draw(SkCanvas* canvas) { + if (NULL != mRecorder.get()) { + this->endRecording(); + SkASSERT(NULL != mPicture.get()); + } + if (NULL != mPicture.get()) { + // TODO: remove this const_cast once pictures are immutable + const_cast<SkPicture*>(mPicture.get())->draw(canvas); + } +} - REG(env, "android/graphics/Picture", gPictureMethods); +SkPicture* Picture::makePartialCopy() const { + SkASSERT(NULL != mRecorder.get()); - return result; -} + SkPictureRecorder reRecorder; + SkCanvas* canvas = reRecorder.beginRecording(mWidth, mHeight, NULL, 0); + mRecorder->partialReplay(canvas); + return reRecorder.endRecording(); } - +}; // namespace android diff --git a/core/jni/android/graphics/AndroidPicture.h b/core/jni/android/graphics/Picture.h index f434941c4381..abb04035d6fe 100644 --- a/core/jni/android/graphics/AndroidPicture.h +++ b/core/jni/android/graphics/Picture.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_PICTURE_H -#define ANDROID_PICTURE_H +#ifndef ANDROID_GRAPHICS_PICTURE_H +#define ANDROID_GRAPHICS_PICTURE_H #include "SkPicture.h" #include "SkPictureRecorder.h" @@ -28,13 +28,15 @@ class SkPictureRecorder; class SkStream; class SkWStream; +namespace android { + // Skia's SkPicture class has been split into an SkPictureRecorder // and an SkPicture. AndroidPicture recreates the functionality // of the old SkPicture interface by flip-flopping between the two // new classes. -class AndroidPicture { +class Picture { public: - explicit AndroidPicture(const AndroidPicture* src = NULL); + explicit Picture(const Picture* src = NULL); SkCanvas* beginRecording(int width, int height); @@ -44,7 +46,7 @@ public: int height() const; - static AndroidPicture* CreateFromStream(SkStream* stream); + static Picture* CreateFromStream(SkStream* stream); void serialize(SkWStream* stream) const; @@ -60,4 +62,6 @@ private: // resulting picture will have balanced saves and restores. SkPicture* makePartialCopy() const; }; -#endif // ANDROID_PICTURE_H + +}; // namespace android +#endif // ANDROID_GRAPHICS_PICTURE_H diff --git a/core/jni/android_graphics_Picture.cpp b/core/jni/android_graphics_Picture.cpp new file mode 100644 index 000000000000..f827907477f2 --- /dev/null +++ b/core/jni/android_graphics_Picture.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "jni.h" +#include "GraphicsJNI.h" +#include <android_runtime/AndroidRuntime.h> + +#include "Picture.h" + +#include "SkCanvas.h" +#include "SkStream.h" +#include "SkTemplates.h" +#include "CreateJavaOutputStreamAdaptor.h" + +namespace android { + +static jlong android_graphics_Picture_newPicture(JNIEnv* env, jobject, jlong srcHandle) { + const Picture* src = reinterpret_cast<Picture*>(srcHandle); + return reinterpret_cast<jlong>(new Picture(src)); +} + +static jlong android_graphics_Picture_deserialize(JNIEnv* env, jobject, jobject jstream, + jbyteArray jstorage) { + Picture* picture = NULL; + SkStream* strm = CreateJavaInputStreamAdaptor(env, jstream, jstorage); + if (strm) { + picture = Picture::CreateFromStream(strm); + delete strm; + } + return reinterpret_cast<jlong>(picture); +} + +static void android_graphics_Picture_killPicture(JNIEnv* env, jobject, jlong pictureHandle) { + Picture* picture = reinterpret_cast<Picture*>(pictureHandle); + SkASSERT(picture); + delete picture; +} + +static void android_graphics_Picture_draw(JNIEnv* env, jobject, jlong canvasHandle, + jlong pictureHandle) { + SkCanvas* canvas = GraphicsJNI::getNativeCanvas(canvasHandle); + Picture* picture = reinterpret_cast<Picture*>(pictureHandle); + SkASSERT(canvas); + SkASSERT(picture); + picture->draw(canvas); +} + +static jboolean android_graphics_Picture_serialize(JNIEnv* env, jobject, jlong pictureHandle, + jobject jstream, jbyteArray jstorage) { + Picture* picture = reinterpret_cast<Picture*>(pictureHandle); + SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage); + + if (NULL != strm) { + picture->serialize(strm); + delete strm; + return JNI_TRUE; + } + return JNI_FALSE; +} + +static jint android_graphics_Picture_getWidth(JNIEnv* env, jobject, jlong pictureHandle) { + Picture* pict = reinterpret_cast<Picture*>(pictureHandle); + return static_cast<jint>(pict->width()); +} + +static jint android_graphics_Picture_getHeight(JNIEnv* env, jobject, jlong pictureHandle) { + Picture* pict = reinterpret_cast<Picture*>(pictureHandle); + return static_cast<jint>(pict->height()); +} + +static jlong android_graphics_Picture_beginRecording(JNIEnv* env, jobject, jlong pictHandle, + jint w, jint h) { + Picture* pict = reinterpret_cast<Picture*>(pictHandle); + // beginRecording does not ref its return value, it just returns it. + SkCanvas* canvas = pict->beginRecording(w, h); + // the java side will wrap this guy in a Canvas.java, which will call + // unref in its finalizer, so we have to ref it here, so that both that + // Canvas.java and our picture can both be owners + canvas->ref(); + return reinterpret_cast<jlong>(canvas); +} + +static void android_graphics_Picture_endRecording(JNIEnv* env, jobject, jlong pictHandle) { + Picture* pict = reinterpret_cast<Picture*>(pictHandle); + pict->endRecording(); +} + +static JNINativeMethod gMethods[] = { + {"nativeGetWidth", "(J)I", (void*) android_graphics_Picture_getWidth}, + {"nativeGetHeight", "(J)I", (void*) android_graphics_Picture_getHeight}, + {"nativeConstructor", "(J)J", (void*) android_graphics_Picture_newPicture}, + {"nativeCreateFromStream", "(Ljava/io/InputStream;[B)J", (void*)android_graphics_Picture_deserialize}, + {"nativeBeginRecording", "(JII)J", (void*) android_graphics_Picture_beginRecording}, + {"nativeEndRecording", "(J)V", (void*) android_graphics_Picture_endRecording}, + {"nativeDraw", "(JJ)V", (void*) android_graphics_Picture_draw}, + {"nativeWriteToStream", "(JLjava/io/OutputStream;[B)Z", (void*)android_graphics_Picture_serialize}, + {"nativeDestructor","(J)V", (void*) android_graphics_Picture_killPicture} +}; + +int register_android_graphics_Picture(JNIEnv* env) { + return AndroidRuntime::registerNativeMethods(env, "android/graphics/Picture", gMethods, NELEM(gMethods)); +} + +}; // namespace android diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp index 4a6e117a051b..de00e594ccda 100644 --- a/core/jni/android_view_GLES20Canvas.cpp +++ b/core/jni/android_view_GLES20Canvas.cpp @@ -669,8 +669,48 @@ static void renderText(OpenGLRenderer* renderer, const jchar* text, int count, #endif } +#ifdef USE_MINIKIN +class RenderTextOnPathFunctor { +public: + RenderTextOnPathFunctor(const Layout& layout, OpenGLRenderer* renderer, float hOffset, + float vOffset, SkPaint* paint, SkPath* path) + : layout(layout), renderer(renderer), hOffset(hOffset), vOffset(vOffset), + paint(paint), path(path) { + } + void operator()(size_t start, size_t end) { + uint16_t glyphs[1]; + for (size_t i = start; i < end; i++) { + glyphs[0] = layout.getGlyphId(i); + float x = hOffset + layout.getX(i); + float y = vOffset + layout.getY(i); + renderer->drawTextOnPath((const char*) glyphs, sizeof(glyphs), 1, path, x, y, paint); + } + } +private: + const Layout& layout; + OpenGLRenderer* renderer; + float hOffset; + float vOffset; + SkPaint* paint; + SkPath* path; +}; +#endif + static void renderTextOnPath(OpenGLRenderer* renderer, const jchar* text, int count, - SkPath* path, jfloat hOffset, jfloat vOffset, int bidiFlags, SkPaint* paint) { + SkPath* path, jfloat hOffset, jfloat vOffset, int bidiFlags, SkPaint* paint, + TypefaceImpl* typeface) { +#ifdef USE_MINIKIN + Layout layout; + std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface); + layout.doLayout(text, 0, count, count, css); + hOffset += MinikinUtils::hOffsetForTextAlign(paint, layout, *path); + SkPaint::Align align = paint->getTextAlign(); + paint->setTextAlign(SkPaint::kLeft_Align); + + RenderTextOnPathFunctor f(layout, renderer, hOffset, vOffset, paint, path); + MinikinUtils::forFontRun(layout, paint, f); + paint->setTextAlign(align); +#else sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint, text, 0, count, count, bidiFlags); if (value == NULL) { @@ -681,6 +721,7 @@ static void renderTextOnPath(OpenGLRenderer* renderer, const jchar* text, int co int bytesCount = glyphsCount * sizeof(jchar); renderer->drawTextOnPath((const char*) glyphs, bytesCount, glyphsCount, path, hOffset, vOffset, paint); +#endif } static void renderTextRun(OpenGLRenderer* renderer, const jchar* text, @@ -739,27 +780,31 @@ static void android_view_GLES20Canvas_drawText(JNIEnv* env, jobject clazz, static void android_view_GLES20Canvas_drawTextArrayOnPath(JNIEnv* env, jobject clazz, jlong rendererPtr, jcharArray text, jint index, jint count, - jlong pathPtr, jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintPtr) { + jlong pathPtr, jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintPtr, + jlong typefacePtr) { OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); jchar* textArray = env->GetCharArrayElements(text, NULL); SkPath* path = reinterpret_cast<SkPath*>(pathPtr); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); + TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefacePtr); renderTextOnPath(renderer, textArray + index, count, path, - hOffset, vOffset, bidiFlags, paint); + hOffset, vOffset, bidiFlags, paint, typeface); env->ReleaseCharArrayElements(text, textArray, JNI_ABORT); } static void android_view_GLES20Canvas_drawTextOnPath(JNIEnv* env, jobject clazz, jlong rendererPtr, jstring text, jint start, jint end, - jlong pathPtr, jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintPtr) { + jlong pathPtr, jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintPtr, + jlong typefacePtr) { OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); const jchar* textArray = env->GetStringChars(text, NULL); SkPath* path = reinterpret_cast<SkPath*>(pathPtr); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); + TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefacePtr); renderTextOnPath(renderer, textArray + start, end - start, path, - hOffset, vOffset, bidiFlags, paint); + hOffset, vOffset, bidiFlags, paint, typeface); env->ReleaseStringChars(text, textArray); } @@ -986,8 +1031,8 @@ static JNINativeMethod gMethods[] = { { "nDrawText", "(JLjava/lang/String;IIFFIJJ)V", (void*) android_view_GLES20Canvas_drawText }, - { "nDrawTextOnPath", "(J[CIIJFFIJ)V", (void*) android_view_GLES20Canvas_drawTextArrayOnPath }, - { "nDrawTextOnPath", "(JLjava/lang/String;IIJFFIJ)V", + { "nDrawTextOnPath", "(J[CIIJFFIJJ)V", (void*) android_view_GLES20Canvas_drawTextArrayOnPath }, + { "nDrawTextOnPath", "(JLjava/lang/String;IIJFFIJJ)V", (void*) android_view_GLES20Canvas_drawTextOnPath }, { "nDrawTextRun", "(J[CIIIIFFZJJ)V", (void*) android_view_GLES20Canvas_drawTextRunArray }, diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index c0d522191147..cfc8eb89fceb 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -61,6 +61,8 @@ static struct { jfieldID xDpi; jfieldID yDpi; jfieldID secure; + jfieldID appVsyncOffsetNanos; + jfieldID presentationDeadlineNanos; } gPhysicalDisplayInfoClassInfo; static struct { @@ -392,6 +394,10 @@ static jobjectArray nativeGetDisplayConfigs(JNIEnv* env, jclass clazz, env->SetFloatField(infoObj, gPhysicalDisplayInfoClassInfo.xDpi, info.xdpi); env->SetFloatField(infoObj, gPhysicalDisplayInfoClassInfo.yDpi, info.ydpi); env->SetBooleanField(infoObj, gPhysicalDisplayInfoClassInfo.secure, info.secure); + env->SetLongField(infoObj, gPhysicalDisplayInfoClassInfo.appVsyncOffsetNanos, + info.appVsyncOffset); + env->SetLongField(infoObj, gPhysicalDisplayInfoClassInfo.presentationDeadlineNanos, + info.presentationDeadline); env->SetObjectArrayElement(configArray, static_cast<jsize>(c), infoObj); env->DeleteLocalRef(infoObj); } @@ -648,6 +654,10 @@ int register_android_view_SurfaceControl(JNIEnv* env) gPhysicalDisplayInfoClassInfo.xDpi = env->GetFieldID(clazz, "xDpi", "F"); gPhysicalDisplayInfoClassInfo.yDpi = env->GetFieldID(clazz, "yDpi", "F"); gPhysicalDisplayInfoClassInfo.secure = env->GetFieldID(clazz, "secure", "Z"); + gPhysicalDisplayInfoClassInfo.appVsyncOffsetNanos = env->GetFieldID(clazz, + "appVsyncOffsetNanos", "J"); + gPhysicalDisplayInfoClassInfo.presentationDeadlineNanos = env->GetFieldID(clazz, + "presentationDeadlineNanos", "J"); jclass rectClazz = env->FindClass("android/graphics/Rect"); gRectClassInfo.bottom = env->GetFieldID(rectClazz, "bottom", "I"); diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index 815c4a755f47..2b94b65d53fe 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -49,6 +49,14 @@ using namespace android::uirenderer::renderthread; static jmethodID gRunnableMethod; +static JNIEnv* getenv(JavaVM* vm) { + JNIEnv* env; + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm); + } + return env; +} + class JavaTask : public RenderTask { public: JavaTask(JNIEnv* env, jobject jrunnable) { @@ -57,20 +65,13 @@ public: } virtual void run() { - env()->CallVoidMethod(mRunnable, gRunnableMethod); - env()->DeleteGlobalRef(mRunnable); + JNIEnv* env = getenv(mVm); + env->CallVoidMethod(mRunnable, gRunnableMethod); + env->DeleteGlobalRef(mRunnable); delete this; }; private: - JNIEnv* env() { - JNIEnv* env; - if (mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { - return 0; - } - return env; - } - JavaVM* mVm; jobject mRunnable; }; @@ -122,12 +123,34 @@ private: std::vector<OnFinishedEvent> mOnFinishedEvents; }; -class RootRenderNode : public RenderNode, public AnimationHook { +class RenderingException : public MessageHandler { public: - RootRenderNode() : RenderNode() { + RenderingException(JavaVM* vm, const std::string& message) + : mVm(vm) + , mMessage(message) { + } + + virtual void handleMessage(const Message&) { + throwException(mVm, mMessage); + } + + static void throwException(JavaVM* vm, const std::string& message) { + JNIEnv* env = getenv(vm); + jniThrowException(env, "java/lang/IllegalStateException", message.c_str()); + } + +private: + JavaVM* mVm; + std::string mMessage; +}; + +class RootRenderNode : public RenderNode, AnimationHook, ErrorHandler { +public: + RootRenderNode(JNIEnv* env) : RenderNode() { mLooper = Looper::getForThread(); LOG_ALWAYS_FATAL_IF(!mLooper.get(), "Must create RootRenderNode on a thread with a looper!"); + env->GetJavaVM(&mVm); } virtual ~RootRenderNode() {} @@ -137,10 +160,16 @@ public: mOnFinishedEvents.push_back(event); } + virtual void onError(const std::string& message) { + mLooper->sendMessage(new RenderingException(mVm, message), 0); + } + virtual void prepareTree(TreeInfo& info) { info.animationHook = this; + info.errorHandler = this; RenderNode::prepareTree(info); info.animationHook = NULL; + info.errorHandler = NULL; // post all the finished stuff if (mOnFinishedEvents.size()) { @@ -160,6 +189,7 @@ protected: private: sp<Looper> mLooper; std::vector<OnFinishedEvent> mOnFinishedEvents; + JavaVM* mVm; }; static void android_view_ThreadedRenderer_setAtlas(JNIEnv* env, jobject clazz, @@ -178,7 +208,7 @@ static void android_view_ThreadedRenderer_setAtlas(JNIEnv* env, jobject clazz, } static jlong android_view_ThreadedRenderer_createRootRenderNode(JNIEnv* env, jobject clazz) { - RootRenderNode* node = new RootRenderNode(); + RootRenderNode* node = new RootRenderNode(env); node->incStrong(0); node->setName("RootRenderNode"); return reinterpret_cast<jlong>(node); diff --git a/core/res/res/drawable-hdpi/ic_corp_badge.png b/core/res/res/drawable-hdpi/ic_corp_badge.png Binary files differnew file mode 100644 index 000000000000..c79ce92f98b0 --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_corp_badge.png diff --git a/core/res/res/drawable-hdpi/ic_corp_icon_badge.png b/core/res/res/drawable-hdpi/ic_corp_icon_badge.png Binary files differnew file mode 100644 index 000000000000..0059e091cd1d --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_corp_icon_badge.png diff --git a/core/res/res/drawable-mdpi/ic_corp_badge.png b/core/res/res/drawable-mdpi/ic_corp_badge.png Binary files differnew file mode 100644 index 000000000000..c1447fe9fb5e --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_corp_badge.png diff --git a/core/res/res/drawable-mdpi/ic_corp_icon_badge.png b/core/res/res/drawable-mdpi/ic_corp_icon_badge.png Binary files differnew file mode 100644 index 000000000000..5ff8c5d27733 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_corp_icon_badge.png diff --git a/core/res/res/drawable-xhdpi/ic_corp_badge.png b/core/res/res/drawable-xhdpi/ic_corp_badge.png Binary files differnew file mode 100644 index 000000000000..2d3d748f772b --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_corp_badge.png diff --git a/core/res/res/drawable-xhdpi/ic_corp_icon_badge.png b/core/res/res/drawable-xhdpi/ic_corp_icon_badge.png Binary files differnew file mode 100644 index 000000000000..dc5716d439d6 --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_corp_icon_badge.png diff --git a/core/res/res/drawable-xxhdpi/ic_corp_badge.png b/core/res/res/drawable-xxhdpi/ic_corp_badge.png Binary files differnew file mode 100644 index 000000000000..430e63b415fe --- /dev/null +++ b/core/res/res/drawable-xxhdpi/ic_corp_badge.png diff --git a/core/res/res/drawable-xxhdpi/ic_corp_icon_badge.png b/core/res/res/drawable-xxhdpi/ic_corp_icon_badge.png Binary files differnew file mode 100644 index 000000000000..cc00dd885b05 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/ic_corp_icon_badge.png diff --git a/core/res/res/drawable/ic_corp_badge.xml b/core/res/res/drawable/ic_corp_badge.xml deleted file mode 100644 index 532571245d55..000000000000 --- a/core/res/res/drawable/ic_corp_badge.xml +++ /dev/null @@ -1,34 +0,0 @@ -<!-- -Copyright (C) 2014 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" > - <size - android:width="19.0dp" - android:height="19.0dp"/> - - <viewport - android:viewportWidth="19.0" - android:viewportHeight="19.0"/> - - <path - android:pathData="M9.5,9.5m-9.5,0.0a9.5,9.5 0.0,1.0 1.0,19.0 0.0a9.5,9.5 0.0,1.0 1.0,-19.0 0.0" - android:fill="#FF5722"/> - <path - android:pathData="M12.667,7.125l-1.583,0.0L11.084,6.333l-0.792,-0.792L8.708,5.5410004L7.917,6.333l0.0,0.792L6.333,7.125c-0.438,0.0 -0.788,0.354 -0.788,0.792l-0.004,4.354c0.0,0.438 0.354,0.792 0.792,0.792l6.333,0.0c0.438,0.0 0.792,-0.354 0.792,-0.792L13.458,7.917C13.458,7.479 13.104,7.125 12.667,7.125zM10.094,10.687L8.906,10.687L8.906,9.5l1.188,0.0L10.094,10.687zM10.292,7.125L8.708,7.125L8.708,6.333l1.583,0.0L10.291,7.125z" - android:fill="#FFFFFF"/> - <path - android:pathData="M4.75,4.75 h9.5 v9.5 h-9.5z" - android:fill="#00000000"/> -</vector> diff --git a/core/res/res/drawable/ic_corp_icon_badge.xml b/core/res/res/drawable/ic_corp_icon_badge.xml deleted file mode 100644 index 7bfab4c55a0d..000000000000 --- a/core/res/res/drawable/ic_corp_icon_badge.xml +++ /dev/null @@ -1,40 +0,0 @@ -<!-- -Copyright (C) 2014 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" > - <size - android:width="64.0dp" - android:height="64.0dp"/> - - <viewport - android:viewportWidth="64.0" - android:viewportHeight="64.0"/> - - <path - android:fill="#FF000000" - android:pathData="M49.062,50.0m-14.0,0.0a14.0,14.0 0.0,1.0 1.0,28.0 0.0a14.0,14.0 0.0,1.0 1.0,-28.0 0.0"/> - <path - android:fill="#FF000000" - android:pathData="M49.0,49.5m-14.0,0.0a14.0,14.0 0.0,1.0 1.0,28.0 0.0a14.0,14.0 0.0,1.0 1.0,-28.0 0.0"/> - <path - android:pathData="M49.0,49.0m-14.0,0.0a14.0,14.0 0.0,1.0 1.0,28.0 0.0a14.0,14.0 0.0,1.0 1.0,-28.0 0.0" - android:fill="#FF5722"/> - <path - android:pathData="M53.667,45.5l-2.333,0.0l0.0,-1.167l-1.167,-1.167l-2.333,0.0l-1.167,1.167L46.667,45.5l-2.333,0.0c-0.645,0.0 -1.161,0.522 -1.161,1.167l-0.006,6.417c0.0,0.645 0.522,1.167 1.167,1.167l9.333,0.0c0.645,0.0 1.167,-0.522 1.167,-1.167l0.0,-6.417C54.833,46.022 54.311,45.5 53.667,45.5zM49.875,50.75l-1.75,0.0L48.125,49.0l1.75,0.0L49.875,50.75zM50.167,45.5l-2.333,0.0l0.0,-1.167l2.333,0.0L50.167,45.5z" - android:fill="#FFFFFF"/> - <path - android:pathData="M42.0,42.0 h14.0 v14.0 h-14.0z" - android:fill="#00000000"/> -</vector> diff --git a/core/res/res/drawable/view_accessibility_focused.xml b/core/res/res/drawable/view_accessibility_focused.xml index 0da9a81bd8fb..68e3f1ecb97f 100644 --- a/core/res/res/drawable/view_accessibility_focused.xml +++ b/core/res/res/drawable/view_accessibility_focused.xml @@ -17,9 +17,11 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" > <stroke - android:width="2dp" - android:color="@color/accessibility_focus_highlight" /> + android:width="4dp" + android:color="@color/accessibility_focus_highlight" + android:dashWidth="4dp" + android:dashGap="2dp" /> - <corners android:radius="2dp"/> + <corners android:radius="2dp" /> </shape> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 814d8fc0c800..a8735cb21f5a 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -873,17 +873,33 @@ <!-- The name of the logical parent of the activity as it appears in the manifest. --> <attr name="parentActivityName" format="string" /> - <!-- Define an activity that will persist across reboots. If such an activity is in the - Recents list when the device is shut off it will appear in the Recents list when - the device is next powered on. To be persisted all activities in the task from the - root activity up to the last activity before a <em>break</em> must be declared with - the persistable attribute. A <em>break</em> is the first activity after the root - started with Intent.FLAG_CLEAR_TASK_WHEN_RESET. - - <p>Activities that are declared with the persistable attribute will be provided with a - forced-persistable Bundle in onCreate() and onSavedInstanceState(), and must only - be passed a persistable Bundle in their Intent.extras. --> - <attr name="persistable" format="boolean" /> + <!-- Define how an activity persist across reboots. Activities defined as "never" will not + be persisted. Those defined as "always" will be persisted. Those defined as "taskOnly" + will persist the root activity of the task only. See below for more detail as to + what gets persisted. --> + <attr name="persistableMode"> + <!-- The default. If this activity forms the root of a task then that task will be + persisted across reboots but only the launching intent will be used. All + activities above this activity in the task will not be persisted. In addition + this activity will not be passed a PersistableBundle into which it could have + stored its state. --> + <enum name="persistRootOnly" value="0" /> + <!-- If this activity forms the root of a task then that task will not be persisted + across reboots --> + <enum name="doNotPersist" value="1" /> + <!-- If this activity forms the root of a task then the task and this activity will + be persisted across reboots. If the activity above this activity is also + tagged with the attribute <code>"persist"</code> then it will be persisted as well. + And so on up the task stack until either an activity without the + <code>persistableMode="persistAcrossReboots"</code> attribute or one that was launched + with the flag Intent.FLAG_CLEAR_TASK_WHEN_RESET is encountered. + + <p>Activities that are declared with the persistAcrossReboots attribute will be + provided with a PersistableBundle in onSavedInstanceState(), These activities may + use this PeristableBundle to save their state. Then, following a reboot, that + PersistableBundle will be provided back to the activity in its onCreate() method. --> + <enum name="persistAcrossReboots" value="2" /> + </attr> <!-- This attribute specifies that an activity shall become the root activity of a new task each time it is launched. Using this attribute permits the user to @@ -1623,7 +1639,7 @@ <!-- @hide This broacast receiver will only receive broadcasts for the primary user. Can only be used with receivers. --> <attr name="primaryUserOnly" format="boolean" /> - <attr name="persistable" /> + <attr name="persistableMode" /> <attr name="allowEmbedded" /> <attr name="documentLaunchMode" /> <attr name="maxRecents" /> diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 9f6c7ad81089..f3045144718c 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -143,7 +143,7 @@ <color name="keyguard_avatar_nick_color">#ffffffff</color> <color name="keyguard_avatar_frame_pressed_color">#ff35b5e5</color> - <color name="accessibility_focus_highlight">#80ffff00</color> + <color name="accessibility_focus_highlight">#bf39b500</color> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 1580d690f630..c5ce2292bde5 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2136,7 +2136,7 @@ <public type="attr" name="colorControlActivated" /> <public type="attr" name="colorButtonNormal" /> <public type="attr" name="colorControlHighlight" /> - <public type="attr" name="persistable" /> + <public type="attr" name="persistableMode" /> <public type="attr" name="titleTextAppearance" /> <public type="attr" name="subtitleTextAppearance" /> <public type="attr" name="slideEdge" /> diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java index 7251e7c3fa77..7f41ac1c5328 100644 --- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java +++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java @@ -305,11 +305,9 @@ public class PackageManagerTests extends AndroidTestCase { private PackageParser.Package parsePackage(Uri packageURI) throws PackageParserException { final String archiveFilePath = packageURI.getPath(); - PackageParser packageParser = new PackageParser(archiveFilePath); + PackageParser packageParser = new PackageParser(); File sourceFile = new File(archiveFilePath); - DisplayMetrics metrics = new DisplayMetrics(); - metrics.setToDefaults(); - PackageParser.Package pkg = packageParser.parseMonolithicPackage(sourceFile, metrics, 0); + PackageParser.Package pkg = packageParser.parseMonolithicPackage(sourceFile, 0); packageParser = null; return pkg; } diff --git a/data/fonts/fallback_fonts.xml b/data/fonts/fallback_fonts.xml index 1eaae657e7eb..37e9b157e750 100644 --- a/data/fonts/fallback_fonts.xml +++ b/data/fonts/fallback_fonts.xml @@ -219,6 +219,21 @@ </family> <family> <fileset> + <file>NotoSansCherokee-Regular.ttf</file> + </fileset> + </family> + <family> + <fileset> + <file>NotoSansCanadianAboriginal-Regular.ttf</file> + </fileset> + </family> + <family> + <fileset> + <file>NotoSansYi-Regular.ttf</file> + </fileset> + </family> + <family> + <fileset> <file lang="zh-CN">NotoSansHans-Regular.otf</file> </fileset> </family> diff --git a/docs/html/google/play-services/games.jd b/docs/html/google/play-services/games.jd index 94f6715a9469..a73f688ec04f 100644 --- a/docs/html/google/play-services/games.jd +++ b/docs/html/google/play-services/games.jd @@ -1,4 +1,5 @@ page.title=Google Play Game Services +page.tags="games" header.hide=1 @jd:body diff --git a/docs/html/guide/topics/manifest/activity-element.jd b/docs/html/guide/topics/manifest/activity-element.jd index b648d482325c..c4d5083175d8 100644 --- a/docs/html/guide/topics/manifest/activity-element.jd +++ b/docs/html/guide/topics/manifest/activity-element.jd @@ -5,7 +5,8 @@ parent.link=manifest-intro.html <dl class="xml"> <dt>syntax:</dt> -<dd><pre class="stx"><activity android:<a href="#reparent">allowTaskReparenting</a>=["true" | "false"] +<dd><pre class="stx"><activity android:<a href="#embedded">allowEmbedded</a>=["true" | "false"] + android:<a href="#reparent">allowTaskReparenting</a>=["true" | "false"] android:<a href="#always">alwaysRetainTaskState</a>=["true" | "false"] android:<a href="#clear">clearTaskOnLaunch</a>=["true" | "false"] android:<a href="#config">configChanges</a>=["mcc", "mnc", "locale", @@ -62,6 +63,17 @@ by the system and will never be run. <dt>attributes:</dt> <dd><dl class="attr"> +<dt><a name="embedded"></a>{@code android:allowEmbedded}</dt> +<dd> + Indicate that the activity can be launched as the embedded child of another + activity. Particularly in the case where the child lives in a container + such as a Display owned by another activity. For example, activities + that are used for Wear custom notifications must declare this so + Wear can display the activity in it's context stream, which resides + in another process. + + <p>The default value of this attribute is <code>false</code>. +</dd> <dt><a name="reparent"></a>{@code android:allowTaskReparenting}</dt> <dd>Whether or not the activity can move from the task that started it to the task it has an affinity for when that task is next brought to the diff --git a/docs/html/preview/api-overview.jd b/docs/html/preview/api-overview.jd index 40618a34fb35..2fa029fb9029 100644 --- a/docs/html/preview/api-overview.jd +++ b/docs/html/preview/api-overview.jd @@ -15,7 +15,9 @@ sdk.platform.apiLevel=20 <ol id="toc44" class="hide-nested"> <li><a href="#Behaviors">Important Behavior Changes</a> <ol> + <li><a href="#ART">New Android Runtime (ART)</a></li> <li><a href="#BehaviorNotifications">If your app implements notifications...</a></li> + <li><a href="#BehaviorMediaControl">If your app uses RemoteControlClient...</a></li> <li><a href="#BehaviorFullscreen">If your app uses fullScreenIntent...</a></li> <li><a href="#BehaviorGetRecentTasks">If your app uses ActivityManager.getRecentTasks()...</a></li> </ol> @@ -23,9 +25,10 @@ sdk.platform.apiLevel=20 <li><a href="#UI">User Interface</a> <ol> <li><a href="#MaterialDesign">Material design support</a></li> + <li><a href="#DoNotDisturb">Do Not Disturb mode</a></li> <li><a href="#LockscreenNotifications">Lockscreen notifications</a></li> <li><a href="#NotificationsMetadata">Notifications metadata</a></li> - <li><a href="#Recents">Concurrent documents and activities in Recents screen</a></li> + <li><a href="#Recents">Concurrent documents and activities in the Recents screen</a></li> <li><a href="#WebView">WebView updates</a></li> </ol> </li> @@ -41,7 +44,7 @@ sdk.platform.apiLevel=20 </li> <li><a href="#Multimedia">Multimedia</a> <ol> - <li><a href="#Camera-v2">Camera V2</a></li> + <li><a href="#Camera-v2">Camera v2 API</a></li> <li><a href="#AudioPlayback">Audio playback</a></li> <li><a href="#MediaPlaybackControl">Media playback control</a></li> </ol> @@ -55,7 +58,7 @@ sdk.platform.apiLevel=20 <ol> <li><a href="#Multinetwork">Dynamic network selection and seamless handoff</a></li> <li><a href="#BluetoothBroadcasting">Bluetooth broadcasting</a></li> - <li><a href="#NFCEnhancements">NFC enhancements for payments</a></li> + <li><a href="#NFCEnhancements">NFC enhancements</a></li> </ol> </li> <li><a href="#Power">Power Efficiency</a> @@ -71,7 +74,7 @@ sdk.platform.apiLevel=20 </li> <li><a href="#Printing">Printing Framework</a> <ol> - <li><a href="#PDFRender">PDF rendering</a></li> + <li><a href="#PDFRender">Render PDF as bitmap</a></li> </ol> </li> <li><a href="#TestingA11y">Testing & Accessibility</a> @@ -96,73 +99,130 @@ Differences Report »</a> </li> </div> </div> -<p>L is an upcoming release for the Android platform -that offers new features for users and app developers. This document provides -an introduction to the most notable new APIs.</p> +<p>The L Developer Preview gives you an advance look at the upcoming release for +the Android platform, +which offers new features for users and app developers. This document provides +an introduction to the most notable APIs.</p> -<p>L is currently available as a <strong>developer preview</strong> intended -for early adopters and testers. If you are interested in influencing the -direction of the Android framework, -<a href="{@docRoot}preview/setup-sdk.html">give the L Developer Preview a -try</a> and send us your feedback!</p> +<p>The L Developer Preview is intended for <strong>developer early adopters</strong> and +<strong>testers</strong>. If you are interested in influencing the direction of the +Android framework, <a href="{@docRoot}preview/setup-sdk.html">give the L +Developer Preview a try</a> and send us your feedback!</p> -<p class="caution"><strong>Caution:</strong>You should not publish apps -using L Developer Preview to the Google Play store.</p> +<p class="caution"><strong>Caution:</strong> Do not not publish apps +that use the L Developer Preview to the Google Play store.</p> + +<p class="note"><strong>Note:</strong> This document often refers to classes and +methods that do not yet have reference material available on <a +href="{@docRoot}">developer.android.com</a>. These API elements are +formatted in {@code code style} in this document (without hyperlinks). For the +preliminary API documentation for these elements, download the <a +href="{@docRoot}preview/l-developer-preview-reference.zip">preview +reference</a>.</p> <h2 id="Behaviors">Important Behavior Changes</h2> <p>If you have previously published an app for Android, be aware that your app - might be affected by changes in L.</p> + might be affected by changes in the upcoming release.</p> + +<h3 id="ART">New Android Runtime (ART)</h3> + +<p>The 4.4 release introduced a new, experimental Android runtime, ART. Under +4.4, ART was optional, and the default runtime remained Dalvik. With the L Developer Preview, ART is +now the default runtime.</p> + +<p>For an overview of ART's new features, see +<a href="https://source.android.com/devices/tech/dalvik/art.html">Introducing +ART</a>. Some of the major new features are:</p> + +<ul> + <li>Ahead-of-Time (AOT) compilation</li> + <li>Improved garbage collection (GC)</li> + <li>Improved debugging support</li> +</ul> + +<p>Most Android apps should just work without change under ART. However, some +techniques that work on Dalvik do not work on ART. For information about the +most important issues, see +<a href="{@docRoot}guide/practices/verifying-apps-art.html">Verifying App +Behavior on the Android Runtime (ART)</a>. Pay particular attention if:</p> + +<ul> + <li>Your app uses Java Native Interface (JNI) to run C/C++ code.</li> + <li>You use development tools that generate non-standard code (such as some + obfuscators).</li> + <li>You use techniques that are incompatible with compacting garbage + collection. (ART does not currently implement compacting GC, but + compacting GC is under development in the Android Open-Source + Project.)</li> +</ul> <h3 id="BehaviorNotifications">If your app implements notifications...</h3> -<p>Notifications will be drawn with dark text atop white (or very light) +<p>Notifications are drawn with dark text atop white (or very light) backgrounds to match the new material design widgets. Make sure that all your -notifications look right with the new color scheme. You should remove or update -assets and text styles that involve color. The system will automatically invert -action icons in notifications. Use -{@code android.app.Notification.Builder.setColor()} to set an accent color -in a circle behind your {@code Notification.icon} image.</p> - -<p>The system will ignore all non-alpha channels in action icons and the main -notification icon, so you should assume that these icons will be alpha-only. -</p> +notifications look right with the new color scheme:</p> + +<ul> + + <li>Update or remove assets that involve color.</li> + + <li>The system automatically inverts action icons in notifications. Use + {@code android.app.Notification.Builder.setColor()} to set an accent color + in a circle behind your {@link android.app.Notification#icon} image.</li> + + <li>The system ignores all non-alpha channels in action icons and the main + notification icon. You should assume that these icons are alpha-only.</li> + +</ul> <p>If you are currently adding sounds and vibrations to your notifications by using the {@link android.media.Ringtone}, {@link android.media.MediaPlayer}, -or {@link android.os.Vibrator} classes, make sure to remove this code so that -the system can present notifications correctly in Do not disturb mode. You -should use the {@link android.app.Notification.Builder} methods instead to add -sounds and vibration. -</p> +or {@link android.os.Vibrator} classes, remove this code so that +the system can present notifications correctly in <a href="#DoNotDisturb">Do Not Disturb</a> mode. +Instead, use the {@link android.app.Notification.Builder} methods instead to add +sounds and vibration.</p> <h3 id="BehaviorMediaControl">If your app uses RemoteControlClient...</h3> -<p>Lockscreens in L will not show transport controls for your +<p>Lockscreens in the L Developer Preview do not show transport controls for your {@link android.media.RemoteControlClient}. Instead, your app can provide media playback control from the lockscreen through a media notification. This gives your app more control over the presentation of media buttons, while providing a consistent experience for users across the lockscreen and unlocked device.</p> -<p>You must call {@code Notification.Builder.setVisibility(Notification.VISIBILITY_PUBLIC)} to mark your media notification as safe to reveal, even when the lockscreen is secured -with a PIN, pattern, or password.</p> +<p>Call {@code +Notification.Builder.setVisibility(Notification.VISIBILITY_PUBLIC)} to mark a +notification as safe to display on the lockscreen (even when the lockscreen is +secured with a PIN, pattern, or password). For more information, see +<a href="#LockscreenNotifications">Lockscreen Notifications</a>.</p> <h3 id="BehaviorFullscreen">If your app uses fullScreenIntent...</h3> <p>Notifications now appear in a small floating window if all these conditions -are met: the user’s activity is in fullscreen mode, the screen is on, and the -device is unlocked. If your app implements fullscreen activities, make sure that +are met:</p> + +<ul> + <li>The user’s activity is in fullscreen mode,</li> + <li>The screen is on, and</li> + <li>The device is unlocked</li> +</ul> + +<p>If your app implements fullscreen activities, make sure that these heads-up notifications are presented correctly.</p> <h3 id="BehaviorGetRecentTasks">If your app uses ActivityManager.getRecentTasks()...</h3> -<p>With the introduction of the new document tasks feature in L (see below), -the {@code android.app.ActivityManager.getRecentTasks()} method is now -deprecated to improve user privacy. For backwards -compatibility, it will still return a small subset of its data including the +<p>With the introduction of the new <em>concurrent documents and activities tasks</em> feature in the upcoming +release (see <a href="#Recents">Concurrent documents and activities in Recents +screen</a> below), +the {@link android.app.ActivityManager#getRecentTasks +ActivityManager.getRecentTasks()} method is now +deprecated to improve user privacy. For backward +compatibility, this method still returns a small subset of its data, including the calling application’s own tasks and possibly some other non-sensitive tasks -such as home. If your app is using this method to retrieve its own tasks, +(such as Home). If your app is using this method to retrieve its own tasks, use {@code android.app.ActivityManager.getAppTasks()} instead to retrieve that information.</p> @@ -170,11 +230,15 @@ information.</p> <h3 id="MaterialDesign">Material design support</h3> -<p>The L Developer Preview adds support for the material design style. You can create -material design apps that are visually dynamic and have UI element transitions -which feel natural and delightful to users. This support includes:</p> + +<p>The upcoming release adds support for Android's new <em>material</em> design +style. You can create +apps with material design that are visually dynamic and have UI element transitions +that feel natural to users. This support includes:</p> + <ul> - <li>The Material theme</li> + + <li>The material theme</li> <li>View shadows</li> <li>The {@code RecyclerView} widget</li> <li>Drawable animation and styling effects</li> @@ -182,8 +246,9 @@ which feel natural and delightful to users. This support includes:</p> <li>Animators for view properties based on the state of a view</li> <li>Customizable UI widgets and app bars with color palettes that you control</li> </ul> + <p>To learn more about adding material design functionality to your app, see -<a href="{@docRoot}preview/material/index.html">Material design on Android</a>.</p> +<a href="{@docRoot}preview/material/index.html">Material Design</a>.</p> <h3 id="LockscreenNotifications">Lockscreen notifications</h3> <p>Lockscreens in the L Developer Preview have the ability to present notifications. @@ -194,29 +259,57 @@ content to be shown over a secure lockscreen.</p> displayed over the secure lockscreen. To control the visibility level, call {@code android.app.Notification.Builder.setVisibility()} and specify one of these values:</p> + <ul> <li>{@code VISIBILITY_PRIVATE}. Shows basic information, such as the notification’s icon, but hides the notification’s full content. If you want to provide a redacted public version of your notification for the system to display -on a secure lockscreen, set the public notification object in the <code>publicVersion</code> -field.</li> +on a secure lockscreen, create a public notification object and put a reference +to it in the private notification's {@code publicVersion} field.</li> <li>{@code VISIBILITY_PUBLIC}. Shows the notification’s full content. This is the system default if visibility is left unspecified.</li> <li>{@code VISIBILITY_SECRET}. Shows only the most minimal information, excluding even the notification’s icon.</li> </ul> +<h3 id="DoNotDisturb">Do Not Disturb mode</h3> + +<p>The L Developer Preview introduces a new <em>Do Not Disturb</em> mode. When +the user puts the device in <em>Do Not Disturb</em> mode, the device limits +the frequency of the notifications it shows the user (when the user +wants to avoid distractions). The user can +customize the feature in a number of ways, such as:</p> + +<ul> + <li>Specifying important people, whose calls should go through even when + the device is in <em>Do Not Disturb</em> mode.</li> + <li>Setting custom categories to allow notifications when the device is in + <em>Do Not Disturb</em> mode. Examples of such categories include phone + calls and direct communications (like Hangouts and Skype calls).</li> + <li>Setting rules so <em>Do Not Disturb</em> automatically goes into effect in + certain conditions (like at particular times of day).</li> +</ul> + +<p>You should add the appropriate metadata to your app notifications to help +make sure <em>Do Not Disturb</em> mode handles them properly. For example, if +your app is an alarm clock, +you can tag the notification as an alarm so it will wake the user up even if the +device is in <em>Do Not Disturb</em> mode. For more information, see <a +href="NotificationsMetadata">Notifications metadata</a>.</p> + <h3 id="NotificationsMetadata">Notifications metadata</h3> <p>The L Developer Preview uses metadata associated with your app notifications -to more intelligently sort your notifications. The metadata you set also +to sort the notifications more intelligently. The metadata you set also controls how the system presents your app notifications when the user is in <em>Do -not disturb</em> mode. When constructing your notification, you can call the -following methods in {@code android.app.Notification.Builder}:</p> +Not Disturb</em> mode. To set the metadata, call the following methods in +{@code android.app.Notification.Builder} when you construct the +notification:</p> <ul> -<li>{@code setCategory()}. Allows the system to handle your app notifications -in <em>Do not disturb mode</em> (for example, if your notification represents an -incoming call, instant message, or alarm).</li> +<li>{@code setCategory()}. Depending on the message category, this tells +the system how to handle your app notifications when the device is +in <em>Do Not Disturb</em> mode (for example, if your notification represents an +incoming call, instant message, or alarm). <li>{@code setPriority()}. Notifications with the priority field set to {@code PRIORITY_MAX} or {@code PRIORITY_HIGH} will appear in a small floating window if the notification also has sound or vibration.</li> @@ -231,30 +324,35 @@ people as being more important.</li> <p>In previous releases, the <a href="{@docRoot}design/get-started/ui-overview.html">Recents screen</a> could only display a single task for each app that the user interacted with -most recently. The L Developer Preview allows your app to open additional tasks -for concurrent activities or documents. This feature facilitates multitasking +most recently. The L Developer Preview enables your app to open more tasks as +needed for additional concurrent activities for documents. +This feature facilitates multitasking by letting users quickly switch between individual activities and documents -from the Recents screen. Examples of such concurrent tasks might include web -pages in a browser app, documents in a productivity app, concurrent matches in +from the Recents screen, with a consistent switching experience across all apps. +Examples of such concurrent tasks might include open tabs in a web +browser app, documents in a productivity app, concurrent matches in a game, or chats in a messaging app. Your app can manage its tasks through the {@code android.app.ActivityManager.AppTask} class.</p> <p>To insert a logical break so that the system treats your activity as a new -document, use {@code android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT} when +task, use {@code android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT} when launching the activity with {@link android.app.Activity#startActivity(android.content.Intent) startActivity()}. You can also get this behavior by declaring the <a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a> attribute {@code documentLaunchMode="intoExisting"} or {@code ="always"} in your manifest.</p> <p>You can also mark that a task should be removed from the Recents screen -when all its activities are closed by using {@code android.content.Intent.FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS} when starting the root activity for +when all its activities are closed. To do this, use {@code +android.content.Intent.FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS} when starting the +root activity for the task. You can also set this behavior for an activity by declaring the <a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a> attribute {@code autoRemoveFromRecents=“true”} in your manifest.</p> <p>To avoid cluttering the Recents screen, you can set the maximum number of -tasks from your app that can appear in the Recents screen through the -<a href="{@docRoot}guide/topics/manifest/application-element.html"><application></a> attribute {@code android:maxRecent}. The current maximum that can be specified +tasks from your app that can appear in that screen. To do this, set the +<a href="{@docRoot}guide/topics/manifest/application-element.html"><application></a> +attribute {@code android:maxRecent}. The current maximum that can be specified is 100 tasks per user.</a></p> <h3 id="WebView">WebView updates</h3> @@ -274,16 +372,20 @@ the new features included in this release, see <a href="https://developer.chrome <h3 id="IME">IME bug fixes and improvements</h3> <p>Beginning in the L Developer Preview, users can more easily switch between -all input method editors (IME) <a href="{@docRoot}guide/topics/text/creating-input-method.html">supported by the platform</a>. Performing the designated +all <a href="{@docRoot}guide/topics/text/creating-input-method.html">input +method editors (IME)</a> supported by the platform. Performing the designated switching action (usually touching a Globe icon on the soft keyboard) will cycle among all such IMEs. This change takes place in -{@code android.view.inputmethod.InputMethodManager.shouldOfferSwitchingToNextInputMethod()}.</p> +{@link android.view.inputmethod.InputMethodManager#shouldOfferSwitchingToNextInputMethod +InputMethodManager.shouldOfferSwitchingToNextInputMethod()}.</p> -<p>In addition, the framework will now check whether the next IME includes a -switching mechanism at all, thus supporting switching to the IME after it. An +<p>In addition, the framework now checks whether the next IME includes a +switching mechanism at all (and, thus, whether that IME supports switching to +the IME after it). An IME with a switching mechanism will not cycle to an IME without one. This change takes place in -{@code android.view.inputmethod.InputMethodManager.switchToNextInputMethod()}. +{@link android.view.inputmethod.InputMethodManager#switchToNextInputMethod +InputMethodManager.switchToNextInputMethod}. <p>To see an example of how to use the updated IME-switching APIs, refer to the updated soft-keyboard implementation sample in this release.</p> @@ -314,17 +416,20 @@ ES 3.1. Key new functionality provided in OpenGL ES 3.1 includes:</p> </manifest> </pre> -<p>For more information about using OpenGL ES, including how to check the device’s supported OpenGL ES version at runtime, see the <a href="{@docRoot}/guide/topics/graphics/opengl.html">OpenGL ES API guide</a>.</p> +<p>For more information about using OpenGL ES, including how to check the device’s supported OpenGL ES version at runtime, see the <a href="{@docRoot}guide/topics/graphics/opengl.html">OpenGL ES API guide</a>.</p> <h2 id="Multimedia">Multimedia</h2> -<h3 id="Camera=v2">Camera v2 API</h3> +<h3 id="Camera-v2">Camera v2 API</h3> <p>The L Developer Preview introduces the new {@code android.hardware.camera2} -API to facilitate fine grain photo capture and image processing. You can now programmatically access the camera devices available to the system with {@code CameraManager.getCameraIdList()} and connect to a specific device with {@code CameraManager.openCamera()}. To start capturing images, you -need to create a {@code CameraCaptureSession} and specify the -{@link android.view.Surface} objects to send the captured images. The {@code CameraCaptureSession} can be configured to take single shots or multiple images -in a burst.</p> +API to facilitate fine-grain photo capture and image processing. You can now +programmatically access the camera devices available to the system with {@code +CameraManager.getCameraIdList()} and connect to a specific device with {@code +CameraManager.openCamera()}. To start capturing images, create a {@code +CameraCaptureSession} and specify the {@link android.view.Surface} objects for +the captured images. The {@code CameraCaptureSession} can be configured to take +single shots or multiple images in a burst.</p> <p>To be notified when new images are captured, implement the {@code CameraCaptureSession.CaptureListener()} interface and set it in your @@ -334,16 +439,17 @@ capture request. Now when the system completes the image capture request, your {@code CaptureResult}.</p> <h3 id="AudioPlayback">Audio playback</h3> -<p>This release includes the following changes for - {@code android.media.AudioTrack}:</p> +<p>This release includes the following changes to + {@link android.media.AudioTrack}:</p> <ul> <li>Your app can now supply audio data in floating-point format ({@code android.media.AudioFormat.ENCODING_PCM_FLOAT}). This permits greater dynamic range, more consistent precision, and greater headroom. Floating-point arithmetic is especially useful during intermediate calculations. Playback -end-points use integer format for audio data, and with lower bit-depth. In L -Developer Preview, portions of the internal pipeline are not yet floating-point. - <li>Your app can now supply audio data as a {@code ByteBuffer}, in the same -format as provided by {@code MediaCodec}. +end-points use integer format for audio data, and with lower bit-depth. (In the +L Developer Preview, portions of the internal pipeline are not yet +floating-point.) + <li>Your app can now supply audio data as a {@link java.nio.ByteBuffer}, in the same +format as provided by {@link android.media.MediaCodec}. <li>The {@code WRITE_NON_BLOCKING} option can simplify buffering and multithreading for some apps. </ul> @@ -352,8 +458,8 @@ format as provided by {@code MediaCodec}. <p>You can now build your own media controller app with the new {@code android.media.session.MediaController} class, which provides simplified transport controls APIs that replace those in -{@code android.media.RemoteControlClient}. The {@code MediaController} class -allows thread-safe control of playback from a non UI process, making it easier +{@link android.media.RemoteControlClient}. The {@code MediaController} class +allows thread-safe control of playback from a non-UI process, making it easier to control your media playback service from your app’s user interface. <p>You can also create multiple controllers to send playback commands, @@ -362,14 +468,16 @@ media keys, and other events to the same ongoing call {@code MediaSession.getSessionToken()} to request an access token in order for your app to interact with the session.</p> -<p>Send transport commands such as "play", "stop", "skip", and +<p>You can now send transport commands such as "play", "stop", "skip", and "set rating" by using {@code MediaController.TransportControls}. To handle -in-bound media transport commands from controllers attached to the session, you -should override the callback methods in +in-bound media transport commands from controllers attached to the session, +override the callback methods in {@code MediaSession.TransportControlsCallback}.</p> <p>You can also create rich notifications that allow playback control tied to a -media session with the new {@code android.app.Notification.MediaStyle} class.</p> +media session with the new {@code android.app.Notification.MediaStyle} class. By +using the new notification and media APIs, you will ensure that the System UI +knows about your playback and can extract and show album art.</p> <h2 id="Storage">Storage</h2> @@ -381,46 +489,58 @@ read/write access to media files. When a directory is selected, your app also has access to all its child directories and content.</p> <p>To get the absolute paths to directories on external storage devices where -applications can store media files, call the -{@code android.content.Context.getExternalMediaDirs()} method. No additional +applications can store media files, call the new +{@code android.content.Context.getExternalMediaDirs()} method. No +additional permissions are needed by your app to read or write to the returned paths. -External storage devices here are those considered by the system to be a +In this context, "external storage devices" are those devices which the system +considers to be a permanent part of the device, and includes emulated external storage and physical media slots such as SD cards in battery compartments.</p> <p>If you want to access a document in an existing directory, call the -{@code android.provider.DocumentsContract.buildDocumentViaUri()} method and pass -in a Uri representing the path to the parent directory and the target document -ID. The method returns a new {@link android.net.Uri} with which your app can +{@code android.provider.DocumentsContract.buildDocumentViaUri()} method. +Pass the method a URI representing the path to the parent directory, and the +target document +ID. The method returns a new {@link android.net.Uri} which your app can use to write media content with {@code DocumentsContract.createDocument()}. <h2 id="Wireless">Wireless & Connectivity</h2> <h3 id="Multinetwork">Dynamic network selection and seamless handoff</h3> -<p>The L Developer Preview provides new multi-networking APIs for your app to +<p>The L Developer Preview provides new multi-networking APIs. These let your app dynamically scan for available networks with specific capabilities, and establish a connection to them. This is useful when your app requires a specialized network, such as an SUPL, MMS, or carrier-billing network, or if you want to send data using a particular type of transport protocol.</p> -<p>To select and connect to a network dynamically from your app, first -instantiate a {@code android.net.ConnectivityManager}. Next, create a -{@code android.net.NetworkRequest} to specify the network features and transport -type your app is interested in. To start scanning for suitable networks, call -{@code ConnectivityManager.requestNetwork()} or -{@code ConnectivityManager.registerNetworkCallback(), and pass in the -{@code NetworkRequest} object and an implementation of -{@code ConnectivityManager.NetworkCallbackListener}.</p> +<p>To select and connect to a network dynamically from your app follow these +steps:</p> + +<ol> + <li>Create a {@link android.net.ConnectivityManager}.</li> + <li>Create a + {@code android.net.NetworkRequest} to specify the network features and transport + type your app is interested in.</li> + <li>To scan for suitable networks, call + {@code ConnectivityManager.requestNetwork()} or + {@code ConnectivityManager.registerNetworkCallback()}, and pass in the + {@code NetworkRequest} object and an implementation of + {@code ConnectivityManager.NetworkCallbackListener}.</li> + +</ol> <p>When the system detects a suitable network, it connects to the network and invokes the {@code NetworkCallbackListener.onAvailable()} callback. You can use the {@code android.net.Network} object from the callback to get additional -information about the network, or to establish a socket connection.</p> +information about the network, or to direct traffic to use the selected +network.</p> <h3 id="BluetoothBroadcasting">Bluetooth broadcasting</h3> <p>Android 4.3 introduced platform support for <a href="{@docRoot}guide/topics/connectivity/bluetooth-le.html">Bluetooth Low Energy</a> (BLE) in the central role. In the L Developer Preview, an Android device can now -act as a Bluetooth LE <em>peripheral device</em> and make its presence known to +act as a Bluetooth LE <em>peripheral device</em>. Apps can use this capability +to make their presence known to nearby devices. For instance, you can build apps that allow a device to function as a pedometer or health monitor and communicate its data with another BLE device.</p> @@ -429,16 +549,19 @@ BLE device.</p> You must add the {@code android.permission.BLUETOOTH_ADMIN} permission in your manifest in order for your app to use the new advertising and scanning features.</a> -<p>To begin Bluetooth LE advertising so that other devices can discover the -device running your app, call {@code android.bluetooth.le.BluetoothAdvertiser.startAdvisertising()} and pass in an implementation of the -{@code android.bluetooth.le.AdvertiseCallback} class to report the success -or failure of the advertising operation.</p> - -<p>Conversely, if you want to scan for Bluetooth LE devices nearby, call -{@code android.bluetooth.le.BluetoothLeScanner.startScan()} and pass in an +<p>To begin Bluetooth LE advertising so that other devices can discover +your app, call {@code android.bluetooth.le.BluetoothAdvertiser.startAdvisertising()} +and pass in an implementation of the +{@code android.bluetooth.le.AdvertiseCallback} class. The callback object +receives a report of the success or failure of the advertising operation.</p> + +<p> The L Developer Preview introduces the {@code +android.bluetooth.le.ScanFilter} class so that your app can scan for only the +specific types of devices it is interested in. To begin scanning for Bluetooth +LE devices, call {@code android.bluetooth.le.BluetoothLeScanner.startScan()} and +pass in a list of filters. In the method call, you must also provide an implementation of {@code android.bluetooth.le.ScanCallback} to report if a -Bluetooth LE advertisement is found. Optionally, you can pass in filters to scan -for a specific type of device.</p> +Bluetooth LE advertisement is found. </p> <h3 id="NFCEnhancements">NFC enhancements</h3> <p>The L Developer Preview adds these enhancements to enable wider and more @@ -446,13 +569,12 @@ flexible use of NFC:</p> <ul> <li>Android Beam is now available in the share menu. -<li>Support for the <a href="http://www.wi-fi.org/discover-wi-fi/wi-fi-direct">Wi-fi Direct standard</a>. <li>Your app can invoke the Android Beam on the user’s device to share data by calling {@code android.nfc.NfcAdapter.invokeBeam()}. This avoids the need for the user to manually tap the device against another NFC-capable device to complete the data transfer. -<li>Use the new {@code android.nfc.NdefRecord.createTextRecord()} method if - you want to create an NDEF record containing UTF-8 text data. +<li>You can use the new {@code android.nfc.NdefRecord.createTextRecord()} method +to create an NDEF record containing UTF-8 text data. <li>If you are developing a payment app, you now have the ability to register an NFC application ID (AID) dynamically by calling {@code android.nfc.cardemulation.CardEmulation.registerAidsForService()}. @@ -466,18 +588,31 @@ activity is in the foreground. <h3 id="JobScheduler">Scheduling jobs</h3> <p>The L Developer Preview provides a new {@code android.app.job.JobScheduler} API that lets you optimize battery life by defining jobs for the system to run -asynchronously at a later time, such as when the device is charging. This is -useful when you want to defer non user-facing units of work, have application -code that accesses the network, or want to run a number of tasks as a batch on -a regular schedule.</p> +asynchronously at a later time or under specified conditions (such as when the +device is charging). This is useful in such situations as:</p> +<ul> + <li>The app has non-user-facing work that you want to defer until the unit is + plugged in.</li> + <li>The app has a task that requires network access (or requires a wifi + connection).</li> + <li>The app has a number of tasks that you want to run as a batch on a regular + schedule.</li> -<p>A {@code android.app.job.JobInfo} object encapsulates such a unit of work, -and provides an exact description of the criteria you are scheduling.</p> +</ul> + +<p>A unit of work is encapsulated by a {@code android.app.job.JobInfo} object. +This object provides an exact description of the criteria to be used for +scheduling.</p> <p>Use the {@code android.app.job.JobInfo.Builder} to configure how the scheduled task should run. You can schedule the task to run under specific -conditions such as only while the device is charging, when connected to an -unmetered network, or when the system deems the device is idle.</p> +conditions, such as:</p> + +<ul> + <li>The device is charging</li> + <li>The device is connected to an unmetered network</li> + <li>The system deems the device to be idle</li> +</ul> <p>For example, you can add code like this to run your task on an unmetered network:</p> @@ -513,23 +648,33 @@ statistical data about battery usage on a device, organized by unique user ID </ul> <p>Use the {@code --help} option to learn about the various options for -tailoring the output. For example, to run the tool to print battery usage -statistics since the device was last charged for a given app package, run this +tailoring the output. For example, to print battery usage +statistics for a given app package since the device was last charged, run this command: <pre> -$ adb shell dumpsys batterystats --charged <package-name> +$ adb shell dumpsys batterystats --charged <package-name> </pre> </dd> <dt><strong>Battery Historian</strong></dt> <dd> -<p>The Battery Historian tool ({@code historian.par}) analyzes L-based Android -bug reports and creates an HTML visualization of power-related events. It can -also visualize power consumption data from a power monitor, and will attempt to -map power usage to the wakelocks seen. You can find the Battery Historian tool +<p>The Battery Historian tool ({@code historian.par}) analyzes Android +bug reports from the L Developer Preview and creates an HTML visualization of +power-related events. It can +also visualize power consumption data from a power monitor, and attempts to +map power usage to the wake locks seen. You can find the Battery Historian tool in {@code <sdk>/tools}.</p> -<p>For best results, you should first enable full wakelock reporting to allow +<img src="images/battery_historian.png" + srcset="images/battery_historian@2x.png 2x" + alt="" width="440" height="240" + id="figure1" /> +<p class="img-caption"> + <strong>Figure 1.</strong>HTML visualization generated by the Battery + Historian tool. +</p> + +<p>For best results, you should first enable full wake lock reporting, to allow the Battery Historian tool to monitor uninterrupted over an extended period of time:</p> <pre> @@ -548,93 +693,70 @@ $ historian.par [-p powerfile] bugreport.txt > out.html </pre> </dd> -<dt><strong>On-device power management</strong></dt> -<dd> -<p>You can use the {@code android.os.BatteryManager} API to obtain power -consumption information based on the battery fuel gauge included in Android -phones and tablets. This is useful in cases when it is not convenient to -connect external measurement equipment to the Android device.</p> -<p>To retrieve the battery properties, call {@code BatteryManager.getIntProperty()} -or {@code BatteryManager.getLongProperty()}. The properties available, the -exact resolution of the values of each, and other characteristics such as -update frequency depend on the particular device being tested.</p> - -<p>The following properties can be inspected on all Android devices:</p> - -<table> - <tr> - <th>Property</th> - <th>Description</th> - </tr> - <tr> - <td>{@code BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER}</td> - <td>Remaining battery capacity in microampere-hours.</td> - </tr> - <tr> - <td>{@code BatteryManager.BATTERY_PROPERTY_CURRENT_NOW}</td> - <td>Instantaneous battery current in microamperes.</td> - </tr> - <tr> - <td>{@code BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE}</td> - <td>Average battery current in microamperes</td> - </tr> - <tr> - <td>{@code BatteryManager.BATTERY_PROPERTY_CAPACITY}</td> - <td>Remaining battery capacity as an integer percentage.</td> - </tr> - <tr> - <td>{@code BatteryManager.BATTERY_PROPERTY_ENERGY_COUNTER}</td> - <td>Remaining energy in nanowatt-hours.</td> - </tr> -</table> -<dd> </dl> <h2 id="Enterprise">Enterprise</h2> <h3 id="ManagedProvisioning">Managed provisioning</h3> +<div class="figure" style="width:360px"> + <img src="images/managed_apps_launcher.png" + srcset="images/managed_apps_launcher@2x.png 2x" + alt="" width="360" height="572" id="figure2" /> + <p class="img-caption"> + <strong>Figure 2.</strong> Launcher screen showing managed apps (marked with + a lock badge) + </p> +</div> + <p>The L Developer Preview provides new functionality for running apps within an enterprise environment:</p> <ul> <li><strong>Create managed user profiles</strong>. A device administrator can -initiate a managed provisioning process to enroll a user device with an -existing personal account into a co-present but separate managed profile that -the administrator controls. -<li><strong>Set device owner scope</strong>. Device administrators can also -apply managed provisioning to configure a device that has no previous user -accounts installed, so that they have full control over the device. +initiate a managed provisioning process to add a co-present but separate managed +profile to a device with an existing personal account. The administrator has +control over the managed profile.</li> +<li><strong>Set device owner</strong>. Device administrators can also initiate a +managed provisioning process to automatically provision a +currently-unprovisioned device such that they have full control over the +device.</li> </ul> -<p>To start the manged provisioning process, send -{@code ACTION_PROVISION_MANAGED_PROFILE} in an {@link android.content.Intent}. A -user may be associated with more than one managed profile. To get a list of the -managed profiles associated with the user, call -{@code android.os.UserManager.getUserProfiles()}.</p> +<p>To start the managed provisioning process, send {@code +ACTION_PROVISION_MANAGED_PROFILE} in an {@link android.content.Intent}. If the +call is successful, the system triggers the {@code +android.app.admin.DeviceAdminReceiver. onProfileProvisioningComplete()} callback. +You can then call {@code app.admin.DevicePolicyManager. setProfileEnabled()} to +set this profile to the enabled state.</p> + +<p>A user may be associated with more than one managed profile. To get a list of +the managed profiles associated with the user, call +{@code android.os.UserManager. getUserProfiles()}.</p> <p>Once a managed profile is created for a user, apps that are managed by the device administrator will appear alongside non-managed apps in the user’s -Launcher, Recent apps screen, and notifications. A device policy management app -can make the managed apps visually prominent by appending a “work” badge to the -icon drawable with {@code android.os.UserManager.getBadgeDrawableForUser()}.</p> +Launcher, Recent apps screen, and notifications.</p> -<p>If you are developing a Launcher app, you can use the new {@code android.content.pm.LauncherApps} class to get a list of launchable activities for the current user -and any associated managed profiles.</p> +<p>If you are developing a Launcher app, you can use the new {@code +android.content.pm.LauncherApps} class to get a list of launchable activities +for the current user and any associated managed profiles. Your Launcher can make +the managed apps visually prominent by appending a “work” badge to the icon +drawable with {@code android.os.UserManager.getBadgeDrawableForUser()}.</p> <h2 id="Printing">Printing Framework</h2> <h3 id="PDFRender">Render PDF as bitmap</h3> <p>You can now render PDF document pages into bitmap images for printing by using the new {@code android.graphics.pdf.PdfRenderer} class. You must specify a -{@code ParcelFileDescriptor} that is seekable (that is, the file can be randomly +{@link android.os.ParcelFileDescriptor} that is seekable (that is, the content can be randomly accessed) on which the system writes the the printable content. Your app can obtain a page for rendering with {@code openPage()}, then call {@code render()} to turn the opened {@code PdfRenderer.Page} into a bitmap. You can also set -additional parameters if you only wan to convert a portion of the document into +additional parameters if you only want to convert a portion of the document into a bitmap image (for example, to implement <a href="http://en.wikipedia.org/wiki/Tiled_rendering">tile rendering</a> in order to zoom in on the document).</p> <h2 id="TestingA11y">Testing & Accessibility </h2> -<h3 id="Testing A11yImprovements">Testing and accessibility improvements</h3> +<h3 id="TestingA11yImprovements">Testing and accessibility improvements</h3> <p>The L Developer Preview adds the following support for testing and accessibility:</p> @@ -644,44 +766,45 @@ and {@code android.app.UiAutomation.getWindowContentFrameStats()} methods to capture frame statistics for window animations and content. This lets you write instrumentation tests to evaluate if the app under test is rendering frames at a sufficient refresh frequency to provide a smooth user experience. + <li>You can execute shell commands from your instrumentation test with the new {@code android.app.UiAutomation.executeShellCommand()}. The command execution -is similar to running 'adb shell' from a host connected to the device. This +is similar to running {@code adb shell} from a host connected to the device. This allows you to use shell based tools such as {@code dumpsys}, {@code am}, {@code content}, and {@code pm}. + <li>Accessibility services and test tools that use the accessibility APIs -(such as <a href="{@docRoot}tools/help/uiautomator/index.html">UiAutomator</a>) +(such as <a href="{@docRoot}tools/help/uiautomator/index.html">uiautomator</a>) can now retrieve detailed information about the properties of windows on the screen that sighted users can interact with. To retrieve a list of -{@code android.view.accessibility.AccessibilityWindowInfo} representing the +{@code android.view.accessibility.AccessibilityWindowInfo} objects +representing the windows information, call the new {@code android.accessibilityservice.AccessibilityService.getWindows()} method. <li>You can use the new {@code android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction} to define standard or customized -actions to perform on an {@code android.view.accessibility.AccessibilityNodeInfo}. +actions to perform on an {@link android.view.accessibility.AccessibilityNodeInfo}. The new {@code AccessibilityAction} class replaces the actions-related APIs previously found in {@code AccessibilityNodeInfo}. </ul> -<h2 id="manifest">Manifest Declarations</h2> +<h2 id="Manifest">Manifest Declarations</h2> <h3 id="ManifestFeatures">Declarable required features</h3> -<p>The following values are now supported in the <a href="{@docRoot}guide/topics/manifest/uses-feature-element.html">{@code <uses-feature>}</a> element so you +<p>The following values are now supported in the <a href="{@docRoot}guide/topics/manifest/uses-feature-element.html">{@code <uses-feature>}</a> element, so you can ensure that your app is installed only on devices that provide the features your app needs.</p> <ul> -<li>{@code FEATURE_LEANBACK}. Declares that your app must be installed only on devices that support the <a href="{@docRoot}tv}">Android TV</a> user interface. Example: +<li>{@code FEATURE_LEANBACK}. Declares that your app must be installed only on +devices that support the <a href="{@docRoot}training/tv}">Android TV</a> user +interface. Example: <pre> <uses-feature android:name="android.software.leanback" android:required="true" /> </pre> -<li>{@code FEATURE_MANAGEDPROFILES}. Declares that your app must only be installed on devices that support managed profiles for enterprise users. Example: -<pre> -<uses-feature android:name="android.software.managedprofiles" - android:required="true" /> -</pre> -<li>{@code FEATURE_WEBVIEW}. Declares that your app must only be installed on devices that fully implement the android.webkit.* APIs. Example: +<li>{@code FEATURE_WEBVIEW}. Declares that your app must only be installed on +devices that fully implement the {@code android.webkit.*} APIs. Example: <pre> <uses-feature android:name="android.software.webview" android:required="true" /> diff --git a/docs/html/preview/images/battery_historian.png b/docs/html/preview/images/battery_historian.png Binary files differnew file mode 100644 index 000000000000..5b0db74cb87e --- /dev/null +++ b/docs/html/preview/images/battery_historian.png diff --git a/docs/html/preview/images/battery_historian@2x.png b/docs/html/preview/images/battery_historian@2x.png Binary files differnew file mode 100644 index 000000000000..dbb5d5eb5ff8 --- /dev/null +++ b/docs/html/preview/images/battery_historian@2x.png diff --git a/docs/html/preview/images/managed_apps_launcher.png b/docs/html/preview/images/managed_apps_launcher.png Binary files differnew file mode 100644 index 000000000000..983d90460353 --- /dev/null +++ b/docs/html/preview/images/managed_apps_launcher.png diff --git a/docs/html/preview/images/managed_apps_launcher@2.png b/docs/html/preview/images/managed_apps_launcher@2.png Binary files differnew file mode 100644 index 000000000000..d298fd2deb28 --- /dev/null +++ b/docs/html/preview/images/managed_apps_launcher@2.png diff --git a/docs/html/tv/images/hero.jpg b/docs/html/tv/images/hero.jpg Binary files differindex c42a43617a9d..e95116726653 100644 --- a/docs/html/tv/images/hero.jpg +++ b/docs/html/tv/images/hero.jpg diff --git a/docs/html/tv/index.jd b/docs/html/tv/index.jd index e1cae8cb65c4..3e7652c733b7 100644 --- a/docs/html/tv/index.jd +++ b/docs/html/tv/index.jd @@ -4,7 +4,6 @@ fullpage=true no_footer_links=true page.type=about - @jd:body <style> @@ -14,17 +13,9 @@ page.type=about } </style> -<style> -#footer { - display: none; -} -.content-footer { - display: none; -} -</style> - <div class="landing-body-content"> + <div class="landing-hero-container"> <div class="landing-section tv-hero"> @@ -42,9 +33,11 @@ page.type=about Put your app on TV and bring everyone into the action.</p> </div> + </div> <div class="landing-body"> - <a href="{@docRoot}preview/tv/index.html" class="landing-button landing-primary" style="margin-top: 40px;"> + <a href="{@docRoot}preview/tv/start/index.html" class="landing-button + landing-primary" style="margin-top: 40px;"> Get Started </a> </div> @@ -58,11 +51,10 @@ page.type=about </a> </div> </div> <!-- end .landing-section .landing-hero --> - </div> <!-- end .landing-hero-container --> <div class="landing-rest-of-page"> - <div class="landing-section landing-gray-background" id="reimagine-your-app"> + <div class="landing-section" style="background-color:#f5f5f5" id="reimagine-your-app"> <div class="wrap"> <div class="landing-section-header"> <div class="landing-h1">Reimagine Your App</div> @@ -71,7 +63,6 @@ page.type=about </div> </div> - <div class="landing-body"> <div class="landing-breakout cols"> @@ -119,13 +110,13 @@ page.type=about </div> <!-- end .wrap --> </div> <!-- end .landing-section --> - <div class="landing-section" style="background-color:#f5f5f5"> + <div class="landing-section landing-gray-background"> <div class="wrap"> <div class="landing-section-header"> <div class="landing-h1">Build to Entertain</div> <div class="landing-subhead"> - Android TV let's you engage your users in a new, shared environment.<br> - Find out how to get your app ready for it's big screen debut. + Android TV lets you engage your users in a new, shared environment.<br> + Find out how to get your app ready for its big-screen debut. </div> </div> @@ -142,7 +133,7 @@ page.type=about catalogs. </p> <p class="landing-small"> - <a href="{@docRoot}design/tv/index.html">Learn pre-built fragments</a> + <a href="{@docRoot}preview/tv/ui/browse.html">Learn pre-built fragments</a> </p> </div> @@ -151,11 +142,10 @@ page.type=about <p>Get Found</p> <p class="landing-small"> - Give your content the attention it deserves by including it in Android TV's global - search results. + Help users find your content quickly with in-app searching. </p> <p class="landing-small"> - <a href="{@docRoot}design/tv/index.html">Learn about TV design</a> + <a href="{@docRoot}preview/tv/ui/in-app-search.html">Learn about app search</a> </p> </div> @@ -167,7 +157,8 @@ page.type=about Suggest content from your app to keep your users coming back. </p> <p class="landing-small"> - <a href="{@docRoot}design/tv/index.html">Learn about design for TV</a> + <a href="{@docRoot}preview/tv/ui/recommendations.html">Learn about + recommendations</a> </p> </div> @@ -182,28 +173,26 @@ page.type=about <div class="landing-section-header"> <div class="landing-h1 landing-align-left">Get Started with Android TV</div> <div class="landing-body"> - <p>You can begin building apps right away using these developer resources.</p> + <p>Begin building TV apps right away using these developer resources:</p> </div> </div> <div class="landing-body"> <div class="landing-breakout cols"> - <div class="col-8"> - <p>Preview SDK</p> + <div class="col-8" style="margin-left: -8px;"> + <p style="font-size: 24px;">L-Preview SDK</p> <p> - Get started building for Android TV using the Android L-preview SDK. The preview - SDK includes the Android TV emulator so you can start building your TV app right - away. + The preview SDK includes all the tools you need to build and test apps for TV. + Download it and start creating your big-screen app. </p> </div> <div class="col-8"> - <p>ADT-1 Developer Kit</p> + <p style="font-size: 24px;">ADT-1 Developer Kit</p> <p> - While supplies last, developers can request an ADT-1 Developer Kit, a compact and - powerful streaming media player and gamepad, ideal for developing apps for Android - TV. + Request an ADT-1 Developer Kit, a compact and powerful streaming media player + and gamepad, ideal for developing and testing apps for TV. </p> </div> @@ -215,15 +204,16 @@ page.type=about <div class="landing-breakout cols"> <div class="col-8"> - <a href="{@docRoot}preview/download.html" class="landing-button landing-secondary"> + <a href="{@docRoot}preview/setup-sdk.html" class="landing-button landing-secondary"> Download the Preview SDK </a> </div> <div class="col-8"> - <a href="{@docRoot}tv/adt-1/request.html" class="landing-button landing-secondary"> + <a href="{@docRoot}preview/tv/adt-1/request.html" class="landing-button landing-secondary"> Request ADT-1 Developer Kit </a> + </div> </div> </div> @@ -232,31 +222,33 @@ page.type=about </div> <!-- end .landing-rest-of-page --> - - <div class="content-footer wrap" itemscope="" itemtype="http://schema.org/SiteNavigationElement"> - <div class="layout-content-col col-16" style="padding-top:4px"> - <style>#___plusone_0 {float:right !important;}</style> - <div class="g-plusone" data-size="medium"></div> + <div class="content-footer wrap" itemscope="" itemtype="http://schema.org/SiteNavigationElement" + style="border-top: none;"> + <div class="layout-content-col col-16" style="padding-top:4px"> + <style>#___plusone_0 {float:right !important;}</style> + <div class="g-plusone" data-size="medium"></div> + </div> </div> - </div> - <div id="footer" class="wrap" style="width:940px;position:relative;top:-35px;z-index:-1"> - <div id="copyright"> - Except as noted, this content is - licensed under <a href="http://creativecommons.org/licenses/by/2.5/"> - Creative Commons Attribution 2.5</a>. For details and - restrictions, see the <a href="/license.html">Content - License</a>. + <div id="footer" class="wrap" style="width:940px;position:relative;top:-35px;z-index:-1"> + <div id="copyright"> + Except as noted, this content is + licensed under <a href="http://creativecommons.org/licenses/by/2.5/"> + Creative Commons Attribution 2.5</a>. For details and + restrictions, see the <a href="/license.html">Content + License</a>. + </div> </div> - </div> - - </div> <!-- end landing-body-content --> + </div> <!-- end .landing-hero-container --> <script> $("a.landing-down-arrow").on("click", function(e) { $("body").animate({ - scrollTop: $(".wear-hero").height() + 76 + scrollTop: $(".tv-hero").height() + 120 }, 1000, "easeOutQuint"); e.preventDefault(); }); </script> + +</div> <!-- end landing-body-content --> + diff --git a/docs/html/wear/index.jd b/docs/html/wear/index.jd index 59d5506242a3..0d9325d1d971 100644 --- a/docs/html/wear/index.jd +++ b/docs/html/wear/index.jd @@ -66,7 +66,7 @@ page.type=about </a> </div> </div> <!-- end .landing-section .landing-hero --> - </div> <!-- end .landing-hero-container --> + <div class="landing-rest-of-page"> <div class="landing-section" id="extending-android-to-wearables"> @@ -264,7 +264,7 @@ page.type=about <div class="content-footer wrap" itemscope="" itemtype="http://schema.org/SiteNavigationElement"> <div class="layout-content-col col-16" style="padding-top:4px"> <style>#___plusone_0 {float:right !important;}</style> - <div id="___plusone_0" style="text-indent: 0px; margin: 0px; padding: 0px; border-style: none; float: none; line-height: normal; font-size: 1px; vertical-align: baseline; display: inline-block; width: 90px; height: 20px; background: transparent;"><iframe frameborder="0" hspace="0" marginheight="0" marginwidth="0" scrolling="no" style="position: static; top: 0px; width: 90px; margin: 0px; border-style: none; left: 0px; visibility: visible; height: 20px;" tabindex="0" vspace="0" width="100%" id="I0_1402525433965" name="I0_1402525433965" src="https://apis.google.com/u/0/_/+1/fastbutton?usegapi=1&size=medium&origin=http%3A%2F%2Frobertly.mtv%3A8080&url=http%3A%2F%2Frobertly.mtv%3A8080%2Fwear%2Findex.html&gsrc=3p&jsh=m%3B%2F_%2Fscs%2Fapps-static%2F_%2Fjs%2Fk%3Doz.gapi.en.QxHQHBkhz7M.O%2Fm%3D__features__%2Fam%3DUQ%2Frt%3Dj%2Fd%3D1%2Fz%3Dzcms%2Frs%3DAItRSTMLrMyRVKsu2FQoRingre3w1MT49A#_methods=onPlusOne%2C_ready%2C_close%2C_open%2C_resizeMe%2C_renderstart%2Concircled%2Cdrefresh%2Cerefresh%2Conload&id=I0_1402525433965&parent=http%3A%2F%2Frobertly.mtv%3A8080&pfname=&rpctoken=32453860" data-gapiattached="true" title="+1"></iframe></div> + <div class="g-plusone" data-size="medium"></div> </div> </div> <div id="footer" class="wrap" style="width:940px;position:relative;top:-35px;z-index:-1"> @@ -276,5 +276,16 @@ page.type=about License</a>. </div> </div> - </div> <!-- end landing-body-content --> + </div> <!-- end .landing-hero-container --> + + <script> + $("a.landing-down-arrow").on("click", function(e) { + $("body").animate({ + scrollTop: $(".wear-hero").height() + 120 + }, 1000, "easeOutQuint"); + e.preventDefault(); + }); + </script> + +</div> <!-- end landing-body-content --> diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index 158801c34c71..13421aa1dea0 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -1770,7 +1770,7 @@ public class Canvas { } native_drawTextOnPath(mNativeCanvasWrapper, text, index, count, path.ni(), hOffset, vOffset, - paint.mBidiFlags, paint.mNativePaint); + paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); } /** @@ -1790,7 +1790,7 @@ public class Canvas { float vOffset, @NonNull Paint paint) { if (text.length() > 0) { native_drawTextOnPath(mNativeCanvasWrapper, text, path.ni(), hOffset, vOffset, - paint.mBidiFlags, paint.mNativePaint); + paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); } } @@ -2021,11 +2021,11 @@ public class Canvas { int count, long nativePath, float hOffset, float vOffset, int bidiFlags, - long nativePaint); + long nativePaint, long nativeTypeface); private static native void native_drawTextOnPath(long nativeCanvas, String text, long nativePath, float hOffset, float vOffset, - int flags, long nativePaint); + int flags, long nativePaint, long nativeTypeface); private static native void finalizer(long nativeCanvas); } diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java index a0211653c4c1..5aa7c6a8df52 100644 --- a/graphics/java/android/graphics/Picture.java +++ b/graphics/java/android/graphics/Picture.java @@ -31,18 +31,13 @@ public class Picture { private Canvas mRecordingCanvas; private final long mNativePicture; - /** - * @hide - */ - public final boolean createdFromStream; - private static final int WORKING_STREAM_STORAGE = 16 * 1024; /** * Creates an empty picture that is ready to record. */ public Picture() { - this(nativeConstructor(0), false); + this(nativeConstructor(0)); } /** @@ -51,7 +46,23 @@ public class Picture { * changes will not be reflected in this picture. */ public Picture(Picture src) { - this(nativeConstructor(src != null ? src.mNativePicture : 0), false); + this(nativeConstructor(src != null ? src.mNativePicture : 0)); + } + + private Picture(long nativePicture) { + if (nativePicture == 0) { + throw new RuntimeException(); + } + mNativePicture = nativePicture; + } + + @Override + protected void finalize() throws Throwable { + try { + nativeDestructor(mNativePicture); + } finally { + super.finalize(); + } } /** @@ -85,13 +96,17 @@ public class Picture { * Get the width of the picture as passed to beginRecording. This * does not reflect (per se) the content of the picture. */ - public native int getWidth(); + public int getWidth() { + return nativeGetWidth(mNativePicture); + } /** * Get the height of the picture as passed to beginRecording. This * does not reflect (per se) the content of the picture. */ - public native int getHeight(); + public int getHeight() { + return nativeGetHeight(mNativePicture); + } /** * Draw this picture on the canvas. @@ -130,7 +145,7 @@ public class Picture { */ @Deprecated public static Picture createFromStream(InputStream stream) { - return new Picture(nativeCreateFromStream(stream, new byte[WORKING_STREAM_STORAGE]), true); + return new Picture(nativeCreateFromStream(stream, new byte[WORKING_STREAM_STORAGE])); } /** @@ -159,32 +174,12 @@ public class Picture { } } - protected void finalize() throws Throwable { - try { - nativeDestructor(mNativePicture); - } finally { - super.finalize(); - } - } - - final long ni() { - return mNativePicture; - } - - private Picture(long nativePicture, boolean fromStream) { - if (nativePicture == 0) { - throw new RuntimeException(); - } - mNativePicture = nativePicture; - createdFromStream = fromStream; - } - // return empty picture if src is 0, or a copy of the native src private static native long nativeConstructor(long nativeSrcOr0); - private static native long nativeCreateFromStream(InputStream stream, - byte[] storage); - private static native long nativeBeginRecording(long nativeCanvas, - int w, int h); + private static native long nativeCreateFromStream(InputStream stream, byte[] storage); + private static native int nativeGetWidth(long nativePicture); + private static native int nativeGetHeight(long nativePicture); + private static native long nativeBeginRecording(long nativeCanvas, int w, int h); private static native void nativeEndRecording(long nativeCanvas); private static native void nativeDraw(long nativeCanvas, long nativePicture); private static native boolean nativeWriteToStream(long nativePicture, @@ -201,18 +196,15 @@ public class Picture { @Override public void setBitmap(Bitmap bitmap) { - throw new RuntimeException( - "Cannot call setBitmap on a picture canvas"); + throw new RuntimeException("Cannot call setBitmap on a picture canvas"); } @Override public void drawPicture(Picture picture) { if (mPicture == picture) { - throw new RuntimeException( - "Cannot draw a picture into its recording canvas"); + throw new RuntimeException("Cannot draw a picture into its recording canvas"); } super.drawPicture(picture); } } } - diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java index be940df215e7..0a394d56d322 100644 --- a/graphics/java/android/graphics/drawable/BitmapDrawable.java +++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java @@ -928,7 +928,7 @@ public class BitmapDrawable extends Drawable { mTargetDensity = state.mTargetDensity; } - updateTintFilter(mTintFilter, state.mTint, state.mTintMode); + mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); computeBitmapSize(); } } diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java index c3c1bcabad4e..a1e1f7612a60 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -147,10 +147,12 @@ public class VectorDrawable extends Drawable { } private VectorDrawable(VectorDrawableState state, Resources res, Theme theme) { - mVectorState = new VectorDrawableState(state); - - if (theme != null && canApplyTheme()) { + if (theme != null && state.canApplyTheme()) { + // If we need to apply a theme, implicitly mutate. + mVectorState = new VectorDrawableState(state); applyTheme(theme); + } else { + mVectorState = state; } mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); diff --git a/include/androidfw/ByteBucketArray.h b/include/androidfw/ByteBucketArray.h new file mode 100644 index 000000000000..87c6b128eca1 --- /dev/null +++ b/include/androidfw/ByteBucketArray.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __BYTE_BUCKET_ARRAY_H +#define __BYTE_BUCKET_ARRAY_H + +#include <utils/Log.h> +#include <stdint.h> +#include <string.h> + +namespace android { + +/** + * Stores a sparsely populated array. Has a fixed size of 256 + * (number of entries that a byte can represent). + */ +template<typename T> +class ByteBucketArray { +public: + ByteBucketArray() : mDefault() { + memset(mBuckets, 0, sizeof(mBuckets)); + } + + ~ByteBucketArray() { + for (size_t i = 0; i < NUM_BUCKETS; i++) { + if (mBuckets[i] != NULL) { + delete [] mBuckets[i]; + } + } + memset(mBuckets, 0, sizeof(mBuckets)); + } + + inline size_t size() const { + return NUM_BUCKETS * BUCKET_SIZE; + } + + inline const T& get(size_t index) const { + return (*this)[index]; + } + + const T& operator[](size_t index) const { + if (index >= size()) { + return mDefault; + } + + uint8_t bucketIndex = static_cast<uint8_t>(index) >> 4; + T* bucket = mBuckets[bucketIndex]; + if (bucket == NULL) { + return mDefault; + } + return bucket[0x0f & static_cast<uint8_t>(index)]; + } + + T& editItemAt(size_t index) { + ALOG_ASSERT(index < size(), "ByteBucketArray.getOrCreate(index=%u) with size=%u", + (uint32_t) index, (uint32_t) size()); + + uint8_t bucketIndex = static_cast<uint8_t>(index) >> 4; + T* bucket = mBuckets[bucketIndex]; + if (bucket == NULL) { + bucket = mBuckets[bucketIndex] = new T[BUCKET_SIZE](); + } + return bucket[0x0f & static_cast<uint8_t>(index)]; + } + + bool set(size_t index, const T& value) { + if (index >= size()) { + return false; + } + + editItemAt(index) = value; + return true; + } + +private: + enum { NUM_BUCKETS = 16, BUCKET_SIZE = 16 }; + + T* mBuckets[NUM_BUCKETS]; + T mDefault; +}; + +} // namespace android + +#endif // __BYTE_BUCKET_ARRAY_H diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h index 4d8e512d1651..e612c0a92a51 100644 --- a/include/androidfw/ResourceTypes.h +++ b/include/androidfw/ResourceTypes.h @@ -237,6 +237,7 @@ enum { #define Res_MAKEARRAY(entry) (0x02000000 | (entry&0xFFFF)) #define Res_MAXPACKAGE 255 +#define Res_MAXTYPE 255 /** * Representation of a value in a resource, supplying type @@ -510,6 +511,23 @@ private: uint32_t mStylePoolSize; // number of uint32_t }; +/** + * Wrapper class that allows the caller to retrieve a string from + * a string pool without knowing which string pool to look. + */ +class StringPoolRef { +public: + StringPoolRef(); + StringPoolRef(const ResStringPool* pool, uint32_t index); + + const char* string8(size_t* outLen) const; + const char16_t* string16(size_t* outLen) const; + +private: + const ResStringPool* mPool; + uint32_t mIndex; +}; + /** ******************************************************************** * XML Tree * @@ -835,6 +853,8 @@ struct ResTable_package // Last index into keyStrings that is for public use by others. uint32_t lastPublicKey; + + uint32_t typeIdOffset; }; // The most specific locale can consist of: @@ -1469,9 +1489,13 @@ public: bool copyData=false); ~ResTable(); - status_t add(Asset* asset, const int32_t cookie, bool copyData, - const void* idmap = NULL); - status_t add(const void *data, size_t size); + status_t add(const void* data, size_t size, const int32_t cookie=-1, bool copyData=false); + status_t add(const void* data, size_t size, const void* idmapData, size_t idmapDataSize, + const int32_t cookie=-1, bool copyData=false); + + status_t add(Asset* asset, const int32_t cookie=-1, bool copyData=false); + status_t add(Asset* asset, Asset* idmapAsset, const int32_t cookie=-1, bool copyData=false); + status_t add(ResTable* src); status_t addEmpty(const int32_t cookie); @@ -1610,13 +1634,14 @@ public: uint32_t typeSpecFlags; Res_value value; }; + struct type_info { size_t numEntries; theme_entry* entries; }; + struct package_info { - size_t numTypes; - type_info types[]; + type_info types[Res_MAXTYPE + 1]; }; void free_package(package_info* pi); @@ -1711,6 +1736,7 @@ public: size_t getBasePackageCount() const; const String16 getBasePackageName(size_t idx) const; uint32_t getBasePackageId(size_t idx) const; + uint32_t getLastTypeIdForPackage(size_t idx) const; // Return the number of resource tables that the object contains. size_t getTableCount() const; @@ -1740,13 +1766,15 @@ public: void** outData, size_t* outSize) const; enum { - IDMAP_HEADER_SIZE_BYTES = 3 * sizeof(uint32_t) + 2 * 256, + IDMAP_HEADER_SIZE_BYTES = 4 * sizeof(uint32_t) + 2 * 256, }; + // Retrieve idmap meta-data. // // This function only requires the idmap header (the first // IDMAP_HEADER_SIZE_BYTES) bytes of an idmap file. static bool getIdmapInfo(const void* idmap, size_t size, + uint32_t* pVersion, uint32_t* pTargetCrc, uint32_t* pOverlayCrc, String8* pTargetPath, String8* pOverlayPath); @@ -1756,21 +1784,24 @@ public: private: struct Header; struct Type; + struct Entry; struct Package; struct PackageGroup; struct bag_set; + typedef Vector<Type*> TypeList; - status_t addInternal(const void* data, size_t size, const int32_t cookie, - bool copyData, const Asset* idmap); + status_t addInternal(const void* data, size_t size, const void* idmapData, size_t idmapDataSize, + const int32_t cookie, bool copyData); ssize_t getResourcePackageIndex(uint32_t resID) const; - ssize_t getEntry( - const Package* package, int typeIndex, int entryIndex, + + status_t getEntry( + const PackageGroup* packageGroup, int typeIndex, int entryIndex, const ResTable_config* config, - const ResTable_type** outType, const ResTable_entry** outEntry, - const Type** outTypeClass) const; + Entry* outEntry) const; + status_t parsePackage( - const ResTable_package* const pkg, const Header* const header, uint32_t idmap_id); + const ResTable_package* const pkg, const Header* const header); void print_value(const Package* pkg, const Res_value& value) const; diff --git a/include/androidfw/TypeWrappers.h b/include/androidfw/TypeWrappers.h new file mode 100644 index 000000000000..7bdf8af0ad4c --- /dev/null +++ b/include/androidfw/TypeWrappers.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __TYPE_WRAPPERS_H +#define __TYPE_WRAPPERS_H + +#include <androidfw/ResourceTypes.h> + +namespace android { + +struct TypeVariant { + TypeVariant(const ResTable_type* data) + : data(data) {} + + class iterator { + public: + iterator& operator=(const iterator& rhs) { + mTypeVariant = rhs.mTypeVariant; + mIndex = rhs.mIndex; + } + + bool operator==(const iterator& rhs) const { + return mTypeVariant == rhs.mTypeVariant && mIndex == rhs.mIndex; + } + + bool operator!=(const iterator& rhs) const { + return mTypeVariant != rhs.mTypeVariant || mIndex != rhs.mIndex; + } + + iterator operator++(int) { + uint32_t prevIndex = mIndex; + operator++(); + return iterator(mTypeVariant, prevIndex); + } + + const ResTable_entry* operator->() const { + return operator*(); + } + + uint32_t index() const { + return mIndex; + } + + iterator& operator++(); + const ResTable_entry* operator*() const; + + private: + friend struct TypeVariant; + iterator(const TypeVariant* tv, uint32_t index) + : mTypeVariant(tv), mIndex(index) {} + const TypeVariant* mTypeVariant; + uint32_t mIndex; + }; + + iterator beginEntries() const { + return iterator(this, 0); + } + + iterator endEntries() const { + return iterator(this, dtohl(data->entryCount)); + } + + const ResTable_type* data; +}; + +} // namespace android + +#endif // __TYPE_WRAPPERS_H diff --git a/libs/androidfw/Android.mk b/libs/androidfw/Android.mk index d21197e3a673..957809d82d0e 100644 --- a/libs/androidfw/Android.mk +++ b/libs/androidfw/Android.mk @@ -25,6 +25,7 @@ commonSources := \ ObbFile.cpp \ ResourceTypes.cpp \ StreamingZipInflater.cpp \ + TypeWrappers.cpp \ ZipFileRO.cpp \ ZipUtils.cpp diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index 1b3f1fd9c573..03409286ea6c 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -256,7 +256,7 @@ bool AssetManager::addOverlayPath(const String8& packagePath, int32_t* cookie) String8 targetPath; String8 overlayPath; if (!ResTable::getIdmapInfo(idmap->getBuffer(false), idmap->getLength(), - NULL, NULL, &targetPath, &overlayPath)) { + NULL, NULL, NULL, &targetPath, &overlayPath)) { ALOGW("failed to read idmap file %s\n", idmapPath.string()); delete idmap; return false; @@ -311,7 +311,7 @@ bool AssetManager::createIdmap(const char* targetApkPath, const char* overlayApk ALOGW("failed to find resources.arsc in %s\n", ap.path.string()); return false; } - tables[i].add(ass, 1, false /* copyData */, NULL /* idMap */); + tables[i].add(ass); } return tables[0].createIdmap(tables[1], targetCrc, overlayCrc, @@ -617,7 +617,7 @@ const ResTable* AssetManager::getResTable(bool required) const // can quickly copy it out for others. ALOGV("Creating shared resources for %s", ap.path.string()); sharedRes = new ResTable(); - sharedRes->add(ass, i + 1, false, idmap); + sharedRes->add(ass, idmap, i + 1, false); #ifdef HAVE_ANDROID_OS const char* data = getenv("ANDROID_DATA"); LOG_ALWAYS_FATAL_IF(data == NULL, "ANDROID_DATA not set"); @@ -646,7 +646,7 @@ const ResTable* AssetManager::getResTable(bool required) const mResources->add(sharedRes); } else { ALOGV("Parsing resources for %s", ap.path.string()); - mResources->add(ass, i + 1, !shared, idmap); + mResources->add(ass, idmap, i + 1, !shared); } onlyEmptyResources = false; @@ -654,7 +654,7 @@ const ResTable* AssetManager::getResTable(bool required) const delete ass; } } else { - ALOGW("Installing empty resources in to table %p\n", mResources); + ALOGV("Installing empty resources in to table %p\n", mResources); mResources->addEmpty(i + 1); } @@ -736,7 +736,7 @@ void AssetManager::addSystemOverlays(const char* pathOverlaysList, if (oass != NULL) { Asset* oidmap = openIdmapLocked(oap); offset++; - sharedRes->add(oass, offset + 1, false, oidmap); + sharedRes->add(oass, oidmap, offset + 1, false); const_cast<AssetManager*>(this)->mAssetPaths.add(oap); const_cast<AssetManager*>(this)->mZipSet.addOverlay(targetPackagePath, oap); } diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index a4b78a605496..2e3abb53363c 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -17,7 +17,9 @@ #define LOG_TAG "ResourceType" //#define LOG_NDEBUG 0 +#include <androidfw/ByteBucketArray.h> #include <androidfw/ResourceTypes.h> +#include <androidfw/TypeWrappers.h> #include <utils/Atomic.h> #include <utils/ByteOrder.h> #include <utils/Debug.h> @@ -30,6 +32,7 @@ #include <memory.h> #include <ctype.h> #include <stdint.h> +#include <stddef.h> #ifndef INT32_MAX #define INT32_MAX ((int32_t)(2147483647)) @@ -42,7 +45,7 @@ #define TABLE_SUPER_NOISY(x) //x #define LOAD_TABLE_NOISY(x) //x #define TABLE_THEME(x) //x -#define LIB_NOISY(x) x +#define LIB_NOISY(x) //x namespace android { @@ -63,9 +66,8 @@ namespace android { #endif #endif -#define IDMAP_MAGIC 0x706d6469 -// size measured in sizeof(uint32_t) -#define IDMAP_HEADER_SIZE (ResTable::IDMAP_HEADER_SIZE_BYTES / sizeof(uint32_t)) +#define IDMAP_MAGIC 0x504D4449 +#define IDMAP_CURRENT_VERSION 0x00000001 #define APP_PACKAGE_ID 0x7f #define SYS_PACKAGE_ID 0x01 @@ -77,6 +79,11 @@ inline int isspace16(char16_t c) { return (c < 0x0080 && isspace(c)); } +template<typename T> +inline static T max(T a, T b) { + return a > b ? a : b; +} + // range checked; guaranteed to NUL-terminate within the stated number of available slots // NOTE: if this truncates the dst string due to running out of space, no attempt is // made to avoid splitting surrogate pairs. @@ -215,104 +222,179 @@ void Res_png_9patch::serialize(const Res_png_9patch& patch, const int32_t* xDivs fill9patchOffsets(reinterpret_cast<Res_png_9patch*>(outData)); } -static bool assertIdmapHeader(const uint32_t* map, size_t sizeBytes) -{ - if (sizeBytes < ResTable::IDMAP_HEADER_SIZE_BYTES) { - ALOGW("idmap assertion failed: size=%d bytes\n", (int)sizeBytes); +static bool assertIdmapHeader(const void* idmap, size_t size) { + if (reinterpret_cast<uintptr_t>(idmap) & 0x03) { + ALOGE("idmap: header is not word aligned"); + return false; + } + + if (size < ResTable::IDMAP_HEADER_SIZE_BYTES) { + ALOGW("idmap: header too small (%d bytes)", (uint32_t) size); return false; } - if (*map != htodl(IDMAP_MAGIC)) { // htodl: map data expected to be in correct endianess - ALOGW("idmap assertion failed: invalid magic found (is 0x%08x, expected 0x%08x)\n", - *map, htodl(IDMAP_MAGIC)); + + const uint32_t magic = htodl(*reinterpret_cast<const uint32_t*>(idmap)); + if (magic != IDMAP_MAGIC) { + ALOGW("idmap: no magic found in header (is 0x%08x, expected 0x%08x)", + magic, IDMAP_MAGIC); + return false; + } + + const uint32_t version = htodl(*(reinterpret_cast<const uint32_t*>(idmap) + 1)); + if (version != IDMAP_CURRENT_VERSION) { + // We are strict about versions because files with this format are + // auto-generated and don't need backwards compatibility. + ALOGW("idmap: version mismatch in header (is 0x%08x, expected 0x%08x)", + version, IDMAP_CURRENT_VERSION); return false; } return true; } -static status_t idmapLookup(const uint32_t* map, size_t sizeBytes, uint32_t key, uint32_t* outValue) -{ - // see README for details on the format of map - if (!assertIdmapHeader(map, sizeBytes)) { - return UNKNOWN_ERROR; - } - map = map + IDMAP_HEADER_SIZE; // skip ahead to data segment - // size of data block, in uint32_t - const size_t size = (sizeBytes - ResTable::IDMAP_HEADER_SIZE_BYTES) / sizeof(uint32_t); - const uint32_t type = Res_GETTYPE(key) + 1; // add one, idmap stores "public" type id - const uint32_t entry = Res_GETENTRY(key); - const uint32_t typeCount = *map; +class IdmapEntries { +public: + IdmapEntries() : mData(NULL) {} - if (type > typeCount) { - ALOGW("Resource ID map: type=%d exceeds number of types=%d\n", type, typeCount); - return UNKNOWN_ERROR; + bool hasEntries() const { + if (mData == NULL) { + return false; + } + + return (dtohs(*mData) > 0); } - if (typeCount > size) { - ALOGW("Resource ID map: number of types=%d exceeds size of map=%d\n", typeCount, (int)size); - return UNKNOWN_ERROR; + + size_t byteSize() const { + if (mData == NULL) { + return 0; + } + uint16_t entryCount = dtohs(mData[2]); + return (sizeof(uint16_t) * 4) + (sizeof(uint32_t) * static_cast<size_t>(entryCount)); } - const uint32_t typeOffset = map[type]; - if (typeOffset == 0) { - *outValue = 0; - return NO_ERROR; + + uint8_t targetTypeId() const { + if (mData == NULL) { + return 0; + } + return dtohs(mData[0]); } - if (typeOffset + 1 > size) { - ALOGW("Resource ID map: type offset=%d exceeds reasonable value, size of map=%d\n", - typeOffset, (int)size); - return UNKNOWN_ERROR; + + uint8_t overlayTypeId() const { + if (mData == NULL) { + return 0; + } + return dtohs(mData[1]); } - const uint32_t entryCount = map[typeOffset]; - const uint32_t entryOffset = map[typeOffset + 1]; - if (entryCount == 0 || entry < entryOffset || entry - entryOffset > entryCount - 1) { - *outValue = 0; + + status_t setTo(const void* entryHeader, size_t size) { + if (reinterpret_cast<uintptr_t>(entryHeader) & 0x03) { + ALOGE("idmap: entry header is not word aligned"); + return UNKNOWN_ERROR; + } + + if (size < sizeof(uint16_t) * 4) { + ALOGE("idmap: entry header is too small (%u bytes)", (uint32_t) size); + return UNKNOWN_ERROR; + } + + const uint16_t* header = reinterpret_cast<const uint16_t*>(entryHeader); + const uint16_t targetTypeId = dtohs(header[0]); + const uint16_t overlayTypeId = dtohs(header[1]); + if (targetTypeId == 0 || overlayTypeId == 0 || targetTypeId > 255 || overlayTypeId > 255) { + ALOGE("idmap: invalid type map (%u -> %u)", targetTypeId, overlayTypeId); + return UNKNOWN_ERROR; + } + + uint16_t entryCount = dtohs(header[2]); + if (size < sizeof(uint32_t) * (entryCount + 2)) { + ALOGE("idmap: too small (%u bytes) for the number of entries (%u)", + (uint32_t) size, (uint32_t) entryCount); + return UNKNOWN_ERROR; + } + mData = header; return NO_ERROR; } - const uint32_t index = typeOffset + 2 + entry - entryOffset; - if (index > size) { - ALOGW("Resource ID map: entry index=%u exceeds size of map=%d\n", index, (int)size); - *outValue = 0; + + status_t lookup(uint16_t entryId, uint16_t* outEntryId) const { + uint16_t entryCount = dtohs(mData[2]); + uint16_t offset = dtohs(mData[3]); + + if (entryId < offset) { + // The entry is not present in this idmap + return BAD_INDEX; + } + + entryId -= offset; + + if (entryId >= entryCount) { + // The entry is not present in this idmap + return BAD_INDEX; + } + + // It is safe to access the type here without checking the size because + // we have checked this when it was first loaded. + const uint32_t* entries = reinterpret_cast<const uint32_t*>(mData) + 2; + uint32_t mappedEntry = dtohl(entries[entryId]); + if (mappedEntry == 0xffffffff) { + // This entry is not present in this idmap + return BAD_INDEX; + } + *outEntryId = static_cast<uint16_t>(mappedEntry); return NO_ERROR; } - *outValue = map[index]; - return NO_ERROR; -} +private: + const uint16_t* mData; +}; -static status_t getIdmapPackageId(const uint32_t* map, size_t mapSize, uint32_t *outId) -{ - if (!assertIdmapHeader(map, mapSize)) { +status_t parseIdmap(const void* idmap, size_t size, uint8_t* outPackageId, KeyedVector<uint8_t, IdmapEntries>* outMap) { + if (!assertIdmapHeader(idmap, size)) { return UNKNOWN_ERROR; } - if (mapSize <= IDMAP_HEADER_SIZE + 1) { - ALOGW("corrupt idmap: map size %d too short\n", (int)mapSize); + + size -= ResTable::IDMAP_HEADER_SIZE_BYTES; + if (size < sizeof(uint16_t) * 2) { + ALOGE("idmap: too small to contain any mapping"); return UNKNOWN_ERROR; } - uint32_t typeCount = *(map + IDMAP_HEADER_SIZE); - if (typeCount == 0) { - ALOGW("corrupt idmap: no types\n"); + + const uint16_t* data = reinterpret_cast<const uint16_t*>( + reinterpret_cast<const uint8_t*>(idmap) + ResTable::IDMAP_HEADER_SIZE_BYTES); + + uint16_t targetPackageId = dtohs(*(data++)); + if (targetPackageId == 0 || targetPackageId > 255) { + ALOGE("idmap: target package ID is invalid (%02x)", targetPackageId); return UNKNOWN_ERROR; } - if (IDMAP_HEADER_SIZE + 1 + typeCount > mapSize) { - ALOGW("corrupt idmap: number of types %u extends past idmap size %d\n", typeCount, (int)mapSize); + + uint16_t mapCount = dtohs(*(data++)); + if (mapCount == 0) { + ALOGE("idmap: no mappings"); return UNKNOWN_ERROR; } - const uint32_t* p = map + IDMAP_HEADER_SIZE + 1; - // find first defined type - while (*p == 0) { - ++p; - if (--typeCount == 0) { - ALOGW("corrupt idmap: types declared, none found\n"); - return UNKNOWN_ERROR; - } + + if (mapCount > 255) { + ALOGW("idmap: too many mappings. Only 255 are possible but %u are present", (uint32_t) mapCount); } - // determine package id from first entry of first type - const uint32_t offset = *p + IDMAP_HEADER_SIZE + 2; - if (offset > mapSize) { - ALOGW("corrupt idmap: entry offset %u points outside map size %d\n", offset, (int)mapSize); - return UNKNOWN_ERROR; + while (size > sizeof(uint16_t) * 4) { + IdmapEntries entries; + status_t err = entries.setTo(data, size); + if (err != NO_ERROR) { + return err; + } + + ssize_t index = outMap->add(entries.overlayTypeId(), entries); + if (index < 0) { + return NO_MEMORY; + } + + data += entries.byteSize() / sizeof(uint16_t); + size -= entries.byteSize(); } - *outId = (map[offset] >> 24) & 0x000000ff; + if (outPackageId != NULL) { + *outPackageId = static_cast<uint8_t>(targetPackageId); + } return NO_ERROR; } @@ -2726,7 +2808,7 @@ struct ResTable::Header free(resourceIDMap); } - ResTable* const owner; + const ResTable* const owner; void* ownedData; const ResTable_header* header; size_t size; @@ -2739,6 +2821,17 @@ struct ResTable::Header size_t resourceIDMapSize; }; +struct ResTable::Entry { + ResTable_config config; + const ResTable_entry* entry; + const ResTable_type* type; + uint32_t specFlags; + const Package* package; + + StringPoolRef typeStr; + StringPoolRef keyStr; +}; + struct ResTable::Type { Type(const Header* _header, const Package* _package, size_t count) @@ -2749,33 +2842,29 @@ struct ResTable::Type const size_t entryCount; const ResTable_typeSpec* typeSpec; const uint32_t* typeSpecFlags; + IdmapEntries idmapEntries; Vector<const ResTable_type*> configs; }; struct ResTable::Package { Package(ResTable* _owner, const Header* _header, const ResTable_package* _package) - : owner(_owner), header(_header), package(_package) { } - ~Package() - { - size_t i = types.size(); - while (i > 0) { - i--; - delete types[i]; + : owner(_owner), header(_header), package(_package), typeIdOffset(0) { + if (dtohs(package->header.headerSize) == sizeof(package)) { + // The package structure is the same size as the definition. + // This means it contains the typeIdOffset field. + typeIdOffset = package->typeIdOffset; } } - ResTable* const owner; + const ResTable* const owner; const Header* const header; const ResTable_package* const package; - Vector<Type*> types; ResStringPool typeStrings; ResStringPool keyStrings; - const Type* getType(size_t idx) const { - return idx < types.size() ? types[idx] : NULL; - } + size_t typeIdOffset; }; // A group of objects describing a particular resource package. @@ -2787,13 +2876,24 @@ struct ResTable::PackageGroup : owner(_owner) , name(_name) , id(_id) - , typeCount(0) + , largestTypeId(0) , bags(NULL) , dynamicRefTable(static_cast<uint8_t>(_id)) { } ~PackageGroup() { clearBagCache(); + const size_t numTypes = types.size(); + for (size_t i = 0; i < numTypes; i++) { + const TypeList& typeList = types[i]; + const size_t numInnerTypes = typeList.size(); + for (size_t j = 0; j < numInnerTypes; j++) { + if (typeList[j]->package->owner == owner) { + delete typeList[j]; + } + } + } + const size_t N = packages.size(); for (size_t i=0; i<N; i++) { Package* pkg = packages[i]; @@ -2806,17 +2906,15 @@ struct ResTable::PackageGroup void clearBagCache() { if (bags) { TABLE_NOISY(printf("bags=%p\n", bags)); - Package* pkg = packages[0]; - TABLE_NOISY(printf("typeCount=%x\n", typeCount)); - for (size_t i=0; i<typeCount; i++) { + for (size_t i = 0; i < bags->size(); i++) { TABLE_NOISY(printf("type=%d\n", i)); - const Type* type = pkg->getType(i); - if (type != NULL) { - bag_set** typeBags = bags[i]; + const TypeList& typeList = types[i]; + if (typeList.isEmpty()) { + bag_set** typeBags = bags->get(i); TABLE_NOISY(printf("typeBags=%p\n", typeBags)); if (typeBags) { - TABLE_NOISY(printf("type->entryCount=%x\n", type->entryCount)); - const size_t N = type->entryCount; + const size_t N = typeList[0]->entryCount; + TABLE_NOISY(printf("type->entryCount=%x\n", N)); for (size_t j=0; j<N; j++) { if (typeBags[j] && typeBags[j] != (bag_set*)0xFFFFFFFF) free(typeBags[j]); @@ -2825,25 +2923,38 @@ struct ResTable::PackageGroup } } } - free(bags); + delete bags; bags = NULL; } } - ResTable* const owner; + ssize_t findType16(const char16_t* type, size_t len) const { + const size_t N = packages.size(); + for (size_t i = 0; i < N; i++) { + ssize_t index = packages[i]->typeStrings.indexOfString(type, len); + if (index >= 0) { + return index + packages[i]->typeIdOffset; + } + } + return -1; + } + + const ResTable* const owner; String16 const name; uint32_t const id; + + // This is mainly used to keep track of the loaded packages + // and to clean them up properly. Accessing resources happens from + // the 'types' array. Vector<Package*> packages; - // This is for finding typeStrings and other common package stuff. - Package* basePackage; + ByteBucketArray<TypeList> types; - // For quick access. - size_t typeCount; + uint8_t largestTypeId; // Computed attribute bags, first indexed by the type and second // by the entry in that type. - bag_set*** bags; + ByteBucketArray<bag_set**>* bags; // The table mapping dynamic references to resolved references for // this package group. @@ -2879,7 +2990,7 @@ ResTable::Theme::~Theme() void ResTable::Theme::free_package(package_info* pi) { - for (size_t j=0; j<pi->numTypes; j++) { + for (size_t j = 0; j <= Res_MAXTYPE; j++) { theme_entry* te = pi->types[j].entries; if (te != NULL) { free(te); @@ -2890,10 +3001,8 @@ void ResTable::Theme::free_package(package_info* pi) ResTable::Theme::package_info* ResTable::Theme::copy_package(package_info* pi) { - package_info* newpi = (package_info*)malloc( - sizeof(package_info) + (pi->numTypes*sizeof(type_info))); - newpi->numTypes = pi->numTypes; - for (size_t j=0; j<newpi->numTypes; j++) { + package_info* newpi = (package_info*)malloc(sizeof(package_info)); + for (size_t j = 0; j <= Res_MAXTYPE; j++) { size_t cnt = pi->types[j].numEntries; newpi->types[j].numEntries = cnt; theme_entry* te = pi->types[j].entries; @@ -2946,17 +3055,14 @@ status_t ResTable::Theme::applyStyle(uint32_t resID, bool force) curPI = mPackages[pidx]; if (curPI == NULL) { PackageGroup* const grp = mTable.mPackageGroups[pidx]; - int cnt = grp->typeCount; - curPI = (package_info*)malloc( - sizeof(package_info) + (cnt*sizeof(type_info))); - curPI->numTypes = cnt; - memset(curPI->types, 0, cnt*sizeof(type_info)); + curPI = (package_info*)malloc(sizeof(package_info)); + memset(curPI, 0, sizeof(*curPI)); mPackages[pidx] = curPI; } curType = 0xffffffff; } if (curType != t) { - if (t >= curPI->numTypes) { + if (t > Res_MAXTYPE) { ALOGE("Style contains key with bad type: 0x%08x\n", attrRes); bag++; continue; @@ -2965,8 +3071,8 @@ status_t ResTable::Theme::applyStyle(uint32_t resID, bool force) curEntries = curPI->types[t].entries; if (curEntries == NULL) { PackageGroup* const grp = mTable.mPackageGroups[curPackageIndex]; - const Type* type = grp->packages[0]->getType(t); - int cnt = type != NULL ? type->entryCount : 0; + const TypeList& typeList = grp->types[t]; + int cnt = typeList.isEmpty() ? 0 : typeList[0]->entryCount; curEntries = (theme_entry*)malloc(cnt*sizeof(theme_entry)); memset(curEntries, Res_value::TYPE_NULL, cnt*sizeof(theme_entry)); curPI->types[t].numEntries = cnt; @@ -2981,8 +3087,8 @@ status_t ResTable::Theme::applyStyle(uint32_t resID, bool force) } theme_entry* curEntry = curEntries + e; TABLE_NOISY(ALOGV("Attr 0x%08x: type=0x%x, data=0x%08x; curType=0x%x", - attrRes, bag->map.value.dataType, bag->map.value.data, - curEntry->value.dataType)); + attrRes, bag->map.value.dataType, bag->map.value.data, + curEntry->value.dataType)); if (force || curEntry->value.dataType == Res_value::TYPE_NULL) { curEntry->stringBlock = bag->stringBlock; curEntry->typeSpecFlags |= bagTypeSpecFlags; @@ -3057,8 +3163,8 @@ ssize_t ResTable::Theme::getAttribute(uint32_t resID, Res_value* outValue, const package_info* const pi = mPackages[p]; TABLE_THEME(ALOGI("Found package: %p", pi)); if (pi != NULL) { - TABLE_THEME(ALOGI("Desired type index is %ld in avail %d", t, pi->numTypes)); - if (t < pi->numTypes) { + TABLE_THEME(ALOGI("Desired type index is %ld in avail %d", t, Res_MAXTYPE + 1)); + if (t <= Res_MAXTYPE) { const type_info& ti = pi->types[t]; TABLE_THEME(ALOGI("Desired entry index is %ld in avail %d", e, ti.numEntries)); if (e < ti.numEntries) { @@ -3120,14 +3226,13 @@ void ResTable::Theme::dumpToLog() const package_info* pi = mPackages[i]; if (pi == NULL) continue; - ALOGI(" Package #0x%02x:\n", (int)(i+1)); - for (size_t j=0; j<pi->numTypes; j++) { + ALOGI(" Package #0x%02x:\n", (int)(i + 1)); + for (size_t j = 0; j <= Res_MAXTYPE; j++) { type_info& ti = pi->types[j]; if (ti.numEntries == 0) continue; - - ALOGI(" Type #0x%02x:\n", (int)(j+1)); - for (size_t k=0; k<ti.numEntries; k++) { - theme_entry& te = ti.entries[k]; + ALOGI(" Type #0x%02x:\n", (int)(j + 1)); + for (size_t k = 0; k < ti.numEntries; k++) { + const theme_entry& te = ti.entries[k]; if (te.value.dataType == Res_value::TYPE_NULL) continue; ALOGI(" 0x%08x: t=0x%x, d=0x%08x (block=%d)\n", (int)Res_MAKEID(i, j, k), @@ -3150,7 +3255,7 @@ ResTable::ResTable(const void* data, size_t size, const int32_t cookie, bool cop { memset(&mParams, 0, sizeof(mParams)); memset(mPackageMap, 0, sizeof(mPackageMap)); - addInternal(data, size, cookie, copyData, NULL /* idMap */); + addInternal(data, size, NULL, 0, cookie, copyData); LOG_FATAL_IF(mError != NO_ERROR, "Error parsing resource table"); //ALOGI("Creating ResTable %p\n", this); } @@ -3166,21 +3271,45 @@ inline ssize_t ResTable::getResourcePackageIndex(uint32_t resID) const return ((ssize_t)mPackageMap[Res_GETPACKAGE(resID)+1])-1; } -status_t ResTable::add(const void* data, size_t size) { - return addInternal(data, size, 0 /* cookie */, - false /* copyData */, NULL /* idMap */); +status_t ResTable::add(const void* data, size_t size, const int32_t cookie, bool copyData) { + return addInternal(data, size, NULL, 0, cookie, copyData); } -status_t ResTable::add(Asset* asset, const int32_t cookie, bool copyData, const void* idmap) -{ +status_t ResTable::add(const void* data, size_t size, const void* idmapData, size_t idmapDataSize, + const int32_t cookie, bool copyData) { + return addInternal(data, size, idmapData, idmapDataSize, cookie, copyData); +} + +status_t ResTable::add(Asset* asset, const int32_t cookie, bool copyData) { const void* data = asset->getBuffer(true); if (data == NULL) { ALOGW("Unable to get buffer of resource asset file"); return UNKNOWN_ERROR; } - size_t size = (size_t)asset->getLength(); - return addInternal(data, size, cookie, copyData, - reinterpret_cast<const Asset*>(idmap)); + + return addInternal(data, static_cast<size_t>(asset->getLength()), NULL, 0, cookie, copyData); +} + +status_t ResTable::add(Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData) { + const void* data = asset->getBuffer(true); + if (data == NULL) { + ALOGW("Unable to get buffer of resource asset file"); + return UNKNOWN_ERROR; + } + + size_t idmapSize = 0; + const void* idmapData = NULL; + if (idmapAsset != NULL) { + idmapData = idmapAsset->getBuffer(true); + if (idmapData == NULL) { + ALOGW("Unable to get buffer of idmap asset file"); + return UNKNOWN_ERROR; + } + idmapSize = static_cast<size_t>(idmapAsset->getLength()); + } + + return addInternal(data, static_cast<size_t>(asset->getLength()), + idmapData, idmapSize, cookie, copyData); } status_t ResTable::add(ResTable* src) @@ -3197,8 +3326,16 @@ status_t ResTable::add(ResTable* src) for (size_t j=0; j<srcPg->packages.size(); j++) { pg->packages.add(srcPg->packages[j]); } - pg->basePackage = srcPg->basePackage; - pg->typeCount = srcPg->typeCount; + + for (size_t j = 0; j < srcPg->types.size(); j++) { + if (srcPg->types[j].isEmpty()) { + continue; + } + + TypeList& typeList = pg->types.editItemAt(j); + typeList.appendVector(srcPg->types[j]); + } + pg->largestTypeId = max(pg->largestTypeId, srcPg->largestTypeId); mPackageGroups.add(pg); } @@ -3224,38 +3361,39 @@ status_t ResTable::addEmpty(const int32_t cookie) { return (mError=NO_ERROR); } -status_t ResTable::addInternal(const void* data, size_t size, const int32_t cookie, - bool copyData, const Asset* idmap) +status_t ResTable::addInternal(const void* data, size_t dataSize, const void* idmapData, size_t idmapDataSize, + const int32_t cookie, bool copyData) { - if (!data) return NO_ERROR; + if (!data) { + return NO_ERROR; + } + Header* header = new Header(this); header->index = mHeaders.size(); header->cookie = cookie; - if (idmap != NULL) { - const size_t idmap_size = idmap->getLength(); - const void* idmap_data = const_cast<Asset*>(idmap)->getBuffer(true); - header->resourceIDMap = (uint32_t*)malloc(idmap_size); + if (idmapData != NULL) { + header->resourceIDMap = (uint32_t*) malloc(idmapDataSize); if (header->resourceIDMap == NULL) { delete header; return (mError = NO_MEMORY); } - memcpy((void*)header->resourceIDMap, idmap_data, idmap_size); - header->resourceIDMapSize = idmap_size; + memcpy(header->resourceIDMap, idmapData, idmapDataSize); + header->resourceIDMapSize = idmapDataSize; } mHeaders.add(header); const bool notDeviceEndian = htods(0xf0) != 0xf0; LOAD_TABLE_NOISY( - ALOGV("Adding resources to ResTable: data=%p, size=0x%x, cookie=%d, asset=%p, copy=%d " - "idmap=%p\n", data, size, cookie, asset, copyData, idmap)); + ALOGV("Adding resources to ResTable: data=%p, size=0x%x, cookie=%d, copy=%d " + "idmap=%p\n", data, dataSize, cookie, copyData, idmap)); if (copyData || notDeviceEndian) { - header->ownedData = malloc(size); + header->ownedData = malloc(dataSize); if (header->ownedData == NULL) { return (mError=NO_MEMORY); } - memcpy(header->ownedData, data, size); + memcpy(header->ownedData, data, dataSize); data = header->ownedData; } @@ -3265,10 +3403,10 @@ status_t ResTable::addInternal(const void* data, size_t size, const int32_t cook // dtohl(header->header->header.size), header->header->header.size); LOAD_TABLE_NOISY(ALOGV("Loading ResTable @%p:\n", header->header)); if (dtohs(header->header->header.headerSize) > header->size - || header->size > size) { + || header->size > dataSize) { ALOGW("Bad resource table: header size 0x%x or total size 0x%x is larger than data size 0x%x\n", (int)dtohs(header->header->header.headerSize), - (int)header->size, (int)size); + (int)header->size, (int)dataSize); return (mError=BAD_TYPE); } if (((dtohs(header->header->header.headerSize)|header->size)&0x3) != 0) { @@ -3313,16 +3451,8 @@ status_t ResTable::addInternal(const void* data, size_t size, const int32_t cook dtohl(header->header->packageCount)); return (mError=BAD_TYPE); } - uint32_t idmap_id = 0; - if (idmap != NULL) { - uint32_t tmp; - if (getIdmapPackageId(header->resourceIDMap, - header->resourceIDMapSize, - &tmp) == NO_ERROR) { - idmap_id = tmp; - } - } - if (parsePackage((ResTable_package*)chunk, header, idmap_id) != NO_ERROR) { + + if (parsePackage((ResTable_package*)chunk, header) != NO_ERROR) { return mError; } curPackage++; @@ -3405,46 +3535,38 @@ bool ResTable::getResourceName(uint32_t resID, bool allowUtf8, resource_name* ou ALOGW("Bad identifier when getting name for resource number 0x%08x", resID); return false; } - if (grp->packages.size() > 0) { - const Package* const package = grp->packages[0]; - const ResTable_type* type; - const ResTable_entry* entry; - ssize_t offset = getEntry(package, t, e, NULL, &type, &entry, NULL); - if (offset <= 0) { - return false; - } + Entry entry; + status_t err = getEntry(grp, t, e, NULL, &entry); + if (err != NO_ERROR) { + return false; + } - outName->package = grp->name.string(); - outName->packageLen = grp->name.size(); - if (allowUtf8) { - outName->type8 = grp->basePackage->typeStrings.string8At(t, &outName->typeLen); - outName->name8 = grp->basePackage->keyStrings.string8At( - dtohl(entry->key.index), &outName->nameLen); - } else { - outName->type8 = NULL; - outName->name8 = NULL; - } - if (outName->type8 == NULL) { - outName->type = grp->basePackage->typeStrings.stringAt(t, &outName->typeLen); - // If we have a bad index for some reason, we should abort. - if (outName->type == NULL) { - return false; - } + outName->package = grp->name.string(); + outName->packageLen = grp->name.size(); + if (allowUtf8) { + outName->type8 = entry.typeStr.string8(&outName->typeLen); + outName->name8 = entry.keyStr.string8(&outName->nameLen); + } else { + outName->type8 = NULL; + outName->name8 = NULL; + } + if (outName->type8 == NULL) { + outName->type = entry.typeStr.string16(&outName->typeLen); + // If we have a bad index for some reason, we should abort. + if (outName->type == NULL) { + return false; } - if (outName->name8 == NULL) { - outName->name = grp->basePackage->keyStrings.stringAt( - dtohl(entry->key.index), &outName->nameLen); - // If we have a bad index for some reason, we should abort. - if (outName->name == NULL) { - return false; - } + } + if (outName->name8 == NULL) { + outName->name = entry.keyStr.string16(&outName->nameLen); + // If we have a bad index for some reason, we should abort. + if (outName->name == NULL) { + return false; } - - return true; } - return false; + return true; } ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag, uint16_t density, @@ -3471,15 +3593,6 @@ ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag return BAD_INDEX; } - const Res_value* bestValue = NULL; - const Package* bestPackage = NULL; - ResTable_config bestItem; - memset(&bestItem, 0, sizeof(bestItem)); // make the compiler shut up - - if (outSpecFlags != NULL) *outSpecFlags = 0; - - // Look through all resource packages, starting with the most - // recently added. const PackageGroup* const grp = mPackageGroups[p]; if (grp == NULL) { ALOGW("Bad identifier when getting value for resource number 0x%08x", resID); @@ -3487,142 +3600,62 @@ ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag } // Allow overriding density - const ResTable_config* desiredConfig = &mParams; - ResTable_config* overrideConfig = NULL; + ResTable_config desiredConfig = mParams; if (density > 0) { - overrideConfig = (ResTable_config*) malloc(sizeof(ResTable_config)); - if (overrideConfig == NULL) { - ALOGE("Couldn't malloc ResTable_config for overrides: %s", strerror(errno)); - return BAD_INDEX; - } - memcpy(overrideConfig, &mParams, sizeof(ResTable_config)); - overrideConfig->density = density; - desiredConfig = overrideConfig; - } - - ssize_t rc = BAD_VALUE; - size_t ip = grp->packages.size(); - while (ip > 0) { - ip--; - int T = t; - int E = e; - - const Package* const package = grp->packages[ip]; - if (package->header->resourceIDMap) { - uint32_t overlayResID = 0x0; - status_t retval = idmapLookup(package->header->resourceIDMap, - package->header->resourceIDMapSize, - resID, &overlayResID); - if (retval == NO_ERROR && overlayResID != 0x0) { - // for this loop iteration, this is the type and entry we really want - ALOGV("resource map 0x%08x -> 0x%08x\n", resID, overlayResID); - T = Res_GETTYPE(overlayResID); - E = Res_GETENTRY(overlayResID); - } else { - // resource not present in overlay package, continue with the next package - continue; - } - } - - const ResTable_type* type; - const ResTable_entry* entry; - const Type* typeClass; - ssize_t offset = getEntry(package, T, E, desiredConfig, &type, &entry, &typeClass); - if (offset <= 0) { - // No {entry, appropriate config} pair found in package. If this - // package is an overlay package (ip != 0), this simply means the - // overlay package did not specify a default. - // Non-overlay packages are still required to provide a default. - if (offset < 0 && ip == 0) { - ALOGW("Failure getting entry for 0x%08x (t=%d e=%d) in package %zd (error %d)\n", - resID, T, E, ip, (int)offset); - rc = offset; - goto out; - } - continue; - } + desiredConfig.density = density; + } - if ((dtohs(entry->flags)&entry->FLAG_COMPLEX) != 0) { - if (!mayBeBag) { - ALOGW("Requesting resource 0x%x failed because it is complex\n", - resID); - } - continue; - } + Entry entry; + status_t err = getEntry(grp, t, e, &desiredConfig, &entry); + if (err != NO_ERROR) { + ALOGW("Failure getting entry for 0x%08x (t=%d e=%d) (error %d)\n", + resID, t, e, err); + return err; + } - if ((size_t)offset > (dtohl(type->header.size)-sizeof(Res_value))) { - ALOGW("ResTable_item at %d is beyond type chunk data %d", - (int)offset, dtohl(type->header.size)); - rc = BAD_TYPE; - goto out; + if ((dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) != 0) { + if (!mayBeBag) { + ALOGW("Requesting resource 0x%08x failed because it is complex\n", resID); } + return BAD_VALUE; + } - const Res_value* item = - (const Res_value*)(((const uint8_t*)type) + offset); - ResTable_config thisConfig; - thisConfig.copyFromDtoH(type->config); + const Res_value* value = reinterpret_cast<const Res_value*>( + reinterpret_cast<const uint8_t*>(entry.entry) + entry.entry->size); - if (outSpecFlags != NULL) { - if (typeClass->typeSpecFlags != NULL) { - *outSpecFlags |= dtohl(typeClass->typeSpecFlags[E]); - } else { - *outSpecFlags = -1; - } - } + outValue->size = dtohs(value->size); + outValue->res0 = value->res0; + outValue->dataType = value->dataType; + outValue->data = dtohl(value->data); - if (bestPackage != NULL && - (bestItem.isMoreSpecificThan(thisConfig) || bestItem.diff(thisConfig) == 0)) { - // Discard thisConfig not only if bestItem is more specific, but also if the two configs - // are identical (diff == 0), or overlay packages will not take effect. - continue; - } - - bestItem = thisConfig; - bestValue = item; - bestPackage = package; + // The reference may be pointing to a resource in a shared library. These + // references have build-time generated package IDs. These ids may not match + // the actual package IDs of the corresponding packages in this ResTable. + // We need to fix the package ID based on a mapping. + if (grp->dynamicRefTable.lookupResourceValue(outValue) != NO_ERROR) { + ALOGW("Failed to resolve referenced package: 0x%08x", outValue->data); + return BAD_VALUE; } - TABLE_NOISY(printf("Found result: package %p\n", bestPackage)); - - if (bestValue) { - outValue->size = dtohs(bestValue->size); - outValue->res0 = bestValue->res0; - outValue->dataType = bestValue->dataType; - outValue->data = dtohl(bestValue->data); - - // The reference may be pointing to a resource in a shared library. These - // references have build-time generated package IDs. These ids may not match - // the actual package IDs of the corresponding packages in this ResTable. - // We need to fix the package ID based on a mapping. - status_t err = grp->dynamicRefTable.lookupResourceValue(outValue); - if (err != NO_ERROR) { - ALOGW("Failed to resolve referenced package: 0x%08x", outValue->data); - rc = BAD_VALUE; - goto out; - } + TABLE_NOISY(size_t len; + printf("Found value: pkg=%d, type=%d, str=%s, int=%d\n", + entry.package->header->index, + outValue->dataType, + outValue->dataType == Res_value::TYPE_STRING + ? String8(entry.package->header->values.stringAt( + outValue->data, &len)).string() + : "", + outValue->data)); - if (outConfig != NULL) { - *outConfig = bestItem; - } - TABLE_NOISY(size_t len; - printf("Found value: pkg=%d, type=%d, str=%s, int=%d\n", - bestPackage->header->index, - outValue->dataType, - outValue->dataType == bestValue->TYPE_STRING - ? String8(bestPackage->header->values.stringAt( - outValue->data, &len)).string() - : "", - outValue->data)); - rc = bestPackage->header->index; - goto out; + if (outSpecFlags != NULL) { + *outSpecFlags = entry.specFlags; } -out: - if (overrideConfig != NULL) { - free(overrideConfig); + if (outConfig != NULL) { + *outConfig = entry.config; } - return rc; + return entry.package->header->index; } ssize_t ResTable::resolveReference(Res_value* value, ssize_t blockIndex, @@ -3721,29 +3754,25 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag, PackageGroup* const grp = mPackageGroups[p]; if (grp == NULL) { ALOGW("Bad identifier when getting bag for resource number 0x%08x", resID); - return false; + return BAD_INDEX; } - if (t >= (int)grp->typeCount) { - ALOGW("Type identifier 0x%x is larger than type count 0x%x", - t+1, (int)grp->typeCount); + const TypeList& typeConfigs = grp->types[t]; + if (typeConfigs.isEmpty()) { + ALOGW("Type identifier 0x%x does not exist.", t+1); return BAD_INDEX; } - const Package* const basePackage = grp->packages[0]; - - const Type* const typeConfigs = basePackage->getType(t); - - const size_t NENTRY = typeConfigs->entryCount; + const size_t NENTRY = typeConfigs[0]->entryCount; if (e >= (int)NENTRY) { ALOGW("Entry identifier 0x%x is larger than entry count 0x%x", - e, (int)typeConfigs->entryCount); + e, (int)typeConfigs[0]->entryCount); return BAD_INDEX; } // First see if we've already computed this bag... if (grp->bags) { - bag_set** typeSet = grp->bags[t]; + bag_set** typeSet = grp->bags->get(t); if (typeSet) { bag_set* set = typeSet[e]; if (set) { @@ -3764,229 +3793,174 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag, // Bag not found, we need to compute it! if (!grp->bags) { - grp->bags = (bag_set***)calloc(grp->typeCount, sizeof(bag_set*)); + grp->bags = new ByteBucketArray<bag_set**>(); if (!grp->bags) return NO_MEMORY; } - bag_set** typeSet = grp->bags[t]; + bag_set** typeSet = grp->bags->get(t); if (!typeSet) { typeSet = (bag_set**)calloc(NENTRY, sizeof(bag_set*)); if (!typeSet) return NO_MEMORY; - grp->bags[t] = typeSet; + grp->bags->set(t, typeSet); } // Mark that we are currently working on this one. typeSet[e] = (bag_set*)0xFFFFFFFF; - // This is what we are building. - bag_set* set = NULL; - TABLE_NOISY(ALOGI("Building bag: %p\n", (void*)resID)); - ResTable_config bestConfig; - memset(&bestConfig, 0, sizeof(bestConfig)); + // Now collect all bag attributes + Entry entry; + status_t err = getEntry(grp, t, e, &mParams, &entry); + if (err != NO_ERROR) { + return err; + } - // Now collect all bag attributes from all packages. - size_t ip = grp->packages.size(); - while (ip > 0) { - ip--; - int T = t; - int E = e; - - const Package* const package = grp->packages[ip]; - if (package->header->resourceIDMap) { - uint32_t overlayResID = 0x0; - status_t retval = idmapLookup(package->header->resourceIDMap, - package->header->resourceIDMapSize, - resID, &overlayResID); - if (retval == NO_ERROR && overlayResID != 0x0) { - // for this loop iteration, this is the type and entry we really want - ALOGV("resource map 0x%08x -> 0x%08x\n", resID, overlayResID); - T = Res_GETTYPE(overlayResID); - E = Res_GETENTRY(overlayResID); - } else { - // resource not present in overlay package, continue with the next package - continue; - } - } + const uint16_t entrySize = dtohs(entry.entry->size); + const uint32_t parent = entrySize >= sizeof(ResTable_map_entry) + ? dtohl(((const ResTable_map_entry*)entry.entry)->parent.ident) : 0; + const uint32_t count = entrySize >= sizeof(ResTable_map_entry) + ? dtohl(((const ResTable_map_entry*)entry.entry)->count) : 0; - const ResTable_type* type; - const ResTable_entry* entry; - const Type* typeClass; - ALOGV("Getting entry pkg=%p, t=%d, e=%d\n", package, T, E); - ssize_t offset = getEntry(package, T, E, &mParams, &type, &entry, &typeClass); - ALOGV("Resulting offset=%d\n", (int)offset); - if (offset <= 0) { - // No {entry, appropriate config} pair found in package. If this - // package is an overlay package (ip != 0), this simply means the - // overlay package did not specify a default. - // Non-overlay packages are still required to provide a default. - if (offset < 0 && ip == 0) { - if (set) free(set); - return offset; - } - continue; - } + size_t N = count; - if ((dtohs(entry->flags)&entry->FLAG_COMPLEX) == 0) { - ALOGW("Skipping entry 0x%x in package table %zu because it is not complex!\n", - resID, ip); - continue; + TABLE_NOISY(ALOGI("Found map: size=%p parent=%p count=%d\n", + entrySize, parent, count)); + + // If this map inherits from another, we need to start + // with its parent's values. Otherwise start out empty. + TABLE_NOISY(printf("Creating new bag, entrySize=0x%08x, parent=0x%08x\n", + entrySize, parent)); + + // This is what we are building. + bag_set* set = NULL; + + if (parent) { + uint32_t resolvedParent = parent; + + // Bags encode a parent reference without using the standard + // Res_value structure. That means we must always try to + // resolve a parent reference in case it is actually a + // TYPE_DYNAMIC_REFERENCE. + status_t err = grp->dynamicRefTable.lookupResourceId(&resolvedParent); + if (err != NO_ERROR) { + ALOGE("Failed resolving bag parent id 0x%08x", parent); + return UNKNOWN_ERROR; } - if (set != NULL && !type->config.isBetterThan(bestConfig, NULL)) { - continue; + const bag_entry* parentBag; + uint32_t parentTypeSpecFlags = 0; + const ssize_t NP = getBagLocked(resolvedParent, &parentBag, &parentTypeSpecFlags); + const size_t NT = ((NP >= 0) ? NP : 0) + N; + set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*NT); + if (set == NULL) { + return NO_MEMORY; } - bestConfig = type->config; - if (set) { - free(set); - set = NULL; + if (NP > 0) { + memcpy(set+1, parentBag, NP*sizeof(bag_entry)); + set->numAttrs = NP; + TABLE_NOISY(ALOGI("Initialized new bag with %d inherited attributes.\n", NP)); + } else { + TABLE_NOISY(ALOGI("Initialized new bag with no inherited attributes.\n")); + set->numAttrs = 0; } + set->availAttrs = NT; + set->typeSpecFlags = parentTypeSpecFlags; + } else { + set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*N); + if (set == NULL) { + return NO_MEMORY; + } + set->numAttrs = 0; + set->availAttrs = N; + set->typeSpecFlags = 0; + } - const uint16_t entrySize = dtohs(entry->size); - const uint32_t parent = entrySize >= sizeof(ResTable_map_entry) - ? dtohl(((const ResTable_map_entry*)entry)->parent.ident) : 0; - const uint32_t count = entrySize >= sizeof(ResTable_map_entry) - ? dtohl(((const ResTable_map_entry*)entry)->count) : 0; - - size_t N = count; + set->typeSpecFlags |= entry.specFlags; - TABLE_NOISY(ALOGI("Found map: size=%p parent=%p count=%d\n", - entrySize, parent, count)); + // Now merge in the new attributes... + size_t curOff = (reinterpret_cast<uintptr_t>(entry.entry) - reinterpret_cast<uintptr_t>(entry.type)) + + dtohs(entry.entry->size); + const ResTable_map* map; + bag_entry* entries = (bag_entry*)(set+1); + size_t curEntry = 0; + uint32_t pos = 0; + TABLE_NOISY(ALOGI("Starting with set %p, entries=%p, avail=%d\n", + set, entries, set->availAttrs)); + while (pos < count) { + TABLE_NOISY(printf("Now at %p\n", (void*)curOff)); - // If this map inherits from another, we need to start - // with its parent's values. Otherwise start out empty. - TABLE_NOISY(printf("Creating new bag, entrySize=0x%08x, parent=0x%08x\n", - entrySize, parent)); - if (parent) { - uint32_t resolvedParent = parent; + if (curOff > (dtohl(entry.type->header.size)-sizeof(ResTable_map))) { + ALOGW("ResTable_map at %d is beyond type chunk data %d", + (int)curOff, dtohl(entry.type->header.size)); + return BAD_TYPE; + } + map = (const ResTable_map*)(((const uint8_t*)entry.type) + curOff); + N++; - // Bags encode a parent reference without using the standard - // Res_value structure. That means we must always try to - // resolve a parent reference in case it is actually a - // TYPE_DYNAMIC_REFERENCE. - status_t err = grp->dynamicRefTable.lookupResourceId(&resolvedParent); - if (err != NO_ERROR) { - ALOGE("Failed resolving bag parent id 0x%08x", parent); - return UNKNOWN_ERROR; - } + const uint32_t newName = htodl(map->name.ident); + bool isInside; + uint32_t oldName = 0; + while ((isInside=(curEntry < set->numAttrs)) + && (oldName=entries[curEntry].map.name.ident) < newName) { + TABLE_NOISY(printf("#%d: Keeping existing attribute: 0x%08x\n", + curEntry, entries[curEntry].map.name.ident)); + curEntry++; + } - const bag_entry* parentBag; - uint32_t parentTypeSpecFlags = 0; - const ssize_t NP = getBagLocked(resolvedParent, &parentBag, &parentTypeSpecFlags); - const size_t NT = ((NP >= 0) ? NP : 0) + N; - set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*NT); - if (set == NULL) { - return NO_MEMORY; - } - if (NP > 0) { - memcpy(set+1, parentBag, NP*sizeof(bag_entry)); - set->numAttrs = NP; - TABLE_NOISY(ALOGI("Initialized new bag with %d inherited attributes.\n", NP)); - } else { - TABLE_NOISY(ALOGI("Initialized new bag with no inherited attributes.\n")); - set->numAttrs = 0; - } - set->availAttrs = NT; - set->typeSpecFlags = parentTypeSpecFlags; + if ((!isInside) || oldName != newName) { + // This is a new attribute... figure out what to do with it. + if (set->numAttrs >= set->availAttrs) { + // Need to alloc more memory... + const size_t newAvail = set->availAttrs+N; + set = (bag_set*)realloc(set, + sizeof(bag_set) + + sizeof(bag_entry)*newAvail); + if (set == NULL) { + return NO_MEMORY; + } + set->availAttrs = newAvail; + entries = (bag_entry*)(set+1); + TABLE_NOISY(printf("Reallocated set %p, entries=%p, avail=%d\n", + set, entries, set->availAttrs)); + } + if (isInside) { + // Going in the middle, need to make space. + memmove(entries+curEntry+1, entries+curEntry, + sizeof(bag_entry)*(set->numAttrs-curEntry)); + set->numAttrs++; + } + TABLE_NOISY(printf("#%d: Inserting new attribute: 0x%08x\n", + curEntry, newName)); } else { - set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*N); - if (set == NULL) { - return NO_MEMORY; - } - set->numAttrs = 0; - set->availAttrs = N; - set->typeSpecFlags = 0; + TABLE_NOISY(printf("#%d: Replacing existing attribute: 0x%08x\n", + curEntry, oldName)); } - if (typeClass->typeSpecFlags != NULL) { - set->typeSpecFlags |= dtohl(typeClass->typeSpecFlags[E]); - } else { - set->typeSpecFlags = -1; - } - - // Now merge in the new attributes... - ssize_t curOff = offset; - const ResTable_map* map; - bag_entry* entries = (bag_entry*)(set+1); - size_t curEntry = 0; - uint32_t pos = 0; - TABLE_NOISY(ALOGI("Starting with set %p, entries=%p, avail=%d\n", - set, entries, set->availAttrs)); - while (pos < count) { - TABLE_NOISY(printf("Now at %p\n", (void*)curOff)); - - if ((size_t)curOff > (dtohl(type->header.size)-sizeof(ResTable_map))) { - ALOGW("ResTable_map at %d is beyond type chunk data %d", - (int)curOff, dtohl(type->header.size)); - return BAD_TYPE; - } - map = (const ResTable_map*)(((const uint8_t*)type) + curOff); - N++; - - const uint32_t newName = htodl(map->name.ident); - bool isInside; - uint32_t oldName = 0; - while ((isInside=(curEntry < set->numAttrs)) - && (oldName=entries[curEntry].map.name.ident) < newName) { - TABLE_NOISY(printf("#%d: Keeping existing attribute: 0x%08x\n", - curEntry, entries[curEntry].map.name.ident)); - curEntry++; - } - - if ((!isInside) || oldName != newName) { - // This is a new attribute... figure out what to do with it. - if (set->numAttrs >= set->availAttrs) { - // Need to alloc more memory... - const size_t newAvail = set->availAttrs+N; - set = (bag_set*)realloc(set, - sizeof(bag_set) - + sizeof(bag_entry)*newAvail); - if (set == NULL) { - return NO_MEMORY; - } - set->availAttrs = newAvail; - entries = (bag_entry*)(set+1); - TABLE_NOISY(printf("Reallocated set %p, entries=%p, avail=%d\n", - set, entries, set->availAttrs)); - } - if (isInside) { - // Going in the middle, need to make space. - memmove(entries+curEntry+1, entries+curEntry, - sizeof(bag_entry)*(set->numAttrs-curEntry)); - set->numAttrs++; - } - TABLE_NOISY(printf("#%d: Inserting new attribute: 0x%08x\n", - curEntry, newName)); - } else { - TABLE_NOISY(printf("#%d: Replacing existing attribute: 0x%08x\n", - curEntry, oldName)); - } + bag_entry* cur = entries+curEntry; - bag_entry* cur = entries+curEntry; + cur->stringBlock = entry.package->header->index; + cur->map.name.ident = newName; + cur->map.value.copyFrom_dtoh(map->value); + status_t err = grp->dynamicRefTable.lookupResourceValue(&cur->map.value); + if (err != NO_ERROR) { + ALOGE("Reference item(0x%08x) in bag could not be resolved.", cur->map.value.data); + return UNKNOWN_ERROR; + } - cur->stringBlock = package->header->index; - cur->map.name.ident = newName; - cur->map.value.copyFrom_dtoh(map->value); - status_t err = grp->dynamicRefTable.lookupResourceValue(&cur->map.value); - if (err != NO_ERROR) { - ALOGE("Reference item(0x%08x) in bag could not be resolved.", cur->map.value.data); - return UNKNOWN_ERROR; - } + TABLE_NOISY(printf("Setting entry #%d %p: block=%d, name=0x%08x, type=%d, data=0x%08x\n", + curEntry, cur, cur->stringBlock, cur->map.name.ident, + cur->map.value.dataType, cur->map.value.data)); - TABLE_NOISY(printf("Setting entry #%d %p: block=%d, name=0x%08x, type=%d, data=0x%08x\n", - curEntry, cur, cur->stringBlock, cur->map.name.ident, - cur->map.value.dataType, cur->map.value.data)); + // On to the next! + curEntry++; + pos++; + const size_t size = dtohs(map->value.size); + curOff += size + sizeof(*map)-sizeof(map->value); + }; - // On to the next! - curEntry++; - pos++; - const size_t size = dtohs(map->value.size); - curOff += size + sizeof(*map)-sizeof(map->value); - }; - if (curEntry > set->numAttrs) { - set->numAttrs = curEntry; - } + if (curEntry > set->numAttrs) { + set->numAttrs = curEntry; } // And this is it... @@ -4154,80 +4128,63 @@ nope: continue; } - const ssize_t ti = group->basePackage->typeStrings.indexOfString(type, typeLen); + const ssize_t ti = group->findType16(type, typeLen); if (ti < 0) { TABLE_NOISY(printf("Type not found in package %s\n", String8(group->name).string())); continue; } - const ssize_t ei = group->basePackage->keyStrings.indexOfString(name, nameLen); - if (ei < 0) { - TABLE_NOISY(printf("Name not found in package %s\n", String8(group->name).string())); - continue; - } - - TABLE_NOISY(printf("Search indices: type=%d, name=%d\n", ti, ei)); - - const Type* const typeConfigs = group->packages[0]->getType(ti); - if (typeConfigs == NULL || typeConfigs->configs.size() <= 0) { - TABLE_NOISY(printf("Expected type structure not found in package %s for idnex %d\n", + const TypeList& typeList = group->types[ti]; + if (typeList.isEmpty()) { + TABLE_NOISY(printf("Expected type structure not found in package %s for index %d\n", String8(group->name).string(), ti)); + continue; } - size_t NTC = typeConfigs->configs.size(); - for (size_t tci=0; tci<NTC; tci++) { - const ResTable_type* const ty = typeConfigs->configs[tci]; - const uint32_t typeOffset = dtohl(ty->entriesStart); - - const uint8_t* const end = ((const uint8_t*)ty) + dtohl(ty->header.size); - const uint32_t* const eindex = (const uint32_t*) - (((const uint8_t*)ty) + dtohs(ty->header.headerSize)); - - const size_t NE = dtohl(ty->entryCount); - for (size_t i=0; i<NE; i++) { - uint32_t offset = dtohl(eindex[i]); - if (offset == ResTable_type::NO_ENTRY) { - continue; - } - - offset += typeOffset; + const size_t typeCount = typeList.size(); + for (size_t i = 0; i < typeCount; i++) { + const Type* t = typeList[i]; + const ssize_t ei = t->package->keyStrings.indexOfString(name, nameLen); + if (ei < 0) { + continue; + } - if (offset > (dtohl(ty->header.size)-sizeof(ResTable_entry))) { - ALOGW("ResTable_entry at %d is beyond type chunk data %d", - offset, dtohl(ty->header.size)); - return 0; - } - if ((offset&0x3) != 0) { - ALOGW("ResTable_entry at %d (pkg=%d type=%d ent=%d) is not on an integer boundary when looking for %s:%s/%s", - (int)offset, (int)group->id, (int)ti+1, (int)i, - String8(package, packageLen).string(), - String8(type, typeLen).string(), - String8(name, nameLen).string()); - return 0; - } + const size_t configCount = t->configs.size(); + for (size_t j = 0; j < configCount; j++) { + const TypeVariant tv(t->configs[j]); + for (TypeVariant::iterator iter = tv.beginEntries(); + iter != tv.endEntries(); + iter++) { + const ResTable_entry* entry = *iter; + if (entry == NULL) { + continue; + } - const ResTable_entry* const entry = (const ResTable_entry*) - (((const uint8_t*)ty) + offset); - if (dtohs(entry->size) < sizeof(*entry)) { - ALOGW("ResTable_entry size %d is too small", dtohs(entry->size)); - return BAD_TYPE; - } + if (dtohl(entry->key.index) == (size_t) ei) { + uint32_t resId = Res_MAKEID(group->id - 1, ti, iter.index()); + if (outTypeSpecFlags) { + Entry result; + if (getEntry(group, ti, iter.index(), NULL, &result) != NO_ERROR) { + ALOGW("Failed to find spec flags for %s:%s/%s (0x%08x)", + String8(group->name).string(), + String8(String16(type, typeLen)).string(), + String8(String16(name, nameLen)).string(), + resId); + return 0; + } + *outTypeSpecFlags = result.specFlags; - TABLE_SUPER_NOISY(printf("Looking at entry #%d: want str %d, have %d\n", - i, ei, dtohl(entry->key.index))); - if (dtohl(entry->key.index) == (size_t)ei) { - if (outTypeSpecFlags) { - *outTypeSpecFlags = typeConfigs->typeSpecFlags[i]; - if (fakePublic) { - *outTypeSpecFlags |= ResTable_typeSpec::SPEC_PUBLIC; + if (fakePublic) { + *outTypeSpecFlags |= ResTable_typeSpec::SPEC_PUBLIC; + } } + return resId; } - return Res_MAKEID(group->id-1, ti, i); } } } + break; } - return 0; } @@ -5260,6 +5217,18 @@ uint32_t ResTable::getBasePackageId(size_t idx) const return mPackageGroups[idx]->id; } +uint32_t ResTable::getLastTypeIdForPackage(size_t idx) const +{ + if (mError != NO_ERROR) { + return 0; + } + LOG_FATAL_IF(idx >= mPackageGroups.size(), + "Requested package index %d past package count %d", + (int)idx, (int)mPackageGroups.size()); + const PackageGroup* const group = mPackageGroups[idx]; + return group->largestTypeId; +} + size_t ResTable::getTableCount() const { return mHeaders.size(); @@ -5292,32 +5261,31 @@ const DynamicRefTable* ResTable::getDynamicRefTableForCookie(int32_t cookie) con void ResTable::getConfigurations(Vector<ResTable_config>* configs) const { - const size_t I = mPackageGroups.size(); - for (size_t i=0; i<I; i++) { + const size_t packageCount = mPackageGroups.size(); + for (size_t i = 0; i < packageCount; i++) { const PackageGroup* packageGroup = mPackageGroups[i]; - const size_t J = packageGroup->packages.size(); - for (size_t j=0; j<J; j++) { - const Package* package = packageGroup->packages[j]; - const size_t K = package->types.size(); - for (size_t k=0; k<K; k++) { - const Type* type = package->types[k]; - if (type == NULL) continue; - const size_t L = type->configs.size(); - for (size_t l=0; l<L; l++) { - const ResTable_type* config = type->configs[l]; + const size_t typeCount = packageGroup->types.size(); + for (size_t j = 0; j < typeCount; j++) { + const TypeList& typeList = packageGroup->types[j]; + const size_t numTypes = typeList.size(); + for (size_t k = 0; k < numTypes; k++) { + const Type* type = typeList[k]; + const size_t numConfigs = type->configs.size(); + for (size_t m = 0; m < numConfigs; m++) { + const ResTable_type* config = type->configs[m]; ResTable_config cfg; memset(&cfg, 0, sizeof(ResTable_config)); cfg.copyFromDtoH(config->config); // only insert unique - const size_t M = configs->size(); - size_t m; - for (m=0; m<M; m++) { - if (0 == (*configs)[m].compare(cfg)) { + const size_t N = configs->size(); + size_t n; + for (n = 0; n < N; n++) { + if (0 == (*configs)[n].compare(cfg)) { break; } } // if we didn't find it - if (m == M) { + if (n == N) { configs->add(cfg); } } @@ -5350,122 +5318,180 @@ void ResTable::getLocales(Vector<String8>* locales) const } } -ssize_t ResTable::getEntry( - const Package* package, int typeIndex, int entryIndex, - const ResTable_config* config, - const ResTable_type** outType, const ResTable_entry** outEntry, - const Type** outTypeClass) const -{ - ALOGV("Getting entry from package %p\n", package); - const ResTable_package* const pkg = package->package; +StringPoolRef::StringPoolRef(const ResStringPool* pool, uint32_t index) + : mPool(pool), mIndex(index) {} - const Type* allTypes = package->getType(typeIndex); - ALOGV("allTypes=%p\n", allTypes); - if (allTypes == NULL) { - ALOGV("Skipping entry type index 0x%02x because type is NULL!\n", typeIndex); - return 0; +StringPoolRef::StringPoolRef() + : mPool(NULL), mIndex(0) {} + +const char* StringPoolRef::string8(size_t* outLen) const { + if (mPool != NULL) { + return mPool->string8At(mIndex, outLen); } + if (outLen != NULL) { + *outLen = 0; + } + return NULL; +} - if ((size_t)entryIndex >= allTypes->entryCount) { - ALOGW("getEntry failing because entryIndex %d is beyond type entryCount %d", - entryIndex, (int)allTypes->entryCount); +const char16_t* StringPoolRef::string16(size_t* outLen) const { + if (mPool != NULL) { + return mPool->stringAt(mIndex, outLen); + } + if (outLen != NULL) { + *outLen = 0; + } + return NULL; +} + +status_t ResTable::getEntry( + const PackageGroup* packageGroup, int typeIndex, int entryIndex, + const ResTable_config* config, + Entry* outEntry) const +{ + const TypeList& typeList = packageGroup->types[typeIndex]; + if (typeList.isEmpty()) { + ALOGV("Skipping entry type index 0x%02x because type is NULL!\n", typeIndex); return BAD_TYPE; } - const ResTable_type* type = NULL; - uint32_t offset = ResTable_type::NO_ENTRY; + const ResTable_type* bestType = NULL; + uint32_t bestOffset = ResTable_type::NO_ENTRY; + const Package* bestPackage = NULL; + uint32_t specFlags = 0; + uint8_t actualTypeIndex = typeIndex; ResTable_config bestConfig; - memset(&bestConfig, 0, sizeof(bestConfig)); // make the compiler shut up - - const size_t NT = allTypes->configs.size(); - for (size_t i=0; i<NT; i++) { - const ResTable_type* const thisType = allTypes->configs[i]; - if (thisType == NULL) continue; - - ResTable_config thisConfig; - thisConfig.copyFromDtoH(thisType->config); + memset(&bestConfig, 0, sizeof(bestConfig)); - TABLE_GETENTRY(ALOGI("Match entry 0x%x in type 0x%x (sz 0x%x): %s\n", - entryIndex, typeIndex+1, dtohl(thisType->config.size), - thisConfig.toString().string())); + // Iterate over the Types of each package. + const size_t typeCount = typeList.size(); + for (size_t i = 0; i < typeCount; i++) { + const Type* const typeSpec = typeList[i]; + + int realEntryIndex = entryIndex; + int realTypeIndex = typeIndex; + bool currentTypeIsOverlay = false; + + // Runtime overlay packages provide a mapping of app resource + // ID to package resource ID. + if (typeSpec->idmapEntries.hasEntries()) { + uint16_t overlayEntryIndex; + if (typeSpec->idmapEntries.lookup(entryIndex, &overlayEntryIndex) != NO_ERROR) { + // No such mapping exists + continue; + } + realEntryIndex = overlayEntryIndex; + realTypeIndex = typeSpec->idmapEntries.overlayTypeId() - 1; + currentTypeIsOverlay = true; + } - // Check to make sure this one is valid for the current parameters. - if (config && !thisConfig.match(*config)) { - TABLE_GETENTRY(ALOGI("Does not match config!\n")); + if (static_cast<size_t>(realEntryIndex) >= typeSpec->entryCount) { + ALOGW("For resource 0x%08x, entry index(%d) is beyond type entryCount(%d)", + Res_MAKEID(packageGroup->id - 1, typeIndex, entryIndex), + entryIndex, static_cast<int>(typeSpec->entryCount)); + // We should normally abort here, but some legacy apps declare + // resources in the 'android' package (old bug in AAPT). continue; } - // Check if there is the desired entry in this type. + // Aggregate all the flags for each package that defines this entry. + if (typeSpec->typeSpecFlags != NULL) { + specFlags |= dtohl(typeSpec->typeSpecFlags[realEntryIndex]); + } else { + specFlags = -1; + } - const uint8_t* const end = ((const uint8_t*)thisType) - + dtohl(thisType->header.size); - const uint32_t* const eindex = (const uint32_t*) - (((const uint8_t*)thisType) + dtohs(thisType->header.headerSize)); + const size_t numConfigs = typeSpec->configs.size(); + for (size_t c = 0; c < numConfigs; c++) { + const ResTable_type* const thisType = typeSpec->configs[c]; + if (thisType == NULL) { + continue; + } - uint32_t thisOffset = dtohl(eindex[entryIndex]); - if (thisOffset == ResTable_type::NO_ENTRY) { - TABLE_GETENTRY(ALOGI("Skipping because it is not defined!\n")); - continue; - } + ResTable_config thisConfig; + thisConfig.copyFromDtoH(thisType->config); - if (type != NULL) { - // Check if this one is less specific than the last found. If so, - // we will skip it. We check starting with things we most care - // about to those we least care about. - if (!thisConfig.isBetterThan(bestConfig, config)) { - TABLE_GETENTRY(ALOGI("This config is worse than last!\n")); + // Check to make sure this one is valid for the current parameters. + if (config != NULL && !thisConfig.match(*config)) { + continue; + } + + // Check if there is the desired entry in this type. + const uint8_t* const end = reinterpret_cast<const uint8_t*>(thisType) + + dtohl(thisType->header.size); + const uint32_t* const eindex = reinterpret_cast<const uint32_t*>( + reinterpret_cast<const uint8_t*>(thisType) + dtohs(thisType->header.headerSize)); + + uint32_t thisOffset = dtohl(eindex[realEntryIndex]); + if (thisOffset == ResTable_type::NO_ENTRY) { + // There is no entry for this index and configuration. continue; } - } - type = thisType; - offset = thisOffset; - bestConfig = thisConfig; - TABLE_GETENTRY(ALOGI("Best entry so far -- using it!\n")); - if (!config) break; + if (bestType != NULL) { + // Check if this one is less specific than the last found. If so, + // we will skip it. We check starting with things we most care + // about to those we least care about. + if (!thisConfig.isBetterThan(bestConfig, config)) { + if (!currentTypeIsOverlay || thisConfig.compare(bestConfig) != 0) { + continue; + } + } + } + + bestType = thisType; + bestOffset = thisOffset; + bestConfig = thisConfig; + bestPackage = typeSpec->package; + actualTypeIndex = realTypeIndex; + + // If no config was specified, any type will do, so skip + if (config == NULL) { + break; + } + } } - if (type == NULL) { - TABLE_GETENTRY(ALOGI("No value found for requested entry!\n")); + if (bestType == NULL) { return BAD_INDEX; } - offset += dtohl(type->entriesStart); - TABLE_NOISY(ALOGD("Looking in resource table %p, typeOff=%p, offset=%p", - package->header->header, (void*)(((const char*)type)-((const char*)package->header->header)), - (void*)offset)); + bestOffset += dtohl(bestType->entriesStart); - if (offset > (dtohl(type->header.size)-sizeof(ResTable_entry))) { + if (bestOffset > (dtohl(bestType->header.size)-sizeof(ResTable_entry))) { ALOGW("ResTable_entry at 0x%x is beyond type chunk data 0x%x", - offset, dtohl(type->header.size)); + bestOffset, dtohl(bestType->header.size)); return BAD_TYPE; } - if ((offset&0x3) != 0) { - ALOGW("ResTable_entry at 0x%x is not on an integer boundary", - offset); + if ((bestOffset & 0x3) != 0) { + ALOGW("ResTable_entry at 0x%x is not on an integer boundary", bestOffset); return BAD_TYPE; } - const ResTable_entry* const entry = (const ResTable_entry*) - (((const uint8_t*)type) + offset); + const ResTable_entry* const entry = reinterpret_cast<const ResTable_entry*>( + reinterpret_cast<const uint8_t*>(bestType) + bestOffset); if (dtohs(entry->size) < sizeof(*entry)) { ALOGW("ResTable_entry size 0x%x is too small", dtohs(entry->size)); return BAD_TYPE; } - *outType = type; - *outEntry = entry; - if (outTypeClass != NULL) { - *outTypeClass = allTypes; + if (outEntry != NULL) { + outEntry->entry = entry; + outEntry->config = bestConfig; + outEntry->type = bestType; + outEntry->specFlags = specFlags; + outEntry->package = bestPackage; + outEntry->typeStr = StringPoolRef(&bestPackage->typeStrings, actualTypeIndex - bestPackage->typeIdOffset); + outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, dtohl(entry->key.index)); } - return offset + dtohs(entry->size); + return NO_ERROR; } status_t ResTable::parsePackage(const ResTable_package* const pkg, - const Header* const header, uint32_t idmap_id) + const Header* const header) { const uint8_t* base = (const uint8_t*)pkg; - status_t err = validate_chunk(&pkg->header, sizeof(*pkg), + status_t err = validate_chunk(&pkg->header, sizeof(*pkg) - sizeof(pkg->typeIdOffset), header->dataEnd, "ResTable_package"); if (err != NO_ERROR) { return (mError=err); @@ -5494,89 +5520,88 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, return (mError=BAD_TYPE); } - Package* package = NULL; - PackageGroup* group = NULL; - uint32_t id = idmap_id != 0 ? idmap_id : dtohl(pkg->id); - // If at this point id == 0, pkg is an overlay package without a - // corresponding idmap. During regular usage, overlay packages are - // always loaded alongside their idmaps, but during idmap creation - // the package is temporarily loaded by itself. - if (id < 256) { - - package = new Package(this, header, pkg); - if (package == NULL) { - return (mError=NO_MEMORY); - } - - if (idmap_id == 0) { - err = package->typeStrings.setTo(base+dtohl(pkg->typeStrings), - header->dataEnd-(base+dtohl(pkg->typeStrings))); - if (err != NO_ERROR) { - delete group; - delete package; - return (mError=err); - } + uint32_t id = dtohl(pkg->id); + KeyedVector<uint8_t, IdmapEntries> idmapEntries; - err = package->keyStrings.setTo(base+dtohl(pkg->keyStrings), - header->dataEnd-(base+dtohl(pkg->keyStrings))); - if (err != NO_ERROR) { - delete group; - delete package; - return (mError=err); - } + if (header->resourceIDMap != NULL) { + uint8_t targetPackageId = 0; + status_t err = parseIdmap(header->resourceIDMap, header->resourceIDMapSize, &targetPackageId, &idmapEntries); + if (err != NO_ERROR) { + ALOGW("Overlay is broken"); + return (mError=err); } + id = targetPackageId; + } - if (id == 0) { - // This is a library so assign an ID - id = mNextPackageId++; - } + if (id >= 256) { + LOG_ALWAYS_FATAL("Package id out of range"); + return NO_ERROR; + } else if (id == 0) { + // This is a library so assign an ID + id = mNextPackageId++; + } - size_t idx = mPackageMap[id]; - if (idx == 0) { - idx = mPackageGroups.size()+1; + PackageGroup* group = NULL; + Package* package = new Package(this, header, pkg); + if (package == NULL) { + return (mError=NO_MEMORY); + } - char16_t tmpName[sizeof(pkg->name)/sizeof(char16_t)]; - strcpy16_dtoh(tmpName, pkg->name, sizeof(pkg->name)/sizeof(char16_t)); - group = new PackageGroup(this, String16(tmpName), id); - if (group == NULL) { - delete package; - return (mError=NO_MEMORY); - } + err = package->typeStrings.setTo(base+dtohl(pkg->typeStrings), + header->dataEnd-(base+dtohl(pkg->typeStrings))); + if (err != NO_ERROR) { + delete group; + delete package; + return (mError=err); + } - //printf("Adding new package id %d at index %d\n", id, idx); - err = mPackageGroups.add(group); - if (err < NO_ERROR) { - return (mError=err); - } - group->basePackage = package; + err = package->keyStrings.setTo(base+dtohl(pkg->keyStrings), + header->dataEnd-(base+dtohl(pkg->keyStrings))); + if (err != NO_ERROR) { + delete group; + delete package; + return (mError=err); + } - mPackageMap[id] = (uint8_t)idx; + size_t idx = mPackageMap[id]; + if (idx == 0) { + idx = mPackageGroups.size() + 1; - // Find all packages that reference this package - size_t N = mPackageGroups.size(); - for (size_t i = 0; i < N; i++) { - mPackageGroups[i]->dynamicRefTable.addMapping( - group->name, static_cast<uint8_t>(group->id)); - } - } else { - group = mPackageGroups.itemAt(idx-1); - if (group == NULL) { - return (mError=UNKNOWN_ERROR); - } + char16_t tmpName[sizeof(pkg->name)/sizeof(char16_t)]; + strcpy16_dtoh(tmpName, pkg->name, sizeof(pkg->name)/sizeof(char16_t)); + group = new PackageGroup(this, String16(tmpName), id); + if (group == NULL) { + delete package; + return (mError=NO_MEMORY); } - err = group->packages.add(package); + + //printf("Adding new package id %d at index %d\n", id, idx); + err = mPackageGroups.add(group); if (err < NO_ERROR) { return (mError=err); } + + mPackageMap[id] = static_cast<uint8_t>(idx); + + // Find all packages that reference this package + size_t N = mPackageGroups.size(); + for (size_t i = 0; i < N; i++) { + mPackageGroups[i]->dynamicRefTable.addMapping( + group->name, static_cast<uint8_t>(group->id)); + } } else { - LOG_ALWAYS_FATAL("Package id out of range"); - return NO_ERROR; + group = mPackageGroups.itemAt(idx - 1); + if (group == NULL) { + return (mError=UNKNOWN_ERROR); + } } + err = group->packages.add(package); + if (err < NO_ERROR) { + return (mError=err); + } // Iterate through all chunks. - size_t curPackage = 0; - const ResChunk_header* chunk = (const ResChunk_header*)(((const uint8_t*)pkg) + dtohs(pkg->header.headerSize)); @@ -5597,6 +5622,7 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, } const size_t typeSpecSize = dtohl(typeSpec->header.size); + const size_t newEntryCount = dtohl(typeSpec->entryCount); LOAD_TABLE_NOISY(printf("TypeSpec off %p: type=0x%x, headerSize=0x%x, size=%p\n", (void*)(base-(const uint8_t*)chunk), @@ -5605,12 +5631,11 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, (void*)typeSpecSize)); // look for block overrun or int overflow when multiplying by 4 if ((dtohl(typeSpec->entryCount) > (INT32_MAX/sizeof(uint32_t)) - || dtohs(typeSpec->header.headerSize)+(sizeof(uint32_t)*dtohl(typeSpec->entryCount)) + || dtohs(typeSpec->header.headerSize)+(sizeof(uint32_t)*newEntryCount) > typeSpecSize)) { ALOGW("ResTable_typeSpec entry index to %p extends beyond chunk end %p.", - (void*)(dtohs(typeSpec->header.headerSize) - +(sizeof(uint32_t)*dtohl(typeSpec->entryCount))), - (void*)typeSpecSize); + (void*)(dtohs(typeSpec->header.headerSize) + (sizeof(uint32_t)*newEntryCount)), + (void*)typeSpecSize); return (mError=BAD_TYPE); } @@ -5619,21 +5644,36 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, return (mError=BAD_TYPE); } - while (package->types.size() < typeSpec->id) { - package->types.add(NULL); - } - Type* t = package->types[typeSpec->id-1]; - if (t == NULL) { - t = new Type(header, package, dtohl(typeSpec->entryCount)); - package->types.editItemAt(typeSpec->id-1) = t; - } else if (dtohl(typeSpec->entryCount) != t->entryCount) { - ALOGW("ResTable_typeSpec entry count inconsistent: given %d, previously %d", - (int)dtohl(typeSpec->entryCount), (int)t->entryCount); - return (mError=BAD_TYPE); + if (newEntryCount > 0) { + uint8_t typeIndex = typeSpec->id - 1; + ssize_t idmapIndex = idmapEntries.indexOfKey(typeSpec->id); + if (idmapIndex >= 0) { + typeIndex = idmapEntries[idmapIndex].targetTypeId() - 1; + } + + TypeList& typeList = group->types.editItemAt(typeIndex); + if (!typeList.isEmpty()) { + const Type* existingType = typeList[0]; + if (existingType->entryCount != newEntryCount && idmapIndex < 0) { + ALOGW("ResTable_typeSpec entry count inconsistent: given %d, previously %d", + (int) newEntryCount, (int) existingType->entryCount); + // We should normally abort here, but some legacy apps declare + // resources in the 'android' package (old bug in AAPT). + } + } + + Type* t = new Type(header, package, newEntryCount); + t->typeSpec = typeSpec; + t->typeSpecFlags = (const uint32_t*)( + ((const uint8_t*)typeSpec) + dtohs(typeSpec->header.headerSize)); + if (idmapIndex >= 0) { + t->idmapEntries = idmapEntries[idmapIndex]; + } + typeList.add(t); + group->largestTypeId = max(group->largestTypeId, typeSpec->id); + } else { + ALOGV("Skipping empty ResTable_typeSpec for type %d", typeSpec->id); } - t->typeSpecFlags = (const uint32_t*)( - ((const uint8_t*)typeSpec) + dtohs(typeSpec->header.headerSize)); - t->typeSpec = typeSpec; } else if (ctype == RES_TABLE_TYPE_TYPE) { const ResTable_type* type = (const ResTable_type*)(chunk); @@ -5644,50 +5684,69 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, } const uint32_t typeSize = dtohl(type->header.size); + const size_t newEntryCount = dtohl(type->entryCount); LOAD_TABLE_NOISY(printf("Type off %p: type=0x%x, headerSize=0x%x, size=%p\n", (void*)(base-(const uint8_t*)chunk), dtohs(type->header.type), dtohs(type->header.headerSize), (void*)typeSize)); - if (dtohs(type->header.headerSize)+(sizeof(uint32_t)*dtohl(type->entryCount)) - > typeSize) { + if (dtohs(type->header.headerSize)+(sizeof(uint32_t)*newEntryCount) + > typeSize) { ALOGW("ResTable_type entry index to %p extends beyond chunk end 0x%x.", - (void*)(dtohs(type->header.headerSize) - +(sizeof(uint32_t)*dtohl(type->entryCount))), - typeSize); + (void*)(dtohs(type->header.headerSize) + (sizeof(uint32_t)*newEntryCount)), + typeSize); return (mError=BAD_TYPE); } - if (dtohl(type->entryCount) != 0 + + if (newEntryCount != 0 && dtohl(type->entriesStart) > (typeSize-sizeof(ResTable_entry))) { ALOGW("ResTable_type entriesStart at 0x%x extends beyond chunk end 0x%x.", dtohl(type->entriesStart), typeSize); return (mError=BAD_TYPE); } + if (type->id == 0) { ALOGW("ResTable_type has an id of 0."); return (mError=BAD_TYPE); } - while (package->types.size() < type->id) { - package->types.add(NULL); - } - Type* t = package->types[type->id-1]; - if (t == NULL) { - t = new Type(header, package, dtohl(type->entryCount)); - package->types.editItemAt(type->id-1) = t; - } else if (dtohl(type->entryCount) != t->entryCount) { - ALOGW("ResTable_type entry count inconsistent: given %d, previously %d", - (int)dtohl(type->entryCount), (int)t->entryCount); - return (mError=BAD_TYPE); + if (newEntryCount > 0) { + uint8_t typeIndex = type->id - 1; + ssize_t idmapIndex = idmapEntries.indexOfKey(type->id); + if (idmapIndex >= 0) { + typeIndex = idmapEntries[idmapIndex].targetTypeId() - 1; + } + + TypeList& typeList = group->types.editItemAt(typeIndex); + if (typeList.isEmpty()) { + ALOGE("No TypeSpec for type %d", type->id); + return (mError=BAD_TYPE); + } + + Type* t = typeList.editItemAt(typeList.size() - 1); + if (newEntryCount != t->entryCount) { + ALOGE("ResTable_type entry count inconsistent: given %d, previously %d", + (int)newEntryCount, (int)t->entryCount); + return (mError=BAD_TYPE); + } + + if (t->package != package) { + ALOGE("No TypeSpec for type %d", type->id); + return (mError=BAD_TYPE); + } + + t->configs.add(type); + + TABLE_GETENTRY( + ResTable_config thisConfig; + thisConfig.copyFromDtoH(type->config); + ALOGI("Adding config to type %d: %s\n", + type->id, thisConfig.toString().string())); + } else { + ALOGV("Skipping empty ResTable_type for type %d", type->id); } - TABLE_GETENTRY( - ResTable_config thisConfig; - thisConfig.copyFromDtoH(type->config); - ALOGI("Adding config to type %d: %s\n", - type->id, thisConfig.toString().string())); - t->configs.add(type); } else if (ctype == RES_TABLE_LIBRARY_TYPE) { if (group->dynamicRefTable.entries().size() == 0) { status_t err = group->dynamicRefTable.load((const ResTable_lib_header*) chunk); @@ -5714,10 +5773,6 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, (((const uint8_t*)chunk) + csize); } - if (group->typeCount == 0) { - group->typeCount = package->types.size(); - } - return NO_ERROR; } @@ -5818,6 +5873,12 @@ status_t DynamicRefTable::lookupResourceValue(Res_value* value) const { return NO_ERROR; } +struct IdmapTypeMap { + ssize_t overlayTypeId; + size_t entryOffset; + Vector<uint32_t> entryMap; +}; + status_t ResTable::createIdmap(const ResTable& overlay, uint32_t targetCrc, uint32_t overlayCrc, const char* targetPath, const char* overlayPath, @@ -5828,41 +5889,46 @@ status_t ResTable::createIdmap(const ResTable& overlay, ALOGW("idmap: target package has no package groups, cannot create idmap\n"); return UNKNOWN_ERROR; } + if (mPackageGroups[0]->packages.size() == 0) { ALOGW("idmap: target package has no packages in its first package group, " "cannot create idmap\n"); return UNKNOWN_ERROR; } - Vector<Vector<uint32_t> > map; + KeyedVector<uint8_t, IdmapTypeMap> map; + // overlaid packages are assumed to contain only one package group const PackageGroup* pg = mPackageGroups[0]; - const Package* pkg = pg->packages[0]; - size_t typeCount = pkg->types.size(); - // starting size is header + first item (number of types in map) - *outSize = (IDMAP_HEADER_SIZE + 1) * sizeof(uint32_t); + + // starting size is header + *outSize = ResTable::IDMAP_HEADER_SIZE_BYTES; + + // target package id and number of types in map + *outSize += 2 * sizeof(uint16_t); + // overlay packages are assumed to contain only one package group const String16 overlayPackage(overlay.mPackageGroups[0]->packages[0]->package->name); - const uint32_t pkg_id = pkg->package->id << 24; - - for (size_t typeIndex = 0; typeIndex < typeCount; ++typeIndex) { - ssize_t first = -1; - ssize_t last = -1; - const Type* typeConfigs = pkg->getType(typeIndex); - ssize_t mapIndex = map.add(); - if (mapIndex < 0) { - return NO_MEMORY; + + for (size_t typeIndex = 0; typeIndex < pg->types.size(); ++typeIndex) { + const TypeList& typeList = pg->types[typeIndex]; + if (typeList.isEmpty()) { + continue; } - Vector<uint32_t>& vector = map.editItemAt(mapIndex); + + const Type* typeConfigs = typeList[0]; + + IdmapTypeMap typeMap; + typeMap.overlayTypeId = -1; + typeMap.entryOffset = 0; + for (size_t entryIndex = 0; entryIndex < typeConfigs->entryCount; ++entryIndex) { - uint32_t resID = pkg_id - | (0x00ff0000 & ((typeIndex+1)<<16)) - | (0x0000ffff & (entryIndex)); + uint32_t resID = Res_MAKEID(pg->id - 1, typeIndex, entryIndex); resource_name resName; if (!this->getResourceName(resID, false, &resName)) { - ALOGW("idmap: resource 0x%08x has spec but lacks values, skipping\n", resID); - // add dummy value, or trimming leading/trailing zeroes later will fail - vector.push(0); + if (typeMap.entryMap.isEmpty()) { + typeMap.entryOffset++; + } continue; } @@ -5874,49 +5940,55 @@ status_t ResTable::createIdmap(const ResTable& overlay, overlayType.size(), overlayPackage.string(), overlayPackage.size()); - if (overlayResID != 0) { - overlayResID = pkg_id | (0x00ffffff & overlayResID); - last = Res_GETENTRY(resID); - if (first == -1) { - first = Res_GETENTRY(resID); + if (overlayResID == 0) { + if (typeMap.entryMap.isEmpty()) { + typeMap.entryOffset++; } + continue; } - vector.push(overlayResID); -#if 0 - if (overlayResID != 0) { - ALOGD("%s/%s 0x%08x -> 0x%08x\n", - String8(String16(resName.type)).string(), - String8(String16(resName.name)).string(), - resID, overlayResID); + + if (typeMap.overlayTypeId == -1) { + typeMap.overlayTypeId = Res_GETTYPE(overlayResID) + 1; + } + + if (Res_GETTYPE(overlayResID) + 1 != static_cast<size_t>(typeMap.overlayTypeId)) { + ALOGE("idmap: can't mix type ids in entry map. Resource 0x%08x maps to 0x%08x" + " but entries should map to resources of type %02x", + resID, overlayResID, typeMap.overlayTypeId); + return BAD_TYPE; + } + + if (typeMap.entryOffset + typeMap.entryMap.size() < entryIndex) { + // Resize to accomodate this entry and the 0's in between. + if (typeMap.entryMap.resize((entryIndex - typeMap.entryOffset) + 1) < 0) { + return NO_MEMORY; + } + typeMap.entryMap.editTop() = Res_GETENTRY(overlayResID); + } else { + typeMap.entryMap.add(Res_GETENTRY(overlayResID)); } -#endif } - if (first != -1) { - // shave off trailing entries which lack overlay values - const size_t last_past_one = last + 1; - if (last_past_one < vector.size()) { - vector.removeItemsAt(last_past_one, vector.size() - last_past_one); + if (!typeMap.entryMap.isEmpty()) { + if (map.add(static_cast<uint8_t>(typeIndex), typeMap) < 0) { + return NO_MEMORY; } - // shave off leading entries which lack overlay values - vector.removeItemsAt(0, first); - // store offset to first overlaid resource ID of this type - vector.insertAt((uint32_t)first, 0, 1); - // reserve space for number and offset of entries, and the actual entries - *outSize += (2 + vector.size()) * sizeof(uint32_t); - } else { - // no entries of current type defined in overlay package - vector.clear(); - // reserve space for type offset - *outSize += 1 * sizeof(uint32_t); + *outSize += (4 * sizeof(uint16_t)) + (typeMap.entryMap.size() * sizeof(uint32_t)); } } + if (map.isEmpty()) { + ALOGW("idmap: no resources in overlay package present in base package"); + return UNKNOWN_ERROR; + } + if ((*outData = malloc(*outSize)) == NULL) { return NO_MEMORY; } + uint32_t* data = (uint32_t*)*outData; *data++ = htodl(IDMAP_MAGIC); + *data++ = htodl(IDMAP_CURRENT_VERSION); *data++ = htodl(targetCrc); *data++ = htodl(overlayCrc); const char* paths[] = { targetPath, overlayPath }; @@ -5934,44 +6006,30 @@ status_t ResTable::createIdmap(const ResTable& overlay, data += 256 / sizeof(uint32_t); } const size_t mapSize = map.size(); - *data++ = htodl(mapSize); - size_t offset = mapSize; + uint16_t* typeData = reinterpret_cast<uint16_t*>(data); + *typeData++ = htods(pg->id); + *typeData++ = htods(mapSize); for (size_t i = 0; i < mapSize; ++i) { - const Vector<uint32_t>& vector = map.itemAt(i); - const size_t N = vector.size(); - if (N == 0) { - *data++ = htodl(0); - } else { - offset++; - *data++ = htodl(offset); - offset += N; - } - } - if (offset == mapSize) { - ALOGW("idmap: no resources in overlay package present in base package\n"); - return UNKNOWN_ERROR; - } - for (size_t i = 0; i < mapSize; ++i) { - const Vector<uint32_t>& vector = map.itemAt(i); - const size_t N = vector.size(); - if (N == 0) { - continue; - } - if (N == 1) { // vector expected to hold (offset) + (N > 0 entries) - ALOGW("idmap: type %u supposedly has entries, but no entries found\n", (uint32_t)i); - return UNKNOWN_ERROR; - } - *data++ = htodl(N - 1); // do not count the offset (which is vector's first element) - for (size_t j = 0; j < N; ++j) { - const uint32_t& overlayResID = vector.itemAt(j); - *data++ = htodl(overlayResID); + uint8_t targetTypeId = map.keyAt(i); + const IdmapTypeMap& typeMap = map[i]; + *typeData++ = htods(targetTypeId + 1); + *typeData++ = htods(typeMap.overlayTypeId); + *typeData++ = htods(typeMap.entryMap.size()); + *typeData++ = htods(typeMap.entryOffset); + + const size_t entryCount = typeMap.entryMap.size(); + uint32_t* entries = reinterpret_cast<uint32_t*>(typeData); + for (size_t j = 0; j < entryCount; j++) { + entries[j] = htodl(typeMap.entryMap[j]); } + typeData += entryCount * 2; } return NO_ERROR; } bool ResTable::getIdmapInfo(const void* idmap, size_t sizeBytes, + uint32_t* pVersion, uint32_t* pTargetCrc, uint32_t* pOverlayCrc, String8* pTargetPath, String8* pOverlayPath) { @@ -5979,17 +6037,20 @@ bool ResTable::getIdmapInfo(const void* idmap, size_t sizeBytes, if (!assertIdmapHeader(map, sizeBytes)) { return false; } + if (pVersion) { + *pVersion = dtohl(map[1]); + } if (pTargetCrc) { - *pTargetCrc = map[1]; + *pTargetCrc = dtohl(map[2]); } if (pOverlayCrc) { - *pOverlayCrc = map[2]; + *pOverlayCrc = dtohl(map[3]); } if (pTargetPath) { - pTargetPath->setTo(reinterpret_cast<const char*>(map + 3)); + pTargetPath->setTo(reinterpret_cast<const char*>(map + 4)); } if (pOverlayPath) { - pOverlayPath->setTo(reinterpret_cast<const char*>(map + 3 + 256 / sizeof(uint32_t))); + pOverlayPath->setTo(reinterpret_cast<const char*>(map + 4 + 256 / sizeof(uint32_t))); } return true; } @@ -6138,184 +6199,184 @@ void ResTable::print(bool inclValues) const size_t pkgCount = pg->packages.size(); for (size_t pkgIndex=0; pkgIndex<pkgCount; pkgIndex++) { const Package* pkg = pg->packages[pkgIndex]; - size_t typeCount = pkg->types.size(); - printf(" Package %d id=%d name=%s typeCount=%d\n", (int)pkgIndex, - pkg->package->id, String8(String16(pkg->package->name)).string(), - (int)typeCount); - for (size_t typeIndex=0; typeIndex<typeCount; typeIndex++) { - const Type* typeConfigs = pkg->getType(typeIndex); - if (typeConfigs == NULL) { - printf(" type %d NULL\n", (int)typeIndex); + printf(" Package %d id=%d name=%s\n", (int)pkgIndex, + pkg->package->id, String8(String16(pkg->package->name)).string()); + } + + for (size_t typeIndex=0; typeIndex < pg->types.size(); typeIndex++) { + const TypeList& typeList = pg->types[typeIndex]; + if (typeList.isEmpty()) { + //printf(" type %d NULL\n", (int)typeIndex); + continue; + } + const Type* typeConfigs = typeList[0]; + const size_t NTC = typeConfigs->configs.size(); + printf(" type %d configCount=%d entryCount=%d\n", + (int)typeIndex, (int)NTC, (int)typeConfigs->entryCount); + if (typeConfigs->typeSpecFlags != NULL) { + for (size_t entryIndex=0; entryIndex<typeConfigs->entryCount; entryIndex++) { + uint32_t resID = (0xff000000 & ((pg->id)<<24)) + | (0x00ff0000 & ((typeIndex+1)<<16)) + | (0x0000ffff & (entryIndex)); + // Since we are creating resID without actually + // iterating over them, we have no idea which is a + // dynamic reference. We must check. + pg->dynamicRefTable.lookupResourceId(&resID); + + resource_name resName; + if (this->getResourceName(resID, true, &resName)) { + String8 type8; + String8 name8; + if (resName.type8 != NULL) { + type8 = String8(resName.type8, resName.typeLen); + } else { + type8 = String8(resName.type, resName.typeLen); + } + if (resName.name8 != NULL) { + name8 = String8(resName.name8, resName.nameLen); + } else { + name8 = String8(resName.name, resName.nameLen); + } + printf(" spec resource 0x%08x %s:%s/%s: flags=0x%08x\n", + resID, + CHAR16_TO_CSTR(resName.package, resName.packageLen), + type8.string(), name8.string(), + dtohl(typeConfigs->typeSpecFlags[entryIndex])); + } else { + printf(" INVALID TYPE CONFIG FOR RESOURCE 0x%08x\n", resID); + } + } + } + for (size_t configIndex=0; configIndex<NTC; configIndex++) { + const ResTable_type* type = typeConfigs->configs[configIndex]; + if ((((uint64_t)type)&0x3) != 0) { + printf(" NON-INTEGER ResTable_type ADDRESS: %p\n", type); continue; } - const size_t NTC = typeConfigs->configs.size(); - printf(" type %d configCount=%d entryCount=%d\n", - (int)typeIndex, (int)NTC, (int)typeConfigs->entryCount); - if (typeConfigs->typeSpecFlags != NULL) { - for (size_t entryIndex=0; entryIndex<typeConfigs->entryCount; entryIndex++) { - uint32_t resID = (0xff000000 & ((pkg->package->id)<<24)) - | (0x00ff0000 & ((typeIndex+1)<<16)) - | (0x0000ffff & (entryIndex)); - // Since we are creating resID without actually - // iterating over them, we have no idea which is a - // dynamic reference. We must check. - pg->dynamicRefTable.lookupResourceId(&resID); - - resource_name resName; - if (this->getResourceName(resID, true, &resName)) { - String8 type8; - String8 name8; - if (resName.type8 != NULL) { - type8 = String8(resName.type8, resName.typeLen); - } else { - type8 = String8(resName.type, resName.typeLen); - } - if (resName.name8 != NULL) { - name8 = String8(resName.name8, resName.nameLen); - } else { - name8 = String8(resName.name, resName.nameLen); - } - printf(" spec resource 0x%08x %s:%s/%s: flags=0x%08x\n", - resID, - CHAR16_TO_CSTR(resName.package, resName.packageLen), - type8.string(), name8.string(), - dtohl(typeConfigs->typeSpecFlags[entryIndex])); + String8 configStr = type->config.toString(); + printf(" config %s:\n", configStr.size() > 0 + ? configStr.string() : "(default)"); + size_t entryCount = dtohl(type->entryCount); + uint32_t entriesStart = dtohl(type->entriesStart); + if ((entriesStart&0x3) != 0) { + printf(" NON-INTEGER ResTable_type entriesStart OFFSET: 0x%x\n", entriesStart); + continue; + } + uint32_t typeSize = dtohl(type->header.size); + if ((typeSize&0x3) != 0) { + printf(" NON-INTEGER ResTable_type header.size: 0x%x\n", typeSize); + continue; + } + for (size_t entryIndex=0; entryIndex<entryCount; entryIndex++) { + + const uint8_t* const end = ((const uint8_t*)type) + + dtohl(type->header.size); + const uint32_t* const eindex = (const uint32_t*) + (((const uint8_t*)type) + dtohs(type->header.headerSize)); + + uint32_t thisOffset = dtohl(eindex[entryIndex]); + if (thisOffset == ResTable_type::NO_ENTRY) { + continue; + } + + uint32_t resID = (0xff000000 & ((pg->id)<<24)) + | (0x00ff0000 & ((typeIndex+1)<<16)) + | (0x0000ffff & (entryIndex)); + pg->dynamicRefTable.lookupResourceId(&resID); + resource_name resName; + if (this->getResourceName(resID, true, &resName)) { + String8 type8; + String8 name8; + if (resName.type8 != NULL) { + type8 = String8(resName.type8, resName.typeLen); + } else { + type8 = String8(resName.type, resName.typeLen); + } + if (resName.name8 != NULL) { + name8 = String8(resName.name8, resName.nameLen); } else { - printf(" INVALID TYPE CONFIG FOR RESOURCE 0x%08x\n", resID); + name8 = String8(resName.name, resName.nameLen); } + printf(" resource 0x%08x %s:%s/%s: ", resID, + CHAR16_TO_CSTR(resName.package, resName.packageLen), + type8.string(), name8.string()); + } else { + printf(" INVALID RESOURCE 0x%08x: ", resID); } - } - for (size_t configIndex=0; configIndex<NTC; configIndex++) { - const ResTable_type* type = typeConfigs->configs[configIndex]; - if ((((uint64_t)type)&0x3) != 0) { - printf(" NON-INTEGER ResTable_type ADDRESS: %p\n", type); + if ((thisOffset&0x3) != 0) { + printf("NON-INTEGER OFFSET: 0x%x\n", thisOffset); continue; } - String8 configStr = type->config.toString(); - printf(" config %s:\n", configStr.size() > 0 - ? configStr.string() : "(default)"); - size_t entryCount = dtohl(type->entryCount); - uint32_t entriesStart = dtohl(type->entriesStart); - if ((entriesStart&0x3) != 0) { - printf(" NON-INTEGER ResTable_type entriesStart OFFSET: 0x%x\n", entriesStart); + if ((thisOffset+sizeof(ResTable_entry)) > typeSize) { + printf("OFFSET OUT OF BOUNDS: 0x%x+0x%x (size is 0x%x)\n", + entriesStart, thisOffset, typeSize); continue; } - uint32_t typeSize = dtohl(type->header.size); - if ((typeSize&0x3) != 0) { - printf(" NON-INTEGER ResTable_type header.size: 0x%x\n", typeSize); + + const ResTable_entry* ent = (const ResTable_entry*) + (((const uint8_t*)type) + entriesStart + thisOffset); + if (((entriesStart + thisOffset)&0x3) != 0) { + printf("NON-INTEGER ResTable_entry OFFSET: 0x%x\n", + (entriesStart + thisOffset)); continue; } - for (size_t entryIndex=0; entryIndex<entryCount; entryIndex++) { - const uint8_t* const end = ((const uint8_t*)type) - + dtohl(type->header.size); - const uint32_t* const eindex = (const uint32_t*) - (((const uint8_t*)type) + dtohs(type->header.headerSize)); + uintptr_t esize = dtohs(ent->size); + if ((esize&0x3) != 0) { + printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void *)esize); + continue; + } + if ((thisOffset+esize) > typeSize) { + printf("ResTable_entry OUT OF BOUNDS: 0x%x+0x%x+%p (size is 0x%x)\n", + entriesStart, thisOffset, (void *)esize, typeSize); + continue; + } - uint32_t thisOffset = dtohl(eindex[entryIndex]); - if (thisOffset == ResTable_type::NO_ENTRY) { - continue; - } + const Res_value* valuePtr = NULL; + const ResTable_map_entry* bagPtr = NULL; + Res_value value; + if ((dtohs(ent->flags)&ResTable_entry::FLAG_COMPLEX) != 0) { + printf("<bag>"); + bagPtr = (const ResTable_map_entry*)ent; + } else { + valuePtr = (const Res_value*) + (((const uint8_t*)ent) + esize); + value.copyFrom_dtoh(*valuePtr); + printf("t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)", + (int)value.dataType, (int)value.data, + (int)value.size, (int)value.res0); + } - uint32_t resID = (0xff000000 & ((pkg->package->id)<<24)) - | (0x00ff0000 & ((typeIndex+1)<<16)) - | (0x0000ffff & (entryIndex)); - pg->dynamicRefTable.lookupResourceId(&resID); - resource_name resName; - if (this->getResourceName(resID, true, &resName)) { - String8 type8; - String8 name8; - if (resName.type8 != NULL) { - type8 = String8(resName.type8, resName.typeLen); - } else { - type8 = String8(resName.type, resName.typeLen); - } - if (resName.name8 != NULL) { - name8 = String8(resName.name8, resName.nameLen); - } else { - name8 = String8(resName.name, resName.nameLen); + if ((dtohs(ent->flags)&ResTable_entry::FLAG_PUBLIC) != 0) { + printf(" (PUBLIC)"); + } + printf("\n"); + + if (inclValues) { + if (valuePtr != NULL) { + printf(" "); + print_value(typeConfigs->package, value); + } else if (bagPtr != NULL) { + const int N = dtohl(bagPtr->count); + const uint8_t* baseMapPtr = (const uint8_t*)ent; + size_t mapOffset = esize; + const ResTable_map* mapPtr = (ResTable_map*)(baseMapPtr+mapOffset); + const uint32_t parent = dtohl(bagPtr->parent.ident); + uint32_t resolvedParent = parent; + status_t err = pg->dynamicRefTable.lookupResourceId(&resolvedParent); + if (err != NO_ERROR) { + resolvedParent = 0; } - printf(" resource 0x%08x %s:%s/%s: ", resID, - CHAR16_TO_CSTR(resName.package, resName.packageLen), - type8.string(), name8.string()); - } else { - printf(" INVALID RESOURCE 0x%08x: ", resID); - } - if ((thisOffset&0x3) != 0) { - printf("NON-INTEGER OFFSET: 0x%x\n", thisOffset); - continue; - } - if ((thisOffset+sizeof(ResTable_entry)) > typeSize) { - printf("OFFSET OUT OF BOUNDS: 0x%x+0x%x (size is 0x%x)\n", - entriesStart, thisOffset, typeSize); - continue; - } - - const ResTable_entry* ent = (const ResTable_entry*) - (((const uint8_t*)type) + entriesStart + thisOffset); - if (((entriesStart + thisOffset)&0x3) != 0) { - printf("NON-INTEGER ResTable_entry OFFSET: 0x%x\n", - (entriesStart + thisOffset)); - continue; - } - - uintptr_t esize = dtohs(ent->size); - if ((esize&0x3) != 0) { - printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void *)esize); - continue; - } - if ((thisOffset+esize) > typeSize) { - printf("ResTable_entry OUT OF BOUNDS: 0x%x+0x%x+%p (size is 0x%x)\n", - entriesStart, thisOffset, (void *)esize, typeSize); - continue; - } - - const Res_value* valuePtr = NULL; - const ResTable_map_entry* bagPtr = NULL; - Res_value value; - if ((dtohs(ent->flags)&ResTable_entry::FLAG_COMPLEX) != 0) { - printf("<bag>"); - bagPtr = (const ResTable_map_entry*)ent; - } else { - valuePtr = (const Res_value*) - (((const uint8_t*)ent) + esize); - value.copyFrom_dtoh(*valuePtr); - printf("t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)", - (int)value.dataType, (int)value.data, - (int)value.size, (int)value.res0); - } - - if ((dtohs(ent->flags)&ResTable_entry::FLAG_PUBLIC) != 0) { - printf(" (PUBLIC)"); - } - printf("\n"); - - if (inclValues) { - if (valuePtr != NULL) { - printf(" "); - print_value(pkg, value); - } else if (bagPtr != NULL) { - const int N = dtohl(bagPtr->count); - const uint8_t* baseMapPtr = (const uint8_t*)ent; - size_t mapOffset = esize; - const ResTable_map* mapPtr = (ResTable_map*)(baseMapPtr+mapOffset); - const uint32_t parent = dtohl(bagPtr->parent.ident); - uint32_t resolvedParent = parent; - status_t err = pg->dynamicRefTable.lookupResourceId(&resolvedParent); - if (err != NO_ERROR) { - resolvedParent = 0; - } - printf(" Parent=0x%08x(Resolved=0x%08x), Count=%d\n", - parent, resolvedParent, N); - for (int i=0; i<N && mapOffset < (typeSize-sizeof(ResTable_map)); i++) { - printf(" #%i (Key=0x%08x): ", - i, dtohl(mapPtr->name.ident)); - value.copyFrom_dtoh(mapPtr->value); - print_value(pkg, value); - const size_t size = dtohs(mapPtr->value.size); - mapOffset += size + sizeof(*mapPtr)-sizeof(mapPtr->value); - mapPtr = (ResTable_map*)(baseMapPtr+mapOffset); - } + printf(" Parent=0x%08x(Resolved=0x%08x), Count=%d\n", + parent, resolvedParent, N); + for (int i=0; i<N && mapOffset < (typeSize-sizeof(ResTable_map)); i++) { + printf(" #%i (Key=0x%08x): ", + i, dtohl(mapPtr->name.ident)); + value.copyFrom_dtoh(mapPtr->value); + print_value(typeConfigs->package, value); + const size_t size = dtohs(mapPtr->value.size); + mapOffset += size + sizeof(*mapPtr)-sizeof(mapPtr->value); + mapPtr = (ResTable_map*)(baseMapPtr+mapOffset); } } } diff --git a/libs/androidfw/TypeWrappers.cpp b/libs/androidfw/TypeWrappers.cpp new file mode 100644 index 000000000000..06b40405f404 --- /dev/null +++ b/libs/androidfw/TypeWrappers.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <androidfw/TypeWrappers.h> + +namespace android { + +TypeVariant::iterator& TypeVariant::iterator::operator++() { + mIndex++; + if (mIndex > dtohl(mTypeVariant->data->entryCount)) { + mIndex = dtohl(mTypeVariant->data->entryCount); + } + return *this; +} + +const ResTable_entry* TypeVariant::iterator::operator*() const { + const ResTable_type* type = mTypeVariant->data; + const uint32_t entryCount = dtohl(type->entryCount); + if (mIndex >= entryCount) { + return NULL; + } + + const uintptr_t containerEnd = reinterpret_cast<uintptr_t>(type) + + dtohl(type->header.size); + const uint32_t* const entryIndices = reinterpret_cast<const uint32_t*>( + reinterpret_cast<uintptr_t>(type) + dtohs(type->header.headerSize)); + if (reinterpret_cast<uintptr_t>(entryIndices) + (sizeof(uint32_t) * entryCount) > containerEnd) { + ALOGE("Type's entry indices extend beyond its boundaries"); + return NULL; + } + + const uint32_t entryOffset = dtohl(entryIndices[mIndex]); + if (entryOffset == ResTable_type::NO_ENTRY) { + return NULL; + } + + if ((entryOffset & 0x3) != 0) { + ALOGE("Index %u points to entry with unaligned offset 0x%08x", mIndex, entryOffset); + return NULL; + } + + const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>( + reinterpret_cast<uintptr_t>(type) + dtohl(type->entriesStart) + entryOffset); + if (reinterpret_cast<uintptr_t>(entry) > containerEnd - sizeof(*entry)) { + ALOGE("Entry offset at index %u points outside the Type's boundaries", mIndex); + return NULL; + } else if (reinterpret_cast<uintptr_t>(entry) + dtohs(entry->size) > containerEnd) { + ALOGE("Entry at index %u extends beyond Type's boundaries", mIndex); + return NULL; + } else if (dtohs(entry->size) < sizeof(*entry)) { + ALOGE("Entry at index %u is too small (%u)", mIndex, dtohs(entry->size)); + return NULL; + } + return entry; +} + +} // namespace android diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk index 9e9649cf3675..4ff6eecf1685 100644 --- a/libs/androidfw/tests/Android.mk +++ b/libs/androidfw/tests/Android.mk @@ -1,33 +1,66 @@ -# Build the unit tests. +# +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# ========================================================== +# Setup some common variables for the different build +# targets here. +# ========================================================== LOCAL_PATH:= $(call my-dir) +testFiles := \ + ByteBucketArray_test.cpp \ + Idmap_test.cpp \ + ResourceTypes_test.cpp \ + ResTable_test.cpp \ + Split_test.cpp \ + TypeWrappers_test.cpp \ + ZipUtils_test.cpp + +# ========================================================== +# Build the host tests: libandroidfw_tests +# ========================================================== +include $(CLEAR_VARS) + +LOCAL_MODULE := libandroidfw_tests + +LOCAL_SRC_FILES := $(testFiles) +LOCAL_STATIC_LIBRARIES := \ + libandroidfw \ + libutils \ + libcutils \ + liblog + +include $(BUILD_HOST_NATIVE_TEST) + + +# ========================================================== +# Build the device tests: libandroidfw_tests +# ========================================================== include $(CLEAR_VARS) -# Build the unit tests. -test_src_files := \ +LOCAL_MODULE := libandroidfw_tests + +LOCAL_SRC_FILES := $(testFiles) \ BackupData_test.cpp \ - ObbFile_test.cpp \ - ZipUtils_test.cpp \ - ResourceTypes_test.cpp + ObbFile_test.cpp -shared_libraries := \ +LOCAL_SHARED_LIBRARIES := \ libandroidfw \ libcutils \ libutils \ libui \ libstlport -static_libraries := \ - libgtest \ - libgtest_main - -$(foreach file,$(test_src_files), \ - $(eval include $(CLEAR_VARS)) \ - $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \ - $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \ - $(eval LOCAL_SRC_FILES := $(file)) \ - $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \ - $(eval include $(BUILD_NATIVE_TEST)) \ -) - -# Build the manual test programs. -include $(call all-makefiles-under, $(LOCAL_PATH)) +include $(BUILD_NATIVE_TEST) diff --git a/libs/androidfw/tests/ByteBucketArray_test.cpp b/libs/androidfw/tests/ByteBucketArray_test.cpp new file mode 100644 index 000000000000..376e79c6e7cb --- /dev/null +++ b/libs/androidfw/tests/ByteBucketArray_test.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <androidfw/ByteBucketArray.h> + +#include <gtest/gtest.h> + +using android::ByteBucketArray; + +TEST(ByteBucketArrayTest, TestSparseInsertion) { + ByteBucketArray<int> bba; + ASSERT_TRUE(bba.set(0, 1)); + ASSERT_TRUE(bba.set(10, 2)); + ASSERT_TRUE(bba.set(26, 3)); + ASSERT_TRUE(bba.set(129, 4)); + ASSERT_TRUE(bba.set(234, 5)); + + for (size_t i = 0; i < bba.size(); i++) { + switch (i) { + case 0: EXPECT_EQ(1, bba[i]); break; + case 10: EXPECT_EQ(2, bba[i]); break; + case 26: EXPECT_EQ(3, bba[i]); break; + case 129: EXPECT_EQ(4, bba[i]); break; + case 234: EXPECT_EQ(5, bba[i]); break; + default: EXPECT_EQ(0, bba[i]); break; + } + } +} diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp new file mode 100644 index 000000000000..d829b7603ad8 --- /dev/null +++ b/libs/androidfw/tests/Idmap_test.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <androidfw/ResourceTypes.h> + +#include <utils/String8.h> +#include <utils/String16.h> +#include "TestHelpers.h" + +#include <gtest/gtest.h> + +using namespace android; + +namespace { + +/** + * Include a binary resource table. + * + * Package: com.android.test.basic + */ +#include "data/basic/basic_arsc.h" + +/** + * Include a binary resource table. + * This table is an overlay. + * + * Package: com.android.test.basic + */ +#include "data/overlay/overlay_arsc.h" + +enum { MAY_NOT_BE_BAG = false }; + +static const uint32_t attr_attr1 = 0x7f010000; +static const uint32_t attr_attr2 = 0x7f010001; +static const uint32_t string_test1 = 0x7f020000; +static const uint32_t string_test2 = 0x7f020001; +static const uint32_t integer_number1 = 0x7f030000; +static const uint32_t integer_number2 = 0x7f030001; +static const uint32_t style_Theme1 = 0x7f040000; +static const uint32_t style_Theme2 = 0x7f040001; +static const uint32_t array_integerArray1 = 0x7f050000; + +class IdmapTest : public ::testing::Test { +protected: + virtual void SetUp() { + ASSERT_EQ(NO_ERROR, mTargetTable.add(basic_arsc, basic_arsc_len)); + ASSERT_EQ(NO_ERROR, mOverlayTable.add(overlay_arsc, overlay_arsc_len)); + char targetName[256] = "com.android.test.basic"; + ASSERT_EQ(NO_ERROR, mTargetTable.createIdmap(mOverlayTable, 0, 0, + targetName, targetName, &mData, &mDataSize)); + } + + virtual void TearDown() { + free(mData); + } + + ResTable mTargetTable; + ResTable mOverlayTable; + void* mData; + size_t mDataSize; +}; + +TEST_F(IdmapTest, canLoadIdmap) { + ASSERT_EQ(NO_ERROR, mTargetTable.add(overlay_arsc, overlay_arsc_len, mData, mDataSize)); +} + +TEST_F(IdmapTest, overlayOverridesResourceValue) { + Res_value val; + ssize_t block = mTargetTable.getResource(string_test2, &val, false); + ASSERT_GE(block, 0); + ASSERT_EQ(Res_value::TYPE_STRING, val.dataType); + const ResStringPool* pool = mTargetTable.getTableStringBlock(block); + ASSERT_TRUE(pool != NULL); + ASSERT_LT(val.data, pool->size()); + + size_t strLen; + const char16_t* targetStr16 = pool->stringAt(val.data, &strLen); + ASSERT_TRUE(targetStr16 != NULL); + ASSERT_EQ(String16("test2"), String16(targetStr16, strLen)); + + ASSERT_EQ(NO_ERROR, mTargetTable.add(overlay_arsc, overlay_arsc_len, mData, mDataSize)); + + ssize_t newBlock = mTargetTable.getResource(string_test2, &val, false); + ASSERT_GE(newBlock, 0); + ASSERT_NE(block, newBlock); + ASSERT_EQ(Res_value::TYPE_STRING, val.dataType); + pool = mTargetTable.getTableStringBlock(newBlock); + ASSERT_TRUE(pool != NULL); + ASSERT_LT(val.data, pool->size()); + + targetStr16 = pool->stringAt(val.data, &strLen); + ASSERT_TRUE(targetStr16 != NULL); + ASSERT_EQ(String16("test2-overlay"), String16(targetStr16, strLen)); +} + +TEST_F(IdmapTest, overlaidResourceHasSameName) { + ASSERT_EQ(NO_ERROR, mTargetTable.add(overlay_arsc, overlay_arsc_len, mData, mDataSize)); + + ResTable::resource_name resName; + ASSERT_TRUE(mTargetTable.getResourceName(array_integerArray1, false, &resName)); + + ASSERT_TRUE(resName.package != NULL); + ASSERT_TRUE(resName.type != NULL); + ASSERT_TRUE(resName.name != NULL); + + EXPECT_EQ(String16("com.android.test.basic"), String16(resName.package, resName.packageLen)); + EXPECT_EQ(String16("array"), String16(resName.type, resName.typeLen)); + EXPECT_EQ(String16("integerArray1"), String16(resName.name, resName.nameLen)); +} + +} // namespace diff --git a/libs/androidfw/tests/ResTable_test.cpp b/libs/androidfw/tests/ResTable_test.cpp new file mode 100644 index 000000000000..54d42c39752b --- /dev/null +++ b/libs/androidfw/tests/ResTable_test.cpp @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <androidfw/ResourceTypes.h> + +#include <utils/String8.h> +#include <utils/String16.h> +#include "TestHelpers.h" + +#include <gtest/gtest.h> + +using namespace android; + +namespace { + +/** + * Include a binary resource table. + * + * Package: com.android.test.basic + */ +#include "data/basic/basic_arsc.h" + +enum { MAY_NOT_BE_BAG = false }; + +static const uint32_t attr_attr1 = 0x7f010000; +static const uint32_t attr_attr2 = 0x7f010001; +static const uint32_t string_test1 = 0x7f020000; +static const uint32_t string_test2 = 0x7f020001; +static const uint32_t integer_number1 = 0x7f030000; +static const uint32_t integer_number2 = 0x7f030001; +static const uint32_t style_Theme1 = 0x7f040000; +static const uint32_t style_Theme2 = 0x7f040001; +static const uint32_t array_integerArray1 = 0x7f050000; + +TEST(ResTableTest, shouldLoadSuccessfully) { + ResTable table; + ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len)); +} + +TEST(ResTableTest, simpleTypeIsRetrievedCorrectly) { + ResTable table; + ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len)); + + Res_value val; + ssize_t block = table.getResource(string_test1, &val, MAY_NOT_BE_BAG); + + ASSERT_GE(block, 0); + ASSERT_EQ(Res_value::TYPE_STRING, val.dataType); + + const ResStringPool* pool = table.getTableStringBlock(block); + ASSERT_TRUE(NULL != pool); + ASSERT_EQ(String8("test1"), pool->string8ObjectAt(val.data)); +} + +TEST(ResTableTest, resourceNameIsResolved) { + ResTable table; + ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len)); + + String16 defPackage("com.android.test.basic"); + String16 testName("@string/test1"); + uint32_t resID = table.identifierForName(testName.string(), testName.size(), + 0, 0, + defPackage.string(), defPackage.size()); + ASSERT_NE(uint32_t(0x00000000), resID); + ASSERT_EQ(string_test1, resID); +} + +TEST(ResTableTest, noParentThemeIsAppliedCorrectly) { + ResTable table; + ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len)); + + ResTable::Theme theme(table); + ASSERT_EQ(NO_ERROR, theme.applyStyle(style_Theme1)); + + Res_value val; + uint32_t specFlags = 0; + ssize_t index = theme.getAttribute(attr_attr1, &val, &specFlags); + ASSERT_GE(index, 0); + ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType); + ASSERT_EQ(uint32_t(100), val.data); + + index = theme.getAttribute(attr_attr2, &val, &specFlags); + ASSERT_GE(index, 0); + ASSERT_EQ(Res_value::TYPE_REFERENCE, val.dataType); + ASSERT_EQ(integer_number1, val.data); +} + +TEST(ResTableTest, parentThemeIsAppliedCorrectly) { + ResTable table; + ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len)); + + ResTable::Theme theme(table); + ASSERT_EQ(NO_ERROR, theme.applyStyle(style_Theme2)); + + Res_value val; + uint32_t specFlags = 0; + ssize_t index = theme.getAttribute(attr_attr1, &val, &specFlags); + ASSERT_GE(index, 0); + ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType); + ASSERT_EQ(uint32_t(300), val.data); + + index = theme.getAttribute(attr_attr2, &val, &specFlags); + ASSERT_GE(index, 0); + ASSERT_EQ(Res_value::TYPE_REFERENCE, val.dataType); + ASSERT_EQ(integer_number1, val.data); +} + +TEST(ResTableTest, referenceToBagIsNotResolved) { + ResTable table; + ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len)); + + Res_value val; + ssize_t block = table.getResource(integer_number2, &val, MAY_NOT_BE_BAG); + ASSERT_GE(block, 0); + ASSERT_EQ(Res_value::TYPE_REFERENCE, val.dataType); + ASSERT_EQ(array_integerArray1, val.data); + + ssize_t newBlock = table.resolveReference(&val, block); + EXPECT_EQ(block, newBlock); + EXPECT_EQ(Res_value::TYPE_REFERENCE, val.dataType); + EXPECT_EQ(array_integerArray1, val.data); +} + +TEST(ResTableTest, resourcesStillAccessibleAfterParameterChange) { + ResTable table; + ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len)); + + Res_value val; + ssize_t block = table.getResource(integer_number1, &val, MAY_NOT_BE_BAG); + ASSERT_GE(block, 0); + ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType); + + const ResTable::bag_entry* entry; + ssize_t count = table.lockBag(array_integerArray1, &entry); + ASSERT_GE(count, 0); + table.unlockBag(entry); + + ResTable_config param; + memset(¶m, 0, sizeof(param)); + param.density = 320; + table.setParameters(¶m); + + block = table.getResource(integer_number1, &val, MAY_NOT_BE_BAG); + ASSERT_GE(block, 0); + ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType); + + count = table.lockBag(array_integerArray1, &entry); + ASSERT_GE(count, 0); + table.unlockBag(entry); +} + +TEST(ResTableTest, resourceIsOverridenWithBetterConfig) { + ResTable table; + ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len)); + + Res_value val; + ssize_t block = table.getResource(integer_number1, &val, MAY_NOT_BE_BAG); + ASSERT_GE(block, 0); + ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType); + ASSERT_EQ(uint32_t(200), val.data); + + ResTable_config param; + memset(¶m, 0, sizeof(param)); + param.language[0] = 's'; + param.language[1] = 'v'; + param.country[0] = 'S'; + param.country[1] = 'E'; + table.setParameters(¶m); + + block = table.getResource(integer_number1, &val, MAY_NOT_BE_BAG); + ASSERT_GE(block, 0); + ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType); + ASSERT_EQ(uint32_t(400), val.data); +} + +} diff --git a/libs/androidfw/tests/ResourceTypes_test.cpp b/libs/androidfw/tests/ResourceTypes_test.cpp index 4888b4a2ea7b..6041e08009e3 100644 --- a/libs/androidfw/tests/ResourceTypes_test.cpp +++ b/libs/androidfw/tests/ResourceTypes_test.cpp @@ -64,8 +64,8 @@ TEST(ResourceTypesTest, ResourceConfig_packAndUnpack3LetterLanguage) { config.packLanguage("eng"); // 1-00110-01 101-00100 - EXPECT_EQ(0x99, config.language[0]); - EXPECT_EQ(0xa4, config.language[1]); + EXPECT_EQ('\x99', config.language[0]); + EXPECT_EQ('\xA4', config.language[1]); char out[4] = { 1, 1, 1, 1}; config.unpackLanguage(out); diff --git a/libs/androidfw/tests/Split_test.cpp b/libs/androidfw/tests/Split_test.cpp new file mode 100644 index 000000000000..dbfdeae04d8e --- /dev/null +++ b/libs/androidfw/tests/Split_test.cpp @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <androidfw/ResourceTypes.h> + +#include <utils/String8.h> +#include <utils/String16.h> +#include "TestHelpers.h" + +#include <gtest/gtest.h> + +/** + * Include a binary resource table. This table + * is a base table for an APK split. + * + * Package: com.android.example.split + * + * layout/main 0x7f020000 {default, fr-sw600dp-v13} + * + * string/app_title 0x7f030000 {default} + * string/test 0x7f030001 {default} + * string/boom 0x7f030002 {default} + * string/blah 0x7f030003 {default} + * + * array/lotsofstrings 0x7f040000 {default} + * array/numList 0x7f040001 {default} + * array/ary 0x7f040002 {default} + * + */ +#include "data/split_base_arsc.h" + +/** + * Include a binary resource table. This table + * is a configuration split table for an APK split. + * + * Package: com.android.example.split + * + * string/app_title 0x7f030000 {fr} + * string/test 0x7f030001 {de,fr} + * string/blah 0x7f030003 {fr} + * + * array/lotsofstrings 0x7f040000 {fr} + * + */ +#include "data/split_de_fr_arsc.h" + + +using namespace android; + +enum { MAY_NOT_BE_BAG = false }; + +void makeConfigFrench(ResTable_config* config) { + memset(config, 0, sizeof(*config)); + config->language[0] = 'f'; + config->language[1] = 'r'; +} + +TEST(SplitTest, TestLoadBase) { + ResTable table; + ASSERT_EQ(NO_ERROR, table.add(split_base_arsc, split_base_arsc_len)); +} + +TEST(SplitTest, TestGetResourceFromBase) { + ResTable_config frenchConfig; + makeConfigFrench(&frenchConfig); + + ResTable table; + table.setParameters(&frenchConfig); + + ASSERT_EQ(NO_ERROR, table.add(split_base_arsc, split_base_arsc_len)); + + ResTable_config expectedConfig; + memset(&expectedConfig, 0, sizeof(expectedConfig)); + + Res_value val; + ResTable_config config; + ssize_t block = table.getResource(0x7f030000, &val, MAY_NOT_BE_BAG, 0, NULL, &config); + + // The returned block should tell us which string pool to get the value, if it is a string. + EXPECT_GE(block, 0); + + // We expect the default resource to be selected since it is the only resource configuration. + EXPECT_EQ(0, expectedConfig.compare(config)); + + EXPECT_EQ(Res_value::TYPE_STRING, val.dataType); +} + +TEST(SplitTest, TestGetResourceFromSplit) { + ResTable_config expectedConfig; + makeConfigFrench(&expectedConfig); + + ResTable table; + table.setParameters(&expectedConfig); + + ASSERT_EQ(NO_ERROR, table.add(split_base_arsc, split_base_arsc_len)); + ASSERT_EQ(NO_ERROR, table.add(split_de_fr_arsc, split_de_fr_arsc_len)); + + Res_value val; + ResTable_config config; + ssize_t block = table.getResource(0x7f030000, &val, MAY_NOT_BE_BAG, 0, NULL, &config); + + EXPECT_GE(block, 0); + + EXPECT_EQ(0, expectedConfig.compare(config)); + + EXPECT_EQ(Res_value::TYPE_STRING, val.dataType); +} + +TEST(SplitTest, ResourcesFromBaseAndSplitHaveSameNames) { + ResTable_config expectedConfig; + makeConfigFrench(&expectedConfig); + + ResTable table; + table.setParameters(&expectedConfig); + + ASSERT_EQ(NO_ERROR, table.add(split_base_arsc, split_base_arsc_len)); + + ResTable::resource_name baseName; + EXPECT_TRUE(table.getResourceName(0x7f030003, false, &baseName)); + + ASSERT_EQ(NO_ERROR, table.add(split_de_fr_arsc, split_de_fr_arsc_len)); + + ResTable::resource_name frName; + EXPECT_TRUE(table.getResourceName(0x7f030003, false, &frName)); + + EXPECT_EQ( + String16(baseName.package, baseName.packageLen), + String16(frName.package, frName.packageLen)); + + EXPECT_EQ( + String16(baseName.type, baseName.typeLen), + String16(frName.type, frName.typeLen)); + + EXPECT_EQ( + String16(baseName.name, baseName.nameLen), + String16(frName.name, frName.nameLen)); +} + +TEST(SplitTest, TypeEntrySpecFlagsAreUpdated) { + ResTable_config defaultConfig; + memset(&defaultConfig, 0, sizeof(defaultConfig)); + + ResTable table; + ASSERT_EQ(NO_ERROR, table.add(split_base_arsc, split_base_arsc_len)); + + Res_value val; + uint32_t specFlags = 0; + ssize_t block = table.getResource(0x7f030000, &val, MAY_NOT_BE_BAG, 0, &specFlags, NULL); + EXPECT_GE(block, 0); + + EXPECT_EQ(static_cast<uint32_t>(0), specFlags); + + ASSERT_EQ(NO_ERROR, table.add(split_de_fr_arsc, split_de_fr_arsc_len)); + + uint32_t frSpecFlags = 0; + block = table.getResource(0x7f030000, &val, MAY_NOT_BE_BAG, 0, &frSpecFlags, NULL); + EXPECT_GE(block, 0); + + EXPECT_EQ(ResTable_config::CONFIG_LOCALE, frSpecFlags); +} diff --git a/libs/androidfw/tests/TestHelpers.h b/libs/androidfw/tests/TestHelpers.h new file mode 100644 index 000000000000..75a233acad26 --- /dev/null +++ b/libs/androidfw/tests/TestHelpers.h @@ -0,0 +1,17 @@ +#ifndef __TEST_HELPERS_H +#define __TEST_HELPERS_H + +#include <ostream> + +#include <utils/String8.h> +#include <utils/String16.h> + +static inline ::std::ostream& operator<<(::std::ostream& out, const android::String8& str) { + return out << str.string(); +} + +static inline ::std::ostream& operator<<(::std::ostream& out, const android::String16& str) { + return out << android::String8(str).string(); +} + +#endif // __TEST_HELPERS_H diff --git a/libs/androidfw/tests/TypeWrappers_test.cpp b/libs/androidfw/tests/TypeWrappers_test.cpp new file mode 100644 index 000000000000..d69abe5d0f11 --- /dev/null +++ b/libs/androidfw/tests/TypeWrappers_test.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <androidfw/ResourceTypes.h> +#include <androidfw/TypeWrappers.h> +#include <utils/String8.h> + +#include <gtest/gtest.h> + +namespace android { + +void* createTypeData() { + ResTable_type t; + memset(&t, 0, sizeof(t)); + t.header.type = RES_TABLE_TYPE_TYPE; + t.header.headerSize = sizeof(t); + t.id = 1; + t.entryCount = 3; + + uint32_t offsets[3]; + t.entriesStart = t.header.headerSize + sizeof(offsets); + t.header.size = t.entriesStart; + + offsets[0] = 0; + ResTable_entry e1; + memset(&e1, 0, sizeof(e1)); + e1.size = sizeof(e1); + e1.key.index = 0; + t.header.size += sizeof(e1); + + Res_value v1; + memset(&v1, 0, sizeof(v1)); + t.header.size += sizeof(v1); + + offsets[1] = ResTable_type::NO_ENTRY; + + offsets[2] = sizeof(e1) + sizeof(v1); + ResTable_entry e2; + memset(&e2, 0, sizeof(e2)); + e2.size = sizeof(e2); + e2.key.index = 1; + t.header.size += sizeof(e2); + + Res_value v2; + memset(&v2, 0, sizeof(v2)); + t.header.size += sizeof(v2); + + uint8_t* data = (uint8_t*)malloc(t.header.size); + uint8_t* p = data; + memcpy(p, &t, sizeof(t)); + p += sizeof(t); + memcpy(p, offsets, sizeof(offsets)); + p += sizeof(offsets); + memcpy(p, &e1, sizeof(e1)); + p += sizeof(e1); + memcpy(p, &v1, sizeof(v1)); + p += sizeof(v1); + memcpy(p, &e2, sizeof(e2)); + p += sizeof(e2); + memcpy(p, &v2, sizeof(v2)); + p += sizeof(v2); + return data; +} + +TEST(TypeVariantIteratorTest, shouldIterateOverTypeWithoutErrors) { + ResTable_type* data = (ResTable_type*) createTypeData(); + + TypeVariant v(data); + + TypeVariant::iterator iter = v.beginEntries(); + ASSERT_EQ(uint32_t(0), iter.index()); + ASSERT_TRUE(NULL != *iter); + ASSERT_EQ(uint32_t(0), iter->key.index); + ASSERT_NE(v.endEntries(), iter); + + iter++; + + ASSERT_EQ(uint32_t(1), iter.index()); + ASSERT_TRUE(NULL == *iter); + ASSERT_NE(v.endEntries(), iter); + + iter++; + + ASSERT_EQ(uint32_t(2), iter.index()); + ASSERT_TRUE(NULL != *iter); + ASSERT_EQ(uint32_t(1), iter->key.index); + ASSERT_NE(v.endEntries(), iter); + + iter++; + + ASSERT_EQ(v.endEntries(), iter); + + free(data); +} + +} // namespace android diff --git a/libs/androidfw/tests/data/.gitignore b/libs/androidfw/tests/data/.gitignore new file mode 100644 index 000000000000..c05cfb043024 --- /dev/null +++ b/libs/androidfw/tests/data/.gitignore @@ -0,0 +1,2 @@ +*.apk +*.arsc diff --git a/libs/androidfw/tests/data/basic/AndroidManifest.xml b/libs/androidfw/tests/data/basic/AndroidManifest.xml new file mode 100644 index 000000000000..a56ac18e900b --- /dev/null +++ b/libs/androidfw/tests/data/basic/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.test.basic"> + <application> + </application> +</manifest> diff --git a/libs/androidfw/tests/data/basic/basic_arsc.h b/libs/androidfw/tests/data/basic/basic_arsc.h new file mode 100644 index 000000000000..6532076d3493 --- /dev/null +++ b/libs/androidfw/tests/data/basic/basic_arsc.h @@ -0,0 +1,131 @@ +unsigned char basic_arsc[] = { + 0x02, 0x00, 0x0c, 0x00, 0xfc, 0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x1c, 0x00, 0x40, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x31, 0x00, + 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, + 0x32, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xb0, 0x05, 0x00, 0x00, + 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00, + 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00, + 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, + 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00, 0x69, 0x00, 0x63, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x9c, 0x01, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x7c, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, + 0x3c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00, + 0x72, 0x00, 0x00, 0x00, 0x06, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00, + 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x00, 0x00, 0x07, 0x00, 0x69, 0x00, + 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00, + 0x00, 0x00, 0x05, 0x00, 0x73, 0x00, 0x74, 0x00, 0x79, 0x00, 0x6c, 0x00, + 0x65, 0x00, 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, 0x72, 0x00, 0x72, 0x00, + 0x61, 0x00, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, + 0xdc, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, + 0x5c, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x31, 0x00, + 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, + 0x32, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, + 0x74, 0x00, 0x31, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, + 0x73, 0x00, 0x74, 0x00, 0x32, 0x00, 0x00, 0x00, 0x07, 0x00, 0x6e, 0x00, + 0x75, 0x00, 0x6d, 0x00, 0x62, 0x00, 0x65, 0x00, 0x72, 0x00, 0x31, 0x00, + 0x00, 0x00, 0x07, 0x00, 0x6e, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x62, 0x00, + 0x65, 0x00, 0x72, 0x00, 0x32, 0x00, 0x00, 0x00, 0x06, 0x00, 0x54, 0x00, + 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x31, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x54, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, + 0x32, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, + 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00, 0x41, 0x00, 0x72, 0x00, + 0x72, 0x00, 0x61, 0x00, 0x79, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x44, 0x00, 0x84, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x08, 0x00, 0x00, 0x10, 0x05, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x05, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x44, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x44, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x10, 0xc8, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x05, 0x7f, + 0x01, 0x02, 0x44, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x73, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x10, 0x90, 0x01, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, + 0x90, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7f, 0x08, 0x00, 0x00, 0x10, + 0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x7f, 0x08, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x03, 0x7f, 0x10, 0x00, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x7f, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7f, + 0x08, 0x00, 0x00, 0x10, 0x2c, 0x01, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x7c, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, + 0x08, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, + 0x08, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x00 +}; +unsigned int basic_arsc_len = 1532; diff --git a/libs/androidfw/tests/data/basic/build b/libs/androidfw/tests/data/basic/build new file mode 100755 index 000000000000..237342c8dc40 --- /dev/null +++ b/libs/androidfw/tests/data/basic/build @@ -0,0 +1,6 @@ +#!/bin/bash + +aapt package -M AndroidManifest.xml -S res -F bundle.apk -f && \ +unzip bundle.apk resources.arsc && \ +mv resources.arsc basic.arsc && \ +xxd -i basic.arsc > basic_arsc.h diff --git a/libs/androidfw/tests/data/basic/res/values-sv/values.xml b/libs/androidfw/tests/data/basic/res/values-sv/values.xml new file mode 100644 index 000000000000..9d523071dd07 --- /dev/null +++ b/libs/androidfw/tests/data/basic/res/values-sv/values.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <integer name="number1">400</integer> +</resources> diff --git a/libs/androidfw/tests/data/basic/res/values/values.xml b/libs/androidfw/tests/data/basic/res/values/values.xml new file mode 100644 index 000000000000..662eda6a4ed5 --- /dev/null +++ b/libs/androidfw/tests/data/basic/res/values/values.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <attr name="attr1" format="reference|integer" /> + <attr name="attr2" format="reference|integer" /> + + <string name="test1">test1</string> + <string name="test2">test2</string> + + <integer name="number1">200</integer> + <integer name="number2">@array/integerArray1</integer> + + <style name="Theme1"> + <item name="com.android.test.basic:attr1">100</item> + <item name="com.android.test.basic:attr2">@integer/number1</item> + </style> + + <style name="Theme2" parent="@com.android.test.basic:style/Theme1"> + <item name="com.android.test.basic:attr1">300</item> + </style> + + <integer-array name="integerArray1"> + <item>1</item> + <item>2</item> + <item>3</item> + </integer-array> +</resources> diff --git a/libs/androidfw/tests/data/overlay/AndroidManifest.xml b/libs/androidfw/tests/data/overlay/AndroidManifest.xml new file mode 100644 index 000000000000..a56ac18e900b --- /dev/null +++ b/libs/androidfw/tests/data/overlay/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.test.basic"> + <application> + </application> +</manifest> diff --git a/libs/androidfw/tests/data/overlay/build b/libs/androidfw/tests/data/overlay/build new file mode 100755 index 000000000000..87cf6de4c933 --- /dev/null +++ b/libs/androidfw/tests/data/overlay/build @@ -0,0 +1,6 @@ +#!/bin/bash + +aapt package -M AndroidManifest.xml -S res -F bundle.apk -f && \ +unzip bundle.apk resources.arsc && \ +mv resources.arsc overlay.arsc && \ +xxd -i overlay.arsc > overlay_arsc.h diff --git a/libs/androidfw/tests/data/overlay/overlay_arsc.h b/libs/androidfw/tests/data/overlay/overlay_arsc.h new file mode 100644 index 000000000000..5bd98b28409d --- /dev/null +++ b/libs/androidfw/tests/data/overlay/overlay_arsc.h @@ -0,0 +1,69 @@ +unsigned char overlay_arsc[] = { + 0x02, 0x00, 0x0c, 0x00, 0x10, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x1c, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x74, 0x00, + 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x32, 0x00, 0x2d, 0x00, 0x6f, 0x00, + 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xc4, 0x02, 0x00, 0x00, + 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00, + 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00, + 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, + 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00, 0x69, 0x00, 0x63, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x74, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x54, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00, + 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00, 0x06, 0x00, 0x73, 0x00, + 0x74, 0x00, 0x72, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x61, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, + 0x74, 0x00, 0x32, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x69, 0x00, 0x6e, 0x00, + 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00, 0x41, 0x00, + 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00, 0x31, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x44, 0x00, 0x70, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10, + 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10, + 0x0b, 0x00, 0x00, 0x00 +}; +unsigned int overlay_arsc_len = 784; diff --git a/libs/androidfw/tests/data/overlay/res/values/values.xml b/libs/androidfw/tests/data/overlay/res/values/values.xml new file mode 100644 index 000000000000..227e88973cc7 --- /dev/null +++ b/libs/androidfw/tests/data/overlay/res/values/values.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="test2">test2-overlay</string> + <integer-array name="integerArray1"> + <item>10</item> + <item>11</item> + </integer-array> +</resources> diff --git a/libs/androidfw/tests/data/split_base_arsc.h b/libs/androidfw/tests/data/split_base_arsc.h new file mode 100644 index 000000000000..e0321e9bf492 --- /dev/null +++ b/libs/androidfw/tests/data/split_base_arsc.h @@ -0,0 +1,221 @@ +unsigned char split_base_arsc[] = { + 0x02, 0x00, 0x0c, 0x00, 0x30, 0x0a, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x1c, 0x00, 0x94, 0x01, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, + 0x72, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xa6, 0x00, 0x00, 0x00, + 0xb4, 0x00, 0x00, 0x00, 0xc6, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, + 0xf4, 0x00, 0x00, 0x00, 0x0c, 0x01, 0x00, 0x00, 0x2a, 0x01, 0x00, 0x00, + 0x44, 0x01, 0x00, 0x00, 0x13, 0x00, 0x72, 0x00, 0x65, 0x00, 0x73, 0x00, + 0x2f, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00, + 0x74, 0x00, 0x2f, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, + 0x2e, 0x00, 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x22, 0x00, + 0x72, 0x00, 0x65, 0x00, 0x73, 0x00, 0x2f, 0x00, 0x6c, 0x00, 0x61, 0x00, + 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x2d, 0x00, 0x66, 0x00, + 0x72, 0x00, 0x2d, 0x00, 0x73, 0x00, 0x77, 0x00, 0x36, 0x00, 0x30, 0x00, + 0x30, 0x00, 0x64, 0x00, 0x70, 0x00, 0x2d, 0x00, 0x76, 0x00, 0x31, 0x00, + 0x33, 0x00, 0x2f, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, + 0x2e, 0x00, 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x09, 0x00, + 0x53, 0x00, 0x70, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x74, 0x00, 0x20, 0x00, + 0x41, 0x00, 0x50, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x48, 0x00, + 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x2c, 0x00, 0x20, 0x00, + 0x57, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6c, 0x00, 0x64, 0x00, 0x21, 0x00, + 0x00, 0x00, 0x05, 0x00, 0x42, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x6d, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x07, 0x00, 0x42, 0x00, 0x6c, 0x00, 0x61, 0x00, + 0x68, 0x00, 0x2e, 0x00, 0x2e, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x0b, 0x00, + 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x20, 0x00, + 0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x72, 0x00, 0x65, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x47, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x20, 0x00, + 0x62, 0x00, 0x79, 0x00, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x49, 0x00, + 0x20, 0x00, 0x68, 0x00, 0x61, 0x00, 0x7a, 0x00, 0x20, 0x00, 0x41, 0x00, + 0x4c, 0x00, 0x4c, 0x00, 0x21, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x49, 0x00, + 0x20, 0x00, 0x68, 0x00, 0x61, 0x00, 0x7a, 0x00, 0x20, 0x00, 0x31, 0x00, + 0x21, 0x00, 0x31, 0x00, 0x21, 0x00, 0x20, 0x00, 0x3a, 0x00, 0x29, 0x00, + 0x00, 0x00, 0x0b, 0x00, 0x49, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x6f, 0x00, + 0x20, 0x00, 0x68, 0x00, 0x61, 0x00, 0x7a, 0x00, 0x20, 0x00, 0x3a, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1c, 0x01, + 0x90, 0x08, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, + 0x6d, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, + 0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x2e, 0x00, 0x65, 0x00, 0x78, 0x00, + 0x61, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x2e, 0x00, + 0x73, 0x00, 0x70, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x74, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1c, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0xf0, 0x01, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0xd4, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, + 0x3a, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, + 0x66, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00, + 0x74, 0x00, 0x00, 0x00, 0x06, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00, + 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, + 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00, 0x00, 0x00, 0x07, 0x00, + 0x70, 0x00, 0x6c, 0x00, 0x75, 0x00, 0x72, 0x00, 0x61, 0x00, 0x6c, 0x00, + 0x73, 0x00, 0x00, 0x00, 0x04, 0x00, 0x62, 0x00, 0x6f, 0x00, 0x6f, 0x00, + 0x6c, 0x00, 0x00, 0x00, 0x05, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6c, 0x00, + 0x6f, 0x00, 0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x64, 0x00, 0x69, 0x00, + 0x6d, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x69, 0x00, + 0x64, 0x00, 0x00, 0x00, 0x07, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, + 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x1c, 0x00, 0x30, 0x01, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, + 0x46, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00, + 0xa4, 0x00, 0x00, 0x00, 0xb2, 0x00, 0x00, 0x00, 0xca, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x61, 0x00, 0x70, 0x00, 0x70, 0x00, 0x5f, 0x00, 0x74, 0x00, + 0x69, 0x00, 0x74, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x62, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x62, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x68, 0x00, 0x00, 0x00, 0x0d, 0x00, + 0x6c, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x66, 0x00, + 0x73, 0x00, 0x74, 0x00, 0x72, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, + 0x73, 0x00, 0x00, 0x00, 0x07, 0x00, 0x6e, 0x00, 0x75, 0x00, 0x6d, 0x00, + 0x4c, 0x00, 0x69, 0x00, 0x73, 0x00, 0x74, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x61, 0x00, 0x72, 0x00, 0x79, 0x00, 0x00, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x6c, 0x00, 0x75, 0x00, 0x72, 0x00, 0x00, 0x00, 0x03, 0x00, 0x71, 0x00, + 0x75, 0x00, 0x65, 0x00, 0x00, 0x00, 0x05, 0x00, 0x67, 0x00, 0x72, 0x00, + 0x65, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x77, 0x00, + 0x69, 0x00, 0x64, 0x00, 0x74, 0x00, 0x68, 0x00, 0x00, 0x00, 0x0a, 0x00, + 0x69, 0x00, 0x64, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x69, 0x00, + 0x66, 0x00, 0x69, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x06, 0x00, + 0x6e, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x62, 0x00, 0x65, 0x00, 0x72, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x24, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, + 0x58, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x66, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x20, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, + 0x94, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x54, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, + 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x03, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x05, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0xc8, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x01, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x03, + 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x03, + 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x08, 0x00, 0x00, 0x10, 0xd2, 0x04, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x01, 0x01, 0x00, 0x03, 0x7f, + 0x01, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x01, 0x02, 0x00, 0x03, 0x7f, + 0x02, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x05, 0x01, 0x19, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, + 0x7c, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x03, 0x0a, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x03, 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x03, 0x08, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, + 0x58, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x12, 0xff, 0xff, 0xff, 0xff, + 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, + 0x58, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x1d, 0x00, 0xff, 0x00, 0xff, + 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, + 0x58, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x05, 0x01, 0x17, 0x00, 0x00, + 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x58, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x05, + 0x01, 0xe6, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, + 0x0b, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10, + 0x7b, 0x00, 0x00, 0x00 +}; +unsigned int split_base_arsc_len = 2608; diff --git a/libs/androidfw/tests/data/split_de_fr_arsc.h b/libs/androidfw/tests/data/split_de_fr_arsc.h new file mode 100644 index 000000000000..6f6a41626e0f --- /dev/null +++ b/libs/androidfw/tests/data/split_de_fr_arsc.h @@ -0,0 +1,118 @@ +unsigned char split_de_fr_arsc[] = { + 0x02, 0x00, 0x0c, 0x00, 0x64, 0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x1c, 0x00, 0xb8, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x2c, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x5e, 0x00, 0x00, 0x00, + 0x6c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x41, 0x00, 0x63, 0x00, 0x68, 0x00, + 0x74, 0x00, 0x75, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x41, 0x00, 0x50, 0x00, 0x4b, 0x00, 0x20, 0x00, 0x44, 0x00, + 0x69, 0x00, 0x76, 0x00, 0x69, 0x00, 0x73, 0x00, 0xe9, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x42, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x6a, 0x00, 0x6f, 0x00, + 0x75, 0x00, 0x72, 0x00, 0x2c, 0x00, 0x20, 0x00, 0x4d, 0x00, 0x6f, 0x00, + 0x6e, 0x00, 0x64, 0x00, 0x65, 0x00, 0x21, 0x00, 0x00, 0x00, 0x06, 0x00, + 0x42, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x68, 0x00, 0x2e, 0x00, 0x2e, 0x00, + 0x00, 0x00, 0x05, 0x00, 0x48, 0x00, 0xe9, 0x00, 0x20, 0x00, 0x6c, 0x00, + 0xe0, 0x00, 0x00, 0x00, 0x09, 0x00, 0x41, 0x00, 0x75, 0x00, 0x20, 0x00, + 0x72, 0x00, 0x65, 0x00, 0x76, 0x00, 0x6f, 0x00, 0x69, 0x00, 0x72, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1c, 0x01, 0xa0, 0x04, 0x00, 0x00, + 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00, + 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00, + 0x64, 0x00, 0x2e, 0x00, 0x65, 0x00, 0x78, 0x00, 0x61, 0x00, 0x6d, 0x00, + 0x70, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x2e, 0x00, 0x73, 0x00, 0x70, 0x00, + 0x6c, 0x00, 0x69, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x01, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0xf0, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x1c, 0x00, 0xd4, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, + 0x74, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00, + 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00, 0x06, 0x00, 0x6c, 0x00, + 0x61, 0x00, 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00, 0x69, 0x00, 0x6e, 0x00, + 0x67, 0x00, 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, 0x72, 0x00, 0x72, 0x00, + 0x61, 0x00, 0x79, 0x00, 0x00, 0x00, 0x07, 0x00, 0x70, 0x00, 0x6c, 0x00, + 0x75, 0x00, 0x72, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x73, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x62, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x72, 0x00, + 0x00, 0x00, 0x05, 0x00, 0x64, 0x00, 0x69, 0x00, 0x6d, 0x00, 0x65, 0x00, + 0x6e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x69, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, + 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, + 0x78, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x09, 0x00, 0x61, 0x00, 0x70, 0x00, 0x70, 0x00, + 0x5f, 0x00, 0x74, 0x00, 0x69, 0x00, 0x74, 0x00, 0x6c, 0x00, 0x65, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x62, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x68, 0x00, + 0x00, 0x00, 0x0d, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x73, 0x00, + 0x6f, 0x00, 0x66, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00, 0x69, 0x00, + 0x6e, 0x00, 0x67, 0x00, 0x73, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x65, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, + 0x84, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x54, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x66, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, + 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x44, 0x00, 0x78, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x66, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x10, 0x00, 0x01, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x03, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x03, 0x05, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +unsigned int split_de_fr_arsc_len = 1380; diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp index 77d16ab8d452..8b32c40600eb 100644 --- a/libs/hwui/DamageAccumulator.cpp +++ b/libs/hwui/DamageAccumulator.cpp @@ -170,6 +170,9 @@ void DamageAccumulator::applyRenderNodeTransform(DirtyStack* frame) { } const RenderProperties& props = frame->renderNode->properties(); + if (props.getAlpha() <= 0) { + return; + } // Perform clipping if (props.getClipDamageToBounds() && !frame->pendingDirty.isEmpty()) { diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h index 233f3f0a31b8..d578ef58a6be 100644 --- a/libs/hwui/DisplayListOp.h +++ b/libs/hwui/DisplayListOp.h @@ -1118,8 +1118,8 @@ public: const DeferredDisplayState& state) { DrawStrokableOp::onDefer(renderer, deferInfo, state); if (!mPaint->getPathEffect()) { - renderer.getCaches().tessellationCache.precacheRoundRect(state.mMatrix, - mLocalBounds.getWidth(), mLocalBounds.getHeight(), mRx, mRy, mPaint); + renderer.getCaches().tessellationCache.precacheRoundRect(state.mMatrix, *mPaint, + mLocalBounds.getWidth(), mLocalBounds.getHeight(), mRx, mRy); } } diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index 0e47c6e2b92d..fc4d40bec43f 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -308,6 +308,10 @@ status_t DisplayListRenderer::drawOval(float left, float top, float right, float status_t DisplayListRenderer::drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, bool useCenter, const SkPaint* paint) { + if (fabs(sweepAngle) > 360.0f) { + return drawOval(left, top, right, bottom, paint); + } + paint = refPaint(paint); addDrawOp(new (alloc()) DrawArcOp(left, top, right, bottom, startAngle, sweepAngle, useCenter, paint)); diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 8f3872a42a8b..6397478bec60 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -2556,8 +2556,8 @@ status_t OpenGLRenderer::drawRoundRect(float left, float top, float right, float return drawShape(left, top, texture, p); } - const VertexBuffer* vertexBuffer = mCaches.tessellationCache.getRoundRect(*currentTransform(), - right - left, bottom - top, rx, ry, p); + const VertexBuffer* vertexBuffer = mCaches.tessellationCache.getRoundRect( + *currentTransform(), *p, right - left, bottom - top, rx, ry); return drawVertexBuffer(left, top, *vertexBuffer, p); } @@ -2611,10 +2611,6 @@ status_t OpenGLRenderer::drawArc(float left, float top, float right, float botto return DrawGlInfo::kStatusDone; } - if (fabs(sweepAngle) >= 360.0f) { - return drawOval(left, top, right, bottom, p); - } - // TODO: support fills (accounting for concavity if useCenter && sweepAngle > 180) if (p->getStyle() != SkPaint::kStroke_Style || p->getPathEffect() != 0 || useCenter) { mCaches.activeTexture(0); diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index fc51170cced6..3d93383e2c49 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -20,6 +20,7 @@ #include "RenderNode.h" #include <algorithm> +#include <string> #include <SkCanvas.h> #include <algorithm> @@ -117,7 +118,7 @@ void RenderNode::prepareTree(TreeInfo& info) { } void RenderNode::damageSelf(TreeInfo& info) { - if (isRenderable() && properties().getAlpha() > 0) { + if (isRenderable()) { if (properties().getClipDamageToBounds()) { info.damageAccumulator->dirty(0, 0, properties().getWidth(), properties().getHeight()); } else { @@ -158,7 +159,10 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { applyLayerPropertiesToLayer(info); damageSelf(info); } else if (mLayer->layer.getWidth() != getWidth() || mLayer->layer.getHeight() != getHeight()) { - LayerRenderer::resizeLayer(mLayer, getWidth(), getHeight()); + if (!LayerRenderer::resizeLayer(mLayer, getWidth(), getHeight())) { + LayerRenderer::destroyLayer(mLayer); + mLayer = 0; + } damageSelf(info); } @@ -166,6 +170,15 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { info.damageAccumulator->peekAtDirty(&dirty); info.damageAccumulator->popTransform(); + if (!mLayer) { + if (info.errorHandler) { + std::string msg = "Unable to create layer for "; + msg += getName(); + info.errorHandler->onError(msg); + } + return; + } + if (!dirty.isEmpty()) { mLayer->updateDeferred(this, dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom); } diff --git a/libs/hwui/TessellationCache.cpp b/libs/hwui/TessellationCache.cpp index ef3d0d765ba2..08b54ff0f619 100644 --- a/libs/hwui/TessellationCache.cpp +++ b/libs/hwui/TessellationCache.cpp @@ -39,6 +39,8 @@ namespace uirenderer { TessellationCache::Description::Description() : type(kNone) + , scaleX(1.0f) + , scaleY(1.0f) , aa(false) , cap(SkPaint::kDefault_Cap) , style(SkPaint::kFill_Style) @@ -46,21 +48,13 @@ TessellationCache::Description::Description() memset(&shape, 0, sizeof(Shape)); } -TessellationCache::Description::Description(Type type) +TessellationCache::Description::Description(Type type, const Matrix4& transform, const SkPaint& paint) : type(type) - , aa(false) - , cap(SkPaint::kDefault_Cap) - , style(SkPaint::kFill_Style) - , strokeWidth(1.0f) { - memset(&shape, 0, sizeof(Shape)); -} - -TessellationCache::Description::Description(Type type, const SkPaint* paint) - : type(type) - , aa(paint->isAntiAlias()) - , cap(paint->getStrokeCap()) - , style(paint->getStyle()) - , strokeWidth(paint->getStrokeWidth()) { + , aa(paint.isAntiAlias()) + , cap(paint.getStrokeCap()) + , style(paint.getStyle()) + , strokeWidth(paint.getStrokeWidth()) { + PathTessellator::extractTessellationScales(transform, &scaleX, &scaleY); memset(&shape, 0, sizeof(Shape)); } @@ -70,10 +64,20 @@ hash_t TessellationCache::Description::hash() const { hash = JenkinsHashMix(hash, cap); hash = JenkinsHashMix(hash, style); hash = JenkinsHashMix(hash, android::hash_type(strokeWidth)); + hash = JenkinsHashMix(hash, android::hash_type(scaleX)); + hash = JenkinsHashMix(hash, android::hash_type(scaleY)); hash = JenkinsHashMixBytes(hash, (uint8_t*) &shape, sizeof(Shape)); return JenkinsHashWhiten(hash); } +void TessellationCache::Description::setupMatrixAndPaint(Matrix4* matrix, SkPaint* paint) const { + matrix->loadScale(scaleX, scaleY, 1.0f); + paint->setAntiAlias(aa); + paint->setStrokeCap(cap); + paint->setStyle(style); + paint->setStrokeWidth(strokeWidth); +} + TessellationCache::ShadowDescription::ShadowDescription() : nodeKey(NULL) { memset(&matrixData, 0, 16 * sizeof(float)); @@ -96,20 +100,15 @@ hash_t TessellationCache::ShadowDescription::hash() const { class TessellationCache::TessellationTask : public Task<VertexBuffer*> { public: - TessellationTask(Tessellator tessellator, const Description& description, - const SkPaint* paint) + TessellationTask(Tessellator tessellator, const Description& description) : tessellator(tessellator) - , description(description) - , paint(*paint) { + , description(description) { } ~TessellationTask() {} Tessellator tessellator; Description description; - - //copied, since input paint may not be immutable - const SkPaint paint; }; class TessellationCache::TessellationProcessor : public TaskProcessor<VertexBuffer*> { @@ -121,7 +120,7 @@ public: virtual void onProcess(const sp<Task<VertexBuffer*> >& task) { TessellationTask* t = static_cast<TessellationTask*>(task.get()); ATRACE_NAME("shape tessellation"); - VertexBuffer* buffer = t->tessellator(t->description, t->paint); + VertexBuffer* buffer = t->tessellator(t->description); t->setResult(buffer); } }; @@ -416,21 +415,12 @@ void TessellationCache::getShadowBuffers(const Matrix4* drawTransform, const Rec // Tessellation precaching /////////////////////////////////////////////////////////////////////////////// -static VertexBuffer* tessellatePath(const SkPath& path, const SkPaint* paint, - float scaleX, float scaleY) { - VertexBuffer* buffer = new VertexBuffer(); - Matrix4 matrix; - matrix.loadScale(scaleX, scaleY, 1); - PathTessellator::tessellatePath(path, paint, matrix, *buffer); - return buffer; -} - TessellationCache::Buffer* TessellationCache::getOrCreateBuffer( - const Description& entry, Tessellator tessellator, const SkPaint* paint) { + const Description& entry, Tessellator tessellator) { Buffer* buffer = mCache.get(entry); if (!buffer) { // not cached, enqueue a task to fill the buffer - sp<TessellationTask> task = new TessellationTask(tessellator, entry, paint); + sp<TessellationTask> task = new TessellationTask(tessellator, entry); buffer = new Buffer(task); if (mProcessor == NULL) { @@ -442,43 +432,49 @@ TessellationCache::Buffer* TessellationCache::getOrCreateBuffer( return buffer; } +static VertexBuffer* tessellatePath(const TessellationCache::Description& description, + const SkPath& path) { + Matrix4 matrix; + SkPaint paint; + description.setupMatrixAndPaint(&matrix, &paint); + VertexBuffer* buffer = new VertexBuffer(); + PathTessellator::tessellatePath(path, &paint, matrix, *buffer); + return buffer; +} + /////////////////////////////////////////////////////////////////////////////// -// Rounded rects +// RoundRect /////////////////////////////////////////////////////////////////////////////// -static VertexBuffer* tessellateRoundRect(const TessellationCache::Description& description, - const SkPaint& paint) { - SkRect rect = SkRect::MakeWH(description.shape.roundRect.mWidth, - description.shape.roundRect.mHeight); - float rx = description.shape.roundRect.mRx; - float ry = description.shape.roundRect.mRy; - if (paint.getStyle() == SkPaint::kStrokeAndFill_Style) { - float outset = paint.getStrokeWidth() / 2; +static VertexBuffer* tessellateRoundRect(const TessellationCache::Description& description) { + SkRect rect = SkRect::MakeWH(description.shape.roundRect.width, + description.shape.roundRect.height); + float rx = description.shape.roundRect.rx; + float ry = description.shape.roundRect.ry; + if (description.style == SkPaint::kStrokeAndFill_Style) { + float outset = description.strokeWidth / 2; rect.outset(outset, outset); rx += outset; ry += outset; } SkPath path; path.addRoundRect(rect, rx, ry); - return tessellatePath(path, &paint, - description.shape.roundRect.mScaleX, description.shape.roundRect.mScaleY); + return tessellatePath(description, path); } -TessellationCache::Buffer* TessellationCache::getRoundRectBuffer(const Matrix4& transform, - float width, float height, float rx, float ry, const SkPaint* paint) { - Description entry(Description::kRoundRect, paint); - entry.shape.roundRect.mWidth = width; - entry.shape.roundRect.mHeight = height; - entry.shape.roundRect.mRx = rx; - entry.shape.roundRect.mRy = ry; - PathTessellator::extractTessellationScales(transform, - &entry.shape.roundRect.mScaleX, &entry.shape.roundRect.mScaleY); - - return getOrCreateBuffer(entry, &tessellateRoundRect, paint); +TessellationCache::Buffer* TessellationCache::getRoundRectBuffer( + const Matrix4& transform, const SkPaint& paint, + float width, float height, float rx, float ry) { + Description entry(Description::kRoundRect, transform, paint); + entry.shape.roundRect.width = width; + entry.shape.roundRect.height = height; + entry.shape.roundRect.rx = rx; + entry.shape.roundRect.ry = ry; + return getOrCreateBuffer(entry, &tessellateRoundRect); } -const VertexBuffer* TessellationCache::getRoundRect(const Matrix4& transform, - float width, float height, float rx, float ry, const SkPaint* paint) { - return getRoundRectBuffer(transform, width, height, rx, ry, paint)->getVertexBuffer(); +const VertexBuffer* TessellationCache::getRoundRect(const Matrix4& transform, const SkPaint& paint, + float width, float height, float rx, float ry) { + return getRoundRectBuffer(transform, paint, width, height, rx, ry)->getVertexBuffer(); } }; // namespace uirenderer diff --git a/libs/hwui/TessellationCache.h b/libs/hwui/TessellationCache.h index d4ff9434cee8..688a69959a57 100644 --- a/libs/hwui/TessellationCache.h +++ b/libs/hwui/TessellationCache.h @@ -50,30 +50,28 @@ public: enum Type { kNone, kRoundRect, - kAmbientShadow, - kSpotShadow }; Type type; + float scaleX; + float scaleY; bool aa; SkPaint::Cap cap; SkPaint::Style style; float strokeWidth; union Shape { struct RoundRect { - float mScaleX; - float mScaleY; - float mWidth; - float mHeight; - float mRx; - float mRy; + float width; + float height; + float rx; + float ry; } roundRect; } shape; Description(); - Description(Type type); - Description(Type type, const SkPaint* paint); + Description(Type type, const Matrix4& transform, const SkPaint& paint); hash_t hash() const; + void setupMatrixAndPaint(Matrix4* matrix, SkPaint* paint) const; }; struct ShadowDescription { @@ -123,12 +121,12 @@ public: // TODO: precache/get for Oval, Lines, Points, etc. - void precacheRoundRect(const Matrix4& transform, - float width, float height, float rx, float ry, const SkPaint* paint) { - getRoundRectBuffer(transform, width, height, rx, ry, paint); + void precacheRoundRect(const Matrix4& transform, const SkPaint& paint, + float width, float height, float rx, float ry) { + getRoundRectBuffer(transform, paint, width, height, rx, ry); } - const VertexBuffer* getRoundRect(const Matrix4& transform, - float width, float height, float rx, float ry, const SkPaint* paint); + const VertexBuffer* getRoundRect(const Matrix4& transform, const SkPaint& paint, + float width, float height, float rx, float ry); void precacheShadows(const Matrix4* drawTransform, const Rect& localClip, bool opaque, const SkPath* casterPerimeter, @@ -146,14 +144,14 @@ private: class TessellationTask; class TessellationProcessor; + typedef VertexBuffer* (*Tessellator)(const Description&); - typedef VertexBuffer* (*Tessellator)(const Description&, const SkPaint&); + Buffer* getRectBuffer(const Matrix4& transform, const SkPaint& paint, + float width, float height); + Buffer* getRoundRectBuffer(const Matrix4& transform, const SkPaint& paint, + float width, float height, float rx, float ry); - Buffer* getRoundRectBuffer(const Matrix4& transform, - float width, float height, float rx, float ry, const SkPaint* paint); - - Buffer* getOrCreateBuffer(const Description& entry, - Tessellator tessellator, const SkPaint* paint); + Buffer* getOrCreateBuffer(const Description& entry, Tessellator tessellator); uint32_t mSize; uint32_t mMaxSize; diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h index 0fc0cef76fef..f67e43406815 100644 --- a/libs/hwui/TreeInfo.h +++ b/libs/hwui/TreeInfo.h @@ -16,6 +16,8 @@ #ifndef TREEINFO_H #define TREEINFO_H +#include <string> + #include <utils/Timers.h> #include "DamageAccumulator.h" @@ -35,6 +37,13 @@ protected: ~AnimationHook() {} }; +class ErrorHandler { +public: + virtual void onError(const std::string& message) = 0; +protected: + ~ErrorHandler() {} +}; + // This would be a struct, but we want to PREVENT_COPY_AND_ASSIGN class TreeInfo { PREVENT_COPY_AND_ASSIGN(TreeInfo); @@ -65,6 +74,7 @@ public: , prepareTextures(mode == MODE_FULL) , damageAccumulator(NullDamageAccumulator::instance()) , renderer(0) + , errorHandler(0) {} const TraversalMode mode; @@ -78,6 +88,7 @@ public: // The renderer that will be drawing the next frame. Use this to push any // layer updates or similar. May be NULL. OpenGLRenderer* renderer; + ErrorHandler* errorHandler; struct Out { Out() diff --git a/libs/hwui/thread/Task.h b/libs/hwui/thread/Task.h index 9a211a238bbc..30b6ff2c3d83 100644 --- a/libs/hwui/thread/Task.h +++ b/libs/hwui/thread/Task.h @@ -17,8 +17,6 @@ #ifndef ANDROID_HWUI_TASK_H #define ANDROID_HWUI_TASK_H -#define ATRACE_TAG ATRACE_TAG_VIEW - #include <utils/RefBase.h> #include <utils/Trace.h> @@ -40,7 +38,7 @@ public: virtual ~Task() { } T getResult() const { - ATRACE_NAME("waitForTask"); + ScopedTrace tracer(ATRACE_TAG_VIEW, "waitForTask"); return mFuture->get(); } diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java index f70110cd5910..bdd119582add 100644 --- a/location/java/android/location/Location.java +++ b/location/java/android/location/Location.java @@ -583,7 +583,8 @@ public class Location implements Parcelable { } /** - * Get the altitude if available, in meters above sea level. + * Get the altitude if available, in meters above the WGS 84 reference + * ellipsoid. * * <p>If this location does not have an altitude then 0.0 is returned. */ @@ -592,7 +593,7 @@ public class Location implements Parcelable { } /** - * Set the altitude, in meters above sea level. + * Set the altitude, in meters above the WGS 84 reference ellipsoid. * * <p>Following this call {@link #hasAltitude} will return true. */ diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java index 4b4be1bbcb65..e05aef06e238 100644 --- a/media/java/android/media/AudioFormat.java +++ b/media/java/android/media/AudioFormat.java @@ -152,6 +152,8 @@ public class AudioFormat { switch (audioFormat) { case ENCODING_PCM_8BIT: return 1; + case ENCODING_PCM_FLOAT: + return 4; case ENCODING_PCM_16BIT: case ENCODING_DEFAULT: return 2; diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index e25714a53e28..66175d04680d 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -1826,11 +1826,7 @@ public class MediaPlayer implements SubtitleController.Listener } SubtitleTrack track = mInbandSubtitleTracks[index]; if (track != null) { - long runID = data.getStartTimeUs() + 1; - track.onData(data.getData(), true /* eos */, runID); - track.setRunDiscardTimeMs( - runID, - (data.getStartTimeUs() + data.getDurationUs()) / 1000); + track.onData(data); } } }; diff --git a/media/java/android/media/SubtitleTrack.java b/media/java/android/media/SubtitleTrack.java index b0e182dfac74..9fedf63f2190 100644 --- a/media/java/android/media/SubtitleTrack.java +++ b/media/java/android/media/SubtitleTrack.java @@ -75,6 +75,14 @@ public abstract class SubtitleTrack implements MediaTimeProvider.OnMediaTimeList private long mNextScheduledTimeMs = -1; + protected void onData(SubtitleData data) { + long runID = data.getStartTimeUs() + 1; + onData(data.getData(), true /* eos */, runID); + setRunDiscardTimeMs( + runID, + (data.getStartTimeUs() + data.getDurationUs()) / 1000); + } + /** * Called when there is input data for the subtitle track. The * complete subtitle for a track can include multiple whole units diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java index 78417583a4c3..7e9d27970702 100644 --- a/media/java/android/media/tv/TvContract.java +++ b/media/java/android/media/tv/TvContract.java @@ -22,7 +22,9 @@ import android.content.ContentUris; import android.net.Uri; import android.provider.BaseColumns; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * <p> @@ -380,6 +382,81 @@ public final class TvContract { /** The service type for radio channels that have audio only. */ public static final int SERVICE_TYPE_AUDIO = 0x2; + /** The video format for 240p. */ + public static final String VIDEO_FORMAT_240P = "VIDEO_FORMAT_240P"; + + /** The video format for 360p. */ + public static final String VIDEO_FORMAT_360P = "VIDEO_FORMAT_360P"; + + /** The video format for 480i. */ + public static final String VIDEO_FORMAT_480I = "VIDEO_FORMAT_480I"; + + /** The video format for 480p. */ + public static final String VIDEO_FORMAT_480P = "VIDEO_FORMAT_480P"; + + /** The video format for 576i. */ + public static final String VIDEO_FORMAT_576I = "VIDEO_FORMAT_576I"; + + /** The video format for 576p. */ + public static final String VIDEO_FORMAT_576P = "VIDEO_FORMAT_576P"; + + /** The video format for 720p. */ + public static final String VIDEO_FORMAT_720P = "VIDEO_FORMAT_720P"; + + /** The video format for 1080i. */ + public static final String VIDEO_FORMAT_1080I = "VIDEO_FORMAT_1080I"; + + /** The video format for 1080p. */ + public static final String VIDEO_FORMAT_1080P = "VIDEO_FORMAT_1080P"; + + /** The video format for 2160p. */ + public static final String VIDEO_FORMAT_2160P = "VIDEO_FORMAT_2160P"; + + /** The video format for 4320p. */ + public static final String VIDEO_FORMAT_4320P = "VIDEO_FORMAT_4320P"; + + /** The video resolution for standard-definition. */ + public static final String VIDEO_RESOLUTION_SD = "VIDEO_RESOLUTION_SD"; + + /** The video resolution for enhanced-definition. */ + public static final String VIDEO_RESOLUTION_ED = "VIDEO_RESOLUTION_ED"; + + /** The video resolution for high-definition. */ + public static final String VIDEO_RESOLUTION_HD = "VIDEO_RESOLUTION_HD"; + + /** The video resolution for full high-definition. */ + public static final String VIDEO_RESOLUTION_FHD = "VIDEO_RESOLUTION_FHD"; + + /** The video resolution for ultra high-definition. */ + public static final String VIDEO_RESOLUTION_UHD = "VIDEO_RESOLUTION_UHD"; + + private static final Map<String, String> VIDEO_FORMAT_TO_RESOLUTION_MAP = + new HashMap<String, String>(); + + static { + VIDEO_FORMAT_TO_RESOLUTION_MAP.put(VIDEO_FORMAT_480I, VIDEO_RESOLUTION_SD); + VIDEO_FORMAT_TO_RESOLUTION_MAP.put(VIDEO_FORMAT_480P, VIDEO_RESOLUTION_ED); + VIDEO_FORMAT_TO_RESOLUTION_MAP.put(VIDEO_FORMAT_576I, VIDEO_RESOLUTION_SD); + VIDEO_FORMAT_TO_RESOLUTION_MAP.put(VIDEO_FORMAT_576P, VIDEO_RESOLUTION_ED); + VIDEO_FORMAT_TO_RESOLUTION_MAP.put(VIDEO_FORMAT_720P, VIDEO_RESOLUTION_HD); + VIDEO_FORMAT_TO_RESOLUTION_MAP.put(VIDEO_FORMAT_1080I, VIDEO_RESOLUTION_HD); + VIDEO_FORMAT_TO_RESOLUTION_MAP.put(VIDEO_FORMAT_1080P, VIDEO_RESOLUTION_FHD); + VIDEO_FORMAT_TO_RESOLUTION_MAP.put(VIDEO_FORMAT_2160P, VIDEO_RESOLUTION_UHD); + VIDEO_FORMAT_TO_RESOLUTION_MAP.put(VIDEO_FORMAT_4320P, VIDEO_RESOLUTION_UHD); + } + + /** + * Returns the video resolution (definition) for a given video format. + * + * @param videoFormat The video format defined in {@link Channels}. + * @return the corresponding video resolution string. {@code null} if the resolution string + * is not defined for the given video format. + * @see #COLUMN_VIDEO_FORMAT + */ + public static final String getVideoResolution(String videoFormat) { + return VIDEO_FORMAT_TO_RESOLUTION_MAP.get(videoFormat); + } + /** * The name of the {@link TvInputService} subclass that provides this TV channel. This * should be a fully qualified class name (such as, "com.example.project.TvInputService"). @@ -513,6 +590,24 @@ public final class TvContract { public static final String COLUMN_DESCRIPTION = "description"; /** + * The typical video format for programs from this TV channel. + * <p> + * This is primarily used to filter out channels based on video format by applications. The + * value should match one of the followings: {@link #VIDEO_FORMAT_240P}, + * {@link #VIDEO_FORMAT_360P}, {@link #VIDEO_FORMAT_480I}, {@link #VIDEO_FORMAT_480P}, + * {@link #VIDEO_FORMAT_576I}, {@link #VIDEO_FORMAT_576P}, {@link #VIDEO_FORMAT_720P}, + * {@link #VIDEO_FORMAT_1080I}, {@link #VIDEO_FORMAT_1080P}, {@link #VIDEO_FORMAT_2160P}, + * {@link #VIDEO_FORMAT_4320P}. Note that the actual video resolution of each program from a + * given channel can vary thus one should use {@link Programs#COLUMN_VIDEO_WIDTH} and + * {@link Programs#COLUMN_VIDEO_HEIGHT} to get more accurate video resolution. + * </p><p> + * Type: TEXT + * </p><p> + * @see #getVideoResolution + */ + public static final String COLUMN_VIDEO_FORMAT = "video_format"; + + /** * The flag indicating whether this TV channel is browsable or not. * <p> * A value of 1 indicates the channel is included in the channel list that applications use @@ -719,6 +814,32 @@ public final class TvContract { public static final String COLUMN_LONG_DESCRIPTION = "long_description"; /** + * The width of the video for this TV program, in the unit of pixels. + * <p> + * Together with {@link #COLUMN_VIDEO_HEIGHT} this is used to determine the video resolution + * of the current TV program. Can be empty if it is not known initially or the program does + * not convey any video such as the programs from type {@link Channels#SERVICE_TYPE_AUDIO} + * channels. + * </p><p> + * Type: INTEGER + * </p> + */ + public static final String COLUMN_VIDEO_WIDTH = "video_width"; + + /** + * The height of the video for this TV program, in the unit of pixels. + * <p> + * Together with {@link #COLUMN_VIDEO_WIDTH} this is used to determine the video resolution + * of the current TV program. Can be empty if it is not known initially or the program does + * not convey any video such as the programs from type {@link Channels#SERVICE_TYPE_AUDIO} + * channels. + * </p><p> + * Type: INTEGER + * </p> + */ + public static final String COLUMN_VIDEO_HEIGHT = "video_height"; + + /** * The comma-separated audio languages of this TV program. * <p> * This is used to describe available audio languages included in the program. Use @@ -848,7 +969,7 @@ public final class TvContract { * * @hide */ - public static final class WatchedPrograms implements BaseColumns { + public static final class WatchedPrograms implements BaseTvColumns { /** The content:// style URI for this table. */ public static final Uri CONTENT_URI = diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index edfdd60defc6..daa700953466 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -533,12 +533,11 @@ public final class TvInputManager { /** * Releases this session. - * - * @throws IllegalStateException if the session has been already released. */ public void release() { if (mToken == null) { - throw new IllegalStateException("the session has been already released"); + Log.w(TAG, "The session has been already released"); + return; } try { mService.releaseSession(mToken, mUserId); @@ -553,12 +552,12 @@ public final class TvInputManager { * Sets the {@link android.view.Surface} for this session. * * @param surface A {@link android.view.Surface} used to render video. - * @throws IllegalStateException if the session has been already released. * @hide */ public void setSurface(Surface surface) { if (mToken == null) { - throw new IllegalStateException("the session has been already released"); + Log.w(TAG, "The session has been already released"); + return; } // surface can be null. try { @@ -573,11 +572,11 @@ public final class TvInputManager { * * @param volume A volume value between 0.0f to 1.0f. * @throws IllegalArgumentException if the volume value is out of range. - * @throws IllegalStateException if the session has been already released. */ public void setStreamVolume(float volume) { if (mToken == null) { - throw new IllegalStateException("the session has been already released"); + Log.w(TAG, "The session has been already released"); + return; } try { if (volume < 0.0f || volume > 1.0f) { @@ -594,14 +593,14 @@ public final class TvInputManager { * * @param channelUri The URI of a channel. * @throws IllegalArgumentException if the argument is {@code null}. - * @throws IllegalStateException if the session has been already released. */ public void tune(Uri channelUri) { if (channelUri == null) { throw new IllegalArgumentException("channelUri cannot be null"); } if (mToken == null) { - throw new IllegalStateException("the session has been already released"); + Log.w(TAG, "The session has been already released"); + return; } try { mService.tune(mToken, channelUri, mUserId); @@ -620,8 +619,7 @@ public final class TvInputManager { * @param view A view playing TV. * @param frame A position of the overlay view. * @throws IllegalArgumentException if any of the arguments is {@code null}. - * @throws IllegalStateException if {@code view} is not attached to a window or - * if the session has been already released. + * @throws IllegalStateException if {@code view} is not attached to a window. */ void createOverlayView(View view, Rect frame) { if (view == null) { @@ -634,7 +632,8 @@ public final class TvInputManager { throw new IllegalStateException("view must be attached to a window"); } if (mToken == null) { - throw new IllegalStateException("the session has been already released"); + Log.w(TAG, "The session has been already released"); + return; } try { mService.createOverlayView(mToken, view.getWindowToken(), frame, mUserId); @@ -648,14 +647,14 @@ public final class TvInputManager { * * @param frame A new position of the overlay view. * @throws IllegalArgumentException if the arguments is {@code null}. - * @throws IllegalStateException if the session has been already released. */ void relayoutOverlayView(Rect frame) { if (frame == null) { throw new IllegalArgumentException("frame cannot be null"); } if (mToken == null) { - throw new IllegalStateException("the session has been already released"); + Log.w(TAG, "The session has been already released"); + return; } try { mService.relayoutOverlayView(mToken, frame, mUserId); @@ -666,12 +665,11 @@ public final class TvInputManager { /** * Removes the current overlay view. - * - * @throws IllegalStateException if the session has been already released. */ void removeOverlayView() { if (mToken == null) { - throw new IllegalStateException("the session has been already released"); + Log.w(TAG, "The session has been already released"); + return; } try { mService.removeOverlayView(mToken, mUserId); diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java index 2ed3d731b97f..52db30a82fba 100644 --- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java +++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java @@ -27,6 +27,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInfoLite; import android.content.pm.PackageManager; import android.content.pm.PackageParser; +import android.content.pm.PackageParser.PackageParserException; import android.content.res.ObbInfo; import android.content.res.ObbScanner; import android.net.Uri; @@ -157,6 +158,7 @@ public class DefaultContainerService extends IntentService { * @return Returns PackageInfoLite object containing * the package info and recommended app location. */ + @Override public PackageInfoLite getMinimalPackageInfo(final String packagePath, int flags, long threshold, String abiOverride) { PackageInfoLite ret = new PackageInfoLite(); @@ -167,14 +169,13 @@ public class DefaultContainerService extends IntentService { return ret; } - DisplayMetrics metrics = new DisplayMetrics(); - metrics.setToDefaults(); - - PackageParser.ApkLite pkg = PackageParser.parseApkLite(packagePath, 0); - if (pkg == null) { + final File apkFile = new File(packagePath); + final PackageParser.ApkLite pkg; + try { + pkg = PackageParser.parseApkLite(apkFile, 0); + } catch (PackageParserException e) { Slog.w(TAG, "Failed to parse package"); - final File apkFile = new File(packagePath); if (!apkFile.exists()) { ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI; } else { diff --git a/packages/SystemUI/res/drawable-hdpi/recents_lower_gradient.9.png b/packages/SystemUI/res/drawable-hdpi/recents_lower_gradient.9.png Binary files differindex d4fdbf388a76..17100f773a16 100644 --- a/packages/SystemUI/res/drawable-hdpi/recents_lower_gradient.9.png +++ b/packages/SystemUI/res/drawable-hdpi/recents_lower_gradient.9.png diff --git a/packages/SystemUI/res/drawable-hdpi/recents_status_gradient.9.png b/packages/SystemUI/res/drawable-hdpi/recents_status_gradient.9.png Binary files differnew file mode 100644 index 000000000000..e969d4c26717 --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/recents_status_gradient.9.png diff --git a/packages/SystemUI/res/drawable-mdpi/recents_lower_gradient.9.png b/packages/SystemUI/res/drawable-mdpi/recents_lower_gradient.9.png Binary files differindex 9fc1a3bffd57..b53bd8f92a00 100644 --- a/packages/SystemUI/res/drawable-mdpi/recents_lower_gradient.9.png +++ b/packages/SystemUI/res/drawable-mdpi/recents_lower_gradient.9.png diff --git a/packages/SystemUI/res/drawable-mdpi/recents_status_gradient.9.png b/packages/SystemUI/res/drawable-mdpi/recents_status_gradient.9.png Binary files differnew file mode 100644 index 000000000000..657f710ac8d6 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/recents_status_gradient.9.png diff --git a/packages/SystemUI/res/drawable-xhdpi/recents_lower_gradient.9.png b/packages/SystemUI/res/drawable-xhdpi/recents_lower_gradient.9.png Binary files differindex f38de93264d3..09606f629b67 100644 --- a/packages/SystemUI/res/drawable-xhdpi/recents_lower_gradient.9.png +++ b/packages/SystemUI/res/drawable-xhdpi/recents_lower_gradient.9.png diff --git a/packages/SystemUI/res/drawable-xhdpi/recents_status_gradient.9.png b/packages/SystemUI/res/drawable-xhdpi/recents_status_gradient.9.png Binary files differnew file mode 100644 index 000000000000..a444c551d430 --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/recents_status_gradient.9.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/recents_lower_gradient.9.png b/packages/SystemUI/res/drawable-xxhdpi/recents_lower_gradient.9.png Binary files differindex 8194605e1646..427cad9f6326 100644 --- a/packages/SystemUI/res/drawable-xxhdpi/recents_lower_gradient.9.png +++ b/packages/SystemUI/res/drawable-xxhdpi/recents_lower_gradient.9.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/recents_status_gradient.9.png b/packages/SystemUI/res/drawable-xxhdpi/recents_status_gradient.9.png Binary files differnew file mode 100644 index 000000000000..29cf44bf381f --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/recents_status_gradient.9.png diff --git a/packages/SystemUI/res/drawable/recents_button_bg.xml b/packages/SystemUI/res/drawable/recents_button_bg.xml new file mode 100644 index 000000000000..a4cb0885587c --- /dev/null +++ b/packages/SystemUI/res/drawable/recents_button_bg.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="?android:attr/colorControlHighlight" />
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/recents_status_bar_scrim.xml b/packages/SystemUI/res/layout/recents_status_bar_scrim.xml new file mode 100644 index 000000000000..24928d0c7311 --- /dev/null +++ b/packages/SystemUI/res/layout/recents_status_bar_scrim.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<ImageView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal|top" + android:scaleType="fitXY" + android:src="@drawable/recents_status_gradient" />
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/recents_task_view.xml b/packages/SystemUI/res/layout/recents_task_view.xml index 23f2796ccbff..1bab67af7094 100644 --- a/packages/SystemUI/res/layout/recents_task_view.xml +++ b/packages/SystemUI/res/layout/recents_task_view.xml @@ -32,8 +32,10 @@ android:id="@+id/application_icon" android:layout_width="@dimen/recents_task_view_application_icon_size" android:layout_height="@dimen/recents_task_view_application_icon_size" - android:layout_marginStart="16dp" - android:layout_gravity="center_vertical|start" /> + android:layout_marginStart="8dp" + android:layout_gravity="center_vertical|start" + android:padding="8dp" + android:background="@drawable/recents_button_bg" /> <TextView android:id="@+id/activity_description" android:layout_width="match_parent" @@ -56,6 +58,7 @@ android:layout_marginEnd="4dp" android:layout_gravity="center_vertical|end" android:padding="18dp" + android:background="@drawable/recents_button_bg" android:visibility="invisible" android:src="@drawable/recents_dismiss_light" /> </com.android.systemui.recents.views.TaskBarView> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 8c1a9c710dd4..3bd86891e5e1 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -46,9 +46,7 @@ <color name="keyguard_overflow_content_color">#ff686868</color> <!-- The default recents task bar background color. --> - <color name="recents_task_bar_default_background_color">#e6444444</color> - <!-- The default recents task bar text color. --> - <color name="recents_task_bar_default_text_color">#ffeeeeee</color> + <color name="recents_task_bar_default_background_color">#ffe6e6e6</color> <!-- The recents task bar light text color to be drawn on top of dark backgrounds. --> <color name="recents_task_bar_light_text_color">#ffeeeeee</color> <!-- The recents task bar dark text color to be drawn on top of light backgrounds. --> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index c8851dc7d83c..c64a182c788f 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -111,9 +111,9 @@ <!-- The duration in seconds to wait before the dismiss buttons are shown. --> <integer name="recents_task_bar_dismiss_delay_seconds">3</integer> <!-- The min animation duration for animating views that are currently visible. --> - <integer name="recents_filter_animate_current_views_min_duration">175</integer> + <integer name="recents_filter_animate_current_views_duration">250</integer> <!-- The min animation duration for animating views that are newly visible. --> - <integer name="recents_filter_animate_new_views_min_duration">125</integer> + <integer name="recents_filter_animate_new_views_duration">250</integer> <!-- The min animation duration for animating the task bar in. --> <integer name="recents_animate_task_bar_enter_duration">275</integer> <!-- The animation delay for animating the first task in. This should roughly be the animation diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 8b8c126f07d7..bbcc9c13c9ea 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -212,7 +212,7 @@ <dimen name="glowpadview_inner_radius">15dip</dimen> <!-- The size of the application icon in the recents task view. --> - <dimen name="recents_task_view_application_icon_size">32dp</dimen> + <dimen name="recents_task_view_application_icon_size">48dp</dimen> <!-- The size of the activity icon in the recents task view. --> <dimen name="recents_task_view_activity_icon_size">60dp</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index ffd76a7d5367..b9e2e1baf892 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -239,6 +239,12 @@ public class KeyguardViewMediator extends SystemUI { private String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE; /** + * Whether a hide is pending an we are just waiting for #startKeyguardExitAnimation to be + * called. + * */ + private boolean mHiding; + + /** * we send this intent when the keyguard is dismissed. */ private static final Intent USER_PRESENT_INTENT = new Intent(Intent.ACTION_USER_PRESENT) @@ -1169,6 +1175,7 @@ public class KeyguardViewMediator extends SystemUI { } mStatusBarKeyguardViewManager.show(options); + mHiding = false; mShowing = true; mKeyguardDonePending = false; updateActivityLockScreenState(); @@ -1191,7 +1198,7 @@ public class KeyguardViewMediator extends SystemUI { synchronized (KeyguardViewMediator.this) { if (DEBUG) Log.d(TAG, "handleHide"); try { - + mHiding = true; if (mShowing) { // Don't actually hide the Keyguard at the moment, wait for window manager until @@ -1212,6 +1219,11 @@ public class KeyguardViewMediator extends SystemUI { private void handleStartKeyguardExitAnimation(long startTime, long fadeoutDuration) { synchronized (KeyguardViewMediator.this) { + if (!mHiding) { + return; + } + mHiding = false; + // only play "unlock" noises if not on a call (since the incall UI // disables the keyguard) if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState)) { diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index c3ba349cf71f..41b1f75be94e 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java @@ -152,6 +152,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { .setContentText(mContext.getString(R.string.invalid_charger_text)) .setPriority(Notification.PRIORITY_MAX) .setCategory(Notification.CATEGORY_SYSTEM) + .setVisibility(Notification.VISIBILITY_PUBLIC) .setFullScreenIntent(pendingBroadcast(ACTION_SHOW_FALLBACK_CHARGER), true); final Notification n = nb.build(); if (n.headsUpContentView != null) { @@ -171,6 +172,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { .setOngoing(true) .setPriority(Notification.PRIORITY_MAX) .setCategory(Notification.CATEGORY_SYSTEM) + .setVisibility(Notification.VISIBILITY_PUBLIC) .setFullScreenIntent(pendingBroadcast(ACTION_SHOW_FALLBACK_WARNING), true); if (hasBatterySettings()) { nb.setContentIntent(pendingBroadcast(ACTION_SHOW_BATTERY_SETTINGS)); @@ -197,7 +199,8 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { .setContentText(mContext.getString(R.string.battery_saver_notification_text)) .setOngoing(true) .setShowWhen(false) - .setCategory(Notification.CATEGORY_SYSTEM); + .setCategory(Notification.CATEGORY_SYSTEM) + .setVisibility(Notification.VISIBILITY_PUBLIC); if (hasSaverSettings()) { nb.addAction(0, mContext.getString(R.string.battery_saver_notification_action_text), diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java index 1d355cd2daf4..ddea0bfe33b3 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java @@ -30,7 +30,7 @@ public class Constants { // Enables the screenshot app->Recents transition public static final boolean EnableScreenshotAppTransition = false; // Enables the filtering of tasks according to their grouping - public static final boolean EnableTaskFiltering = false; + public static final boolean EnableTaskFiltering = true; // Enables clipping of tasks against each other public static final boolean EnableTaskStackClipping = true; // Enables the use of theme colors as the task bar background @@ -48,7 +48,7 @@ public class Constants { // For debugging, this defines the number of mock recents packages to create public static final int SystemServicesProxyMockPackageCount = 3; // For debugging, this defines the number of mock recents tasks to create - public static final int SystemServicesProxyMockTaskCount = 75; + public static final int SystemServicesProxyMockTaskCount = 100; } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index dce8f57d8704..88ff726c9551 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -72,6 +72,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView FrameLayout mContainerView; RecentsView mRecentsView; View mEmptyView; + View mStatusBarScrimView; View mNavBarScrimView; FullScreenTransitionView mFullScreenshotView; @@ -180,13 +181,11 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false); mConfig.launchedWithNoRecentTasks = !root.hasTasks(); - if (mConfig.shouldAnimateNavBarScrim()) { - // Hide the scrim if we animate into Recents with window transitions - mNavBarScrimView.setVisibility(View.INVISIBLE); - } else { - // Show the scrim if we animate into Recents without window transitions - mNavBarScrimView.setVisibility(View.VISIBLE); - } + // Show the scrim if we animate into Recents without window transitions + mNavBarScrimView.setVisibility(mConfig.hasNavBarScrim() && + !mConfig.shouldAnimateNavBarScrim() ? View.VISIBLE : View.INVISIBLE); + mStatusBarScrimView.setVisibility(mConfig.hasStatusBarScrim() && + !mConfig.shouldAnimateStatusBarScrim() ? View.VISIBLE : View.INVISIBLE); // Add the default no-recents layout if (mConfig.launchedWithNoRecentTasks) { @@ -325,6 +324,10 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView // Create the empty view LayoutInflater inflater = LayoutInflater.from(this); mEmptyView = inflater.inflate(R.layout.recents_empty, mContainerView, false); + mStatusBarScrimView = inflater.inflate(R.layout.recents_status_bar_scrim, mContainerView, false); + mStatusBarScrimView.setLayoutParams(new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.TOP)); mNavBarScrimView = inflater.inflate(R.layout.recents_nav_bar_scrim, mContainerView, false); mNavBarScrimView.setLayoutParams(new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, @@ -336,6 +339,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView } mContainerView = new FrameLayout(this); + mContainerView.addView(mStatusBarScrimView); mContainerView.addView(mRecentsView); mContainerView.addView(mEmptyView); if (Constants.DebugFlags.App.EnableScreenshotAppTransition) { @@ -563,8 +567,18 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView } public void onEnterAnimationTriggered() { - // Fade in the scrim - if (mConfig.shouldAnimateNavBarScrim() && mConfig.hasNavBarScrim()) { + // Fade in the scrims + if (mConfig.hasStatusBarScrim() && mConfig.shouldAnimateStatusBarScrim()) { + mStatusBarScrimView.setVisibility(View.VISIBLE); + mStatusBarScrimView.setTranslationY(-mStatusBarScrimView.getMeasuredHeight()); + mStatusBarScrimView.animate() + .translationY(0) + .setStartDelay(mConfig.taskBarEnterAnimDelay) + .setDuration(mConfig.navBarScrimEnterDuration) + .setInterpolator(mConfig.quintOutInterpolator) + .start(); + } + if (mConfig.hasNavBarScrim() && mConfig.shouldAnimateNavBarScrim()) { mNavBarScrimView.setVisibility(View.VISIBLE); mNavBarScrimView.setTranslationY(mNavBarScrimView.getMeasuredHeight()); mNavBarScrimView.animate() @@ -579,7 +593,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView @Override public void onExitAnimationTriggered() { // Fade out the scrim - if (mConfig.shouldAnimateNavBarScrim() && mConfig.hasNavBarScrim()) { + if (mConfig.hasNavBarScrim() && mConfig.shouldAnimateNavBarScrim()) { mNavBarScrimView.animate() .translationY(mNavBarScrimView.getMeasuredHeight()) .setStartDelay(0) @@ -605,14 +619,9 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView } @Override - public void onTaskLaunching(boolean isTaskInStackBounds) { + public void onTaskLaunching() { mTaskLaunched = true; - // Fade out the scrim - if (!isTaskInStackBounds && mConfig.hasNavBarScrim()) { - onExitAnimationTriggered(); - } - // Mark recents as no longer visible AlternateRecentsComponent.notifyVisibilityChanged(false); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java index c1a8ee6cc0dc..10978ca59bc5 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java @@ -47,8 +47,8 @@ public class RecentsConfiguration { public Interpolator quintOutInterpolator; /** Filtering */ - public int filteringCurrentViewsMinAnimDuration; - public int filteringNewViewsMinAnimDuration; + public int filteringCurrentViewsAnimDuration; + public int filteringNewViewsAnimDuration; /** Insets */ public Rect systemInsets = new Rect(); @@ -81,7 +81,6 @@ public class RecentsConfiguration { /** Task bar colors */ public int taskBarViewDefaultBackgroundColor; - public int taskBarViewDefaultTextColor; public int taskBarViewLightTextColor; public int taskBarViewDarkTextColor; public int taskBarViewHighlightColor; @@ -106,12 +105,30 @@ public class RecentsConfiguration { public boolean developerOptionsEnabled; /** Private constructor */ - private RecentsConfiguration() {} + private RecentsConfiguration(Context context) { + // Properties that don't have to be reloaded with each configuration change can be loaded + // here. + + // Interpolators + fastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, + com.android.internal.R.interpolator.fast_out_slow_in); + fastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context, + com.android.internal.R.interpolator.fast_out_linear_in); + linearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, + com.android.internal.R.interpolator.linear_out_slow_in); + quintOutInterpolator = AnimationUtils.loadInterpolator(context, + com.android.internal.R.interpolator.decelerate_quint); + + // Check if the developer options are enabled + ContentResolver cr = context.getContentResolver(); + developerOptionsEnabled = Settings.Global.getInt(cr, + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0; + } /** Updates the configuration to the current context */ public static RecentsConfiguration reinitialize(Context context) { if (sInstance == null) { - sInstance = new RecentsConfiguration(); + sInstance = new RecentsConfiguration(context); } sInstance.update(context); return sInstance; @@ -132,21 +149,11 @@ public class RecentsConfiguration { animationPxMovementPerSecond = res.getDimensionPixelSize(R.dimen.recents_animation_movement_in_dps_per_second); - // Interpolators - fastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, - com.android.internal.R.interpolator.fast_out_slow_in); - fastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context, - com.android.internal.R.interpolator.fast_out_linear_in); - linearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, - com.android.internal.R.interpolator.linear_out_slow_in); - quintOutInterpolator = AnimationUtils.loadInterpolator(context, - com.android.internal.R.interpolator.decelerate_quint); - // Filtering - filteringCurrentViewsMinAnimDuration = - res.getInteger(R.integer.recents_filter_animate_current_views_min_duration); - filteringNewViewsMinAnimDuration = - res.getInteger(R.integer.recents_filter_animate_new_views_min_duration); + filteringCurrentViewsAnimDuration = + res.getInteger(R.integer.recents_filter_animate_current_views_duration); + filteringNewViewsAnimDuration = + res.getInteger(R.integer.recents_filter_animate_new_views_duration); // Insets displayRect.set(0, 0, dm.widthPixels, dm.heightPixels); @@ -194,8 +201,6 @@ public class RecentsConfiguration { // Task bar colors taskBarViewDefaultBackgroundColor = res.getColor(R.color.recents_task_bar_default_background_color); - taskBarViewDefaultTextColor = - res.getColor(R.color.recents_task_bar_default_text_color); taskBarViewLightTextColor = res.getColor(R.color.recents_task_bar_light_text_color); taskBarViewDarkTextColor = @@ -217,11 +222,6 @@ public class RecentsConfiguration { navBarScrimEnterDuration = res.getInteger(R.integer.recents_nav_bar_scrim_enter_duration); - // Check if the developer options are enabled - ContentResolver cr = context.getContentResolver(); - developerOptionsEnabled = Settings.Global.getInt(cr, - Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0; - if (Console.Enabled) { Console.log(Constants.Log.UI.MeasureAndLayout, "[RecentsConfiguration|orientation]", isLandscape ? "Landscape" : "Portrait", @@ -257,6 +257,16 @@ public class RecentsConfiguration { return searchBarAppWidgetId >= 0; } + /** Returns whether the status bar scrim should be animated when shown for the first time. */ + public boolean shouldAnimateStatusBarScrim() { + return launchedFromHome; + } + + /** Returns whether the status bar scrim should be visible. */ + public boolean hasStatusBarScrim() { + return !launchedWithNoRecentTasks; + } + /** Returns whether the nav bar scrim should be animated when shown for the first time. */ public boolean shouldAnimateNavBarScrim() { return true; diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java index a02e1a7b67a2..7762111c7ac2 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java @@ -118,6 +118,7 @@ class TaskResourceLoader implements Runnable { TaskResourceLoadQueue mLoadQueue; DrawableLruCache mApplicationIconCache; BitmapLruCache mThumbnailCache; + Bitmap mDefaultThumbnail; boolean mCancelled; boolean mWaitingOnLoadQueue; @@ -125,10 +126,12 @@ class TaskResourceLoader implements Runnable { /** Constructor, creates a new loading thread that loads task resources in the background */ public TaskResourceLoader(TaskResourceLoadQueue loadQueue, DrawableLruCache applicationIconCache, - BitmapLruCache thumbnailCache) { + BitmapLruCache thumbnailCache, + Bitmap defaultThumbnail) { mLoadQueue = loadQueue; mApplicationIconCache = applicationIconCache; mThumbnailCache = thumbnailCache; + mDefaultThumbnail = defaultThumbnail; mMainThreadHandler = new Handler(); mLoadThread = new HandlerThread("Recents-TaskResourceLoader"); mLoadThread.setPriority(Thread.NORM_PRIORITY - 1); @@ -238,6 +241,7 @@ class TaskResourceLoader implements Runnable { loadThumbnail = thumbnail; mThumbnailCache.put(t.key, thumbnail); } else { + loadThumbnail = mDefaultThumbnail; Console.logError(mContext, "Failed to load task top thumbnail for: " + t.key.baseIntent.getComponent().getPackageName()); @@ -330,6 +334,7 @@ public class RecentsTaskLoader { BitmapDrawable mDefaultApplicationIcon; Bitmap mDefaultThumbnail; + Bitmap mLoadingThumbnail; /** Private Constructor */ private RecentsTaskLoader(Context context) { @@ -356,18 +361,22 @@ public class RecentsTaskLoader { mLoadQueue = new TaskResourceLoadQueue(); mApplicationIconCache = new DrawableLruCache(iconCacheSize); mThumbnailCache = new BitmapLruCache(thumbnailCacheSize); - mLoader = new TaskResourceLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache); + mLoader = new TaskResourceLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache, + mDefaultThumbnail); // Create the default assets Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); icon.eraseColor(0x00000000); mDefaultThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); - mDefaultThumbnail.eraseColor(0x00000000); + mDefaultThumbnail.eraseColor(0xFFffffff); + mLoadingThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + mLoadingThumbnail.eraseColor(0x00000000); mDefaultApplicationIcon = new BitmapDrawable(context.getResources(), icon); if (Console.Enabled) { Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|defaultBitmaps]", - "icon: " + mDefaultApplicationIcon + " thumbnail: " + mDefaultThumbnail, Console.AnsiRed); + "icon: " + mDefaultApplicationIcon + + " default thumbnail: " + mDefaultThumbnail, Console.AnsiRed); } } @@ -394,7 +403,7 @@ public class RecentsTaskLoader { SystemServicesProxy ssp = mSystemServicesProxy; List<ActivityManager.RecentTaskInfo> tasks = - ssp.getRecentTasks(100, UserHandle.CURRENT.getIdentifier()); + ssp.getRecentTasks(50, UserHandle.CURRENT.getIdentifier()); Collections.reverse(tasks); if (Console.Enabled) { Console.log(Constants.Log.App.TimeSystemCalls, @@ -544,7 +553,7 @@ public class RecentsTaskLoader { requiresLoad = true; } if (thumbnail == null) { - thumbnail = mDefaultThumbnail; + thumbnail = mLoadingThumbnail; requiresLoad = true; } if (requiresLoad) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FullScreenTransitionView.java b/packages/SystemUI/src/com/android/systemui/recents/views/FullScreenTransitionView.java index c861d2cdc802..cadfc5604698 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/FullScreenTransitionView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/FullScreenTransitionView.java @@ -23,6 +23,7 @@ import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Paint; import android.graphics.Rect; import android.view.View; import android.view.ViewGroup; @@ -49,8 +50,8 @@ public class FullScreenTransitionView extends FrameLayout { FullScreenTransitionViewCallbacks mCb; ImageView mScreenshotView; - Rect mClipRect = new Rect(); + Paint mLayerPaint = new Paint(); boolean mIsAnimating; AnimatorSet mEnterAnimation; @@ -159,7 +160,7 @@ public class FullScreenTransitionView extends FrameLayout { int clipBottom = mConfig.systemInsets.top + (int) (ctx.taskRect.height() / scale); // Enable the HW Layers on the screenshot view - mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint); // Compose the animation mEnterAnimation = new AnimatorSet(); @@ -173,7 +174,7 @@ public class FullScreenTransitionView extends FrameLayout { // Mark that we are no longer animating mIsAnimating = false; // Disable the HW Layers on this view - setLayerType(View.LAYER_TYPE_NONE, null); + setLayerType(View.LAYER_TYPE_NONE, mLayerPaint); if (Console.Enabled) { Console.logTraceTime(Constants.Log.App.TimeRecentsScreenshotTransition, @@ -217,7 +218,7 @@ public class FullScreenTransitionView extends FrameLayout { // Mark that we are no longer animating mIsAnimating = false; // Disable the HW Layers on the screenshot view - mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null); + mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint); } }); mEnterAnimation.setDuration(475); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index 3e6879d424b3..724875867faf 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -54,7 +54,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV /** The RecentsView callbacks */ public interface RecentsViewCallbacks { - public void onTaskLaunching(boolean isTaskInStackBounds); + public void onTaskLaunching(); public void onExitAnimationTriggered(); } @@ -389,13 +389,46 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV final TaskStack stack, final Task task) { // Notify any callbacks of the launching of a new task if (mCb != null) { - boolean isTaskInStackBounds = false; - if (stackView != null && tv != null) { - isTaskInStackBounds = stackView.isTaskInStackBounds(tv); - } - mCb.onTaskLaunching(isTaskInStackBounds); + mCb.onTaskLaunching(); + } + + // Upfront the processing of the thumbnail + TaskViewTransform transform; + View sourceView = tv; + int offsetX = 0; + int offsetY = 0; + int stackScroll = stackView.getStackScroll(); + if (tv == null) { + // If there is no actual task view, then use the stack view as the source view + // and then offset to the expected transform rect, but bound this to just + // outside the display rect (to ensure we don't animate from too far away) + sourceView = stackView; + transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll); + offsetX = transform.rect.left; + offsetY = Math.min(transform.rect.top, mConfig.displayRect.height()); + } else { + transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll); + } + + // Compute the thumbnail to scale up from + ActivityOptions opts = null; + int thumbnailWidth = transform.rect.width(); + int thumbnailHeight = transform.rect.height(); + if (task.thumbnail != null && thumbnailWidth > 0 && thumbnailHeight > 0 && + task.thumbnail.getWidth() > 0 && task.thumbnail.getHeight() > 0) { + // Resize the thumbnail to the size of the view that we are animating from + Bitmap b = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight, + Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(b); + c.drawBitmap(task.thumbnail, + new Rect(0, 0, task.thumbnail.getWidth(), task.thumbnail.getHeight()), + new Rect(0, 0, thumbnailWidth, thumbnailHeight), null); + c.setBitmap(null); + opts = ActivityOptions.makeThumbnailScaleUpAnimation(sourceView, + b, offsetX, offsetY); } + final ActivityOptions launchOpts = opts; final Runnable launchRunnable = new Runnable() { @Override public void run() { @@ -404,45 +437,10 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV Constants.Log.App.TimeRecentsLaunchKey, "preStartActivity"); } - TaskViewTransform transform; - View sourceView = tv; - int offsetX = 0; - int offsetY = 0; - int stackScroll = stackView.getStackScroll(); - if (tv == null) { - // If there is no actual task view, then use the stack view as the source view - // and then offset to the expected transform rect, but bound this to just - // outside the display rect (to ensure we don't animate from too far away) - sourceView = stackView; - transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll); - offsetX = transform.rect.left; - offsetY = Math.min(transform.rect.top, mConfig.displayRect.height()); - } else { - transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll); - } - - // Compute the thumbnail to scale up from - ActivityOptions opts = null; - int thumbnailWidth = transform.rect.width(); - int thumbnailHeight = transform.rect.height(); - if (task.thumbnail != null && thumbnailWidth > 0 && thumbnailHeight > 0 && - task.thumbnail.getWidth() > 0 && task.thumbnail.getHeight() > 0) { - // Resize the thumbnail to the size of the view that we are animating from - Bitmap b = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight, - Bitmap.Config.ARGB_8888); - Canvas c = new Canvas(b); - c.drawBitmap(task.thumbnail, - new Rect(0, 0, task.thumbnail.getWidth(), task.thumbnail.getHeight()), - new Rect(0, 0, thumbnailWidth, thumbnailHeight), null); - c.setBitmap(null); - opts = ActivityOptions.makeThumbnailScaleUpAnimation(sourceView, - b, offsetX, offsetY); - } - if (task.isActive) { // Bring an active task to the foreground RecentsTaskLoader.getInstance().getSystemServicesProxy() - .moveTaskToFront(task.key.id, opts); + .moveTaskToFront(task.key.id, launchOpts); } else { // Launch the activity anew with the desired animation Intent i = new Intent(task.key.baseIntent); @@ -451,8 +449,8 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV | Intent.FLAG_ACTIVITY_NEW_TASK); try { UserHandle taskUser = new UserHandle(task.userId); - if (opts != null) { - getContext().startActivityAsUser(i, opts.toBundle(), taskUser); + if (launchOpts != null) { + getContext().startActivityAsUser(i, launchOpts.toBundle(), taskUser); } else { getContext().startActivityAsUser(i, taskUser); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java index 82d622051da6..1ef58add7206 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java @@ -23,6 +23,7 @@ import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.drawable.Drawable; +import android.graphics.drawable.RippleDrawable; import android.util.AttributeSet; import android.view.View; import android.view.ViewPropertyAnimator; @@ -50,6 +51,7 @@ class TaskBarView extends FrameLayout { Drawable mLightDismissDrawable; Drawable mDarkDismissDrawable; + Paint mLayerPaint = new Paint(); static Paint sHighlightPaint; public TaskBarView(Context context) { @@ -91,6 +93,13 @@ class TaskBarView extends FrameLayout { mApplicationIcon = (ImageView) findViewById(R.id.application_icon); mActivityDescription = (TextView) findViewById(R.id.activity_description); mDismissButton = (ImageView) findViewById(R.id.dismiss_task); + + // Hide the backgrounds if they are ripple drawables + if (!Constants.DebugFlags.App.EnableTaskFiltering) { + if (mApplicationIcon.getBackground() instanceof RippleDrawable) { + mApplicationIcon.setBackground(null); + } + } } @Override @@ -142,16 +151,14 @@ class TaskBarView extends FrameLayout { mActivityDescription.setText(t.activityLabel); // Try and apply the system ui tint int tint = t.colorPrimary; - if (Constants.DebugFlags.App.EnableTaskBarThemeColors && tint != 0) { - setBackgroundColor(tint); - mActivityDescription.setTextColor(Utilities.getIdealColorForBackgroundColor(tint, - mConfig.taskBarViewLightTextColor, mConfig.taskBarViewDarkTextColor)); - mDismissButton.setImageDrawable(Utilities.getIdealResourceForBackgroundColor(tint, - mLightDismissDrawable, mDarkDismissDrawable)); - } else { - setBackgroundColor(mConfig.taskBarViewDefaultBackgroundColor); - mActivityDescription.setTextColor(mConfig.taskBarViewDefaultTextColor); + if (!Constants.DebugFlags.App.EnableTaskBarThemeColors || tint == 0) { + tint = mConfig.taskBarViewDefaultBackgroundColor; } + setBackgroundColor(tint); + mActivityDescription.setTextColor(Utilities.getIdealColorForBackgroundColor(tint, + mConfig.taskBarViewLightTextColor, mConfig.taskBarViewDarkTextColor)); + mDismissButton.setImageDrawable(Utilities.getIdealResourceForBackgroundColor(tint, + mLightDismissDrawable, mDarkDismissDrawable)); } /** Unbinds the bar view from the task */ @@ -237,11 +244,11 @@ class TaskBarView extends FrameLayout { /** Enable the hw layers on this task view */ void enableHwLayers() { - mDismissButton.setLayerType(View.LAYER_TYPE_HARDWARE, null); + mDismissButton.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint); } /** Disable the hw layers on this task view */ void disableHwLayers() { - mDismissButton.setLayerType(View.LAYER_TYPE_NONE, null); + mDismissButton.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index e0a12b7fff5d..55f93354544c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -187,12 +187,20 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal return null; } - /** Update/get the transform */ + /** Update/get the transform (creates a new TaskViewTransform) */ public TaskViewTransform getStackTransform(int indexInStack, int stackScroll) { TaskViewTransform transform = new TaskViewTransform(); + return getStackTransform(indexInStack, stackScroll, transform); + } + /** Update/get the transform */ + public TaskViewTransform getStackTransform(int indexInStack, int stackScroll, + TaskViewTransform transformOut) { // Return early if we have an invalid index - if (indexInStack < 0) return transform; + if (indexInStack < 0) { + transformOut.reset(); + return transformOut; + } // Map the items to an continuous position relative to the specified scroll int numPeekCards = Constants.Values.TaskStackView.StackPeekNumCards; @@ -209,35 +217,35 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal float scale = Math.max(minScale, Math.min(1f, minScale + ((boundedT + (numPeekCards + 1)) * scaleInc))); float scaleYOffset = ((1f - scale) * mTaskRect.height()) / 2; - transform.scale = scale; + transformOut.scale = scale; // Set the y translation if (boundedT < 0f) { - transform.translationY = (int) ((Math.max(-numPeekCards, boundedT) / + transformOut.translationY = (int) ((Math.max(-numPeekCards, boundedT) / numPeekCards) * peekHeight - scaleYOffset); } else { - transform.translationY = (int) (boundedT * overlapHeight - scaleYOffset); + transformOut.translationY = (int) (boundedT * overlapHeight - scaleYOffset); } // Set the z translation int minZ = mConfig.taskViewTranslationZMinPx; int incZ = mConfig.taskViewTranslationZIncrementPx; - transform.translationZ = (int) Math.max(minZ, minZ + ((boundedT + numPeekCards) * incZ)); + transformOut.translationZ = (int) Math.max(minZ, minZ + ((boundedT + numPeekCards) * incZ)); // Set the alphas - transform.dismissAlpha = Math.max(-1f, Math.min(0f, t + 1)) + 1f; + transformOut.dismissAlpha = Math.max(-1f, Math.min(0f, t + 1)) + 1f; // Update the rect and visibility - transform.rect.set(mTaskRect); + transformOut.rect.set(mTaskRect); if (t < -(numPeekCards + 1)) { - transform.visible = false; + transformOut.visible = false; } else { - transform.rect.offset(0, transform.translationY); - Utilities.scaleRectAboutCenter(transform.rect, transform.scale); - transform.visible = Rect.intersects(mRect, transform.rect); + transformOut.rect.offset(0, transformOut.translationY); + Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale); + transformOut.visible = Rect.intersects(mRect, transformOut.rect); } - transform.t = t; - return transform; + transformOut.t = t; + return transformOut; } /** @@ -250,14 +258,25 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal boolean boundTranslationsToRect) { // XXX: Optimization: Use binary search to find the visible range + int taskTransformCount = taskTransforms.size(); int taskCount = tasks.size(); int firstVisibleIndex = -1; int lastVisibleIndex = -1; - taskTransforms.clear(); - taskTransforms.ensureCapacity(taskCount); + + // We can reuse the task transforms where possible to reduce object allocation + if (taskTransformCount < taskCount) { + // If there are less transforms than tasks, then add as many transforms as necessary + for (int i = taskTransformCount; i < taskCount; i++) { + taskTransforms.add(new TaskViewTransform()); + } + } else if (taskTransformCount > taskCount) { + // If there are more transforms than tasks, then just subset the transform list + taskTransforms.subList(0, taskCount); + } + + // Update the stack transforms for (int i = 0; i < taskCount; i++) { - TaskViewTransform transform = getStackTransform(i, stackScroll); - taskTransforms.add(transform); + TaskViewTransform transform = getStackTransform(i, stackScroll, taskTransforms.get(i)); if (transform.visible) { if (firstVisibleIndex < 0) { firstVisibleIndex = i; @@ -954,10 +973,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal int getExitTransformsForFilterAnimation(ArrayList<Task> curTasks, ArrayList<TaskViewTransform> curTaskTransforms, ArrayList<Task> tasks, ArrayList<TaskViewTransform> taskTransforms, - HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransformsOut, + HashMap<TaskView, TaskViewTransform> childViewTransformsOut, ArrayList<TaskView> childrenToRemoveOut) { // Animate all of the existing views out of view (if they are not in the visible range in // the new stack) or to their final positions in the new stack + int offset = 0; int movement = 0; int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { @@ -982,10 +1002,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal movement = Math.max(movement, Math.abs(toTransform.translationY - (int) tv.getTranslationY())); } - childViewTransformsOut.put(tv, new Pair(0, toTransform)); + + toTransform.startDelay = offset * Constants.Values.TaskStackView.FilterStartDelay; + childViewTransformsOut.put(tv, toTransform); + offset++; } - return Utilities.calculateTranslationAnimationDuration(movement, - mConfig.filteringCurrentViewsMinAnimDuration); + return mConfig.filteringCurrentViewsAnimDuration; } /** @@ -994,7 +1016,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal */ int getEnterTransformsForFilterAnimation(ArrayList<Task> tasks, ArrayList<TaskViewTransform> taskTransforms, - HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransformsOut) { + HashMap<TaskView, TaskViewTransform> childViewTransformsOut) { int offset = 0; int movement = 0; int taskCount = tasks.size(); @@ -1012,9 +1034,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal tv.prepareTaskTransformForFilterTaskHidden(fromTransform); tv.updateViewPropertiesToTaskTransform(fromTransform, 0); - int startDelay = offset * - Constants.Values.TaskStackView.FilterStartDelay; - childViewTransformsOut.put(tv, new Pair(startDelay, toTransform)); + toTransform.startDelay = offset * Constants.Values.TaskStackView.FilterStartDelay; + childViewTransformsOut.put(tv, toTransform); // Use the movement of the new views to calculate the duration of the animation movement = Math.max(movement, @@ -1023,8 +1044,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } } - return Utilities.calculateTranslationAnimationDuration(movement, - mConfig.filteringNewViewsMinAnimDuration); + return mConfig.filteringNewViewsAnimDuration; } /** Orchestrates the animations of the current child views and any new views. */ @@ -1035,8 +1055,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Calculate the transforms to animate out all the existing views if they are not in the // new visible range (or to their final positions in the stack if they are) final ArrayList<TaskView> childrenToRemove = new ArrayList<TaskView>(); - final HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransforms = - new HashMap<TaskView, Pair<Integer, TaskViewTransform>>(); + final HashMap<TaskView, TaskViewTransform> childViewTransforms = + new HashMap<TaskView, TaskViewTransform>(); int duration = getExitTransformsForFilterAnimation(curTasks, curTaskTransforms, tasks, taskTransforms, childViewTransforms, childrenToRemove); @@ -1051,10 +1071,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Animate all the views to their final transforms for (final TaskView tv : childViewTransforms.keySet()) { - Pair<Integer, TaskViewTransform> t = childViewTransforms.get(tv); + TaskViewTransform t = childViewTransforms.get(tv); tv.animate().cancel(); tv.animate() - .setStartDelay(t.first) .withEndAction(new Runnable() { @Override public void run() { @@ -1071,15 +1090,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal int duration = getEnterTransformsForFilterAnimation(tasks, taskTransforms, childViewTransforms); for (final TaskView tv : childViewTransforms.keySet()) { - Pair<Integer, TaskViewTransform> t = childViewTransforms.get(tv); - tv.animate().setStartDelay(t.first); - tv.updateViewPropertiesToTaskTransform(t.second, duration); + TaskViewTransform t = childViewTransforms.get(tv); + tv.updateViewPropertiesToTaskTransform(t, duration); } } } } }); - tv.updateViewPropertiesToTaskTransform(t.second, duration); + tv.updateViewPropertiesToTaskTransform(t, duration); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java index 09dc1c88dd1c..cfba74cba904 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -22,6 +22,7 @@ import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Outline; +import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; import android.graphics.Rect; @@ -63,6 +64,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On Point mLastTouchDown = new Point(); Path mRoundedRectClipPath = new Path(); Rect mTmpRect = new Rect(); + Paint mLayerPaint = new Paint(); TaskThumbnailView mThumbnailView; TaskBarView mBarView; @@ -200,7 +202,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On if (useLayers) { anim.withLayer(); } - anim.setStartDelay(0) + anim.setStartDelay(toTransform.startDelay) .setDuration(duration) .setInterpolator(mConfig.fastOutSlowInInterpolator) .start(); @@ -246,6 +248,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On // Fade the view out and slide it away toTransform.alpha = 0f; toTransform.translationY += 200; + toTransform.translationZ = 0; } /** @@ -440,13 +443,13 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On /** Enable the hw layers on this task view */ void enableHwLayers() { - mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint); mBarView.enableHwLayers(); } /** Disable the hw layers on this task view */ void disableHwLayers() { - mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, null); + mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint); mBarView.disableHwLayers(); } @@ -583,19 +586,25 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On } @Override - public void onClick(View v) { - if (v == mBarView.mApplicationIcon) { - mCb.onTaskIconClicked(this); - } else if (v == mBarView.mDismissButton) { - // Animate out the view and call the callback - final TaskView tv = this; - startDeleteTaskAnimation(new Runnable() { - @Override - public void run() { - mCb.onTaskDismissed(tv); + public void onClick(final View v) { + // We purposely post the handler delayed to allow for the touch feedback to draw + final TaskView tv = this; + postDelayed(new Runnable() { + @Override + public void run() { + if (v == mBarView.mApplicationIcon) { + mCb.onTaskIconClicked(tv); + } else if (v == mBarView.mDismissButton) { + // Animate out the view and call the callback + startDeleteTaskAnimation(new Runnable() { + @Override + public void run() { + mCb.onTaskDismissed(tv); + } + }); } - }); - } + } + }, 125); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java index 6c420e10b494..b351b0351530 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java @@ -21,6 +21,7 @@ import android.graphics.Rect; /* The transform state for a task view */ public class TaskViewTransform { + public int startDelay = 0; public int translationY = 0; public int translationZ = 0; public float scale = 1f; @@ -28,13 +29,14 @@ public class TaskViewTransform { public float dismissAlpha = 1f; public boolean visible = false; public Rect rect = new Rect(); - float t; + float t = 0f; public TaskViewTransform() { // Do nothing } public TaskViewTransform(TaskViewTransform o) { + startDelay = o.startDelay; translationY = o.translationY; translationZ = o.translationZ; scale = o.scale; @@ -45,6 +47,19 @@ public class TaskViewTransform { t = o.t; } + /** Resets the current transform */ + public void reset() { + startDelay = 0; + translationY = 0; + translationZ = 0; + scale = 1f; + alpha = 1f; + dismissAlpha = 1f; + visible = false; + rect.setEmpty(); + t = 0f; + } + /** Convenience functions to compare against current property values */ public boolean hasAlphaChangedFrom(float v) { return (Float.compare(alpha, v) != 0); @@ -64,8 +79,8 @@ public class TaskViewTransform { @Override public String toString() { - return "TaskViewTransform y: " + translationY + " z: " + translationZ + " scale: " + scale + - " alpha: " + alpha + " visible: " + visible + " rect: " + rect + + return "TaskViewTransform delay: " + startDelay + " y: " + translationY + " z: " + translationZ + + " scale: " + scale + " alpha: " + alpha + " visible: " + visible + " rect: " + rect + " dismissAlpha: " + dismissAlpha; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 20684a131293..5bc23b5e4057 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -311,8 +311,27 @@ public abstract class BaseStatusBar extends SystemUI implements mHandler.post(new Runnable() { @Override public void run() { - if (mNotificationData.findByKey(sbn.getKey()) != null || - isHeadsUp(sbn.getKey())) { + Notification n = sbn.getNotification(); + boolean isUpdate = mNotificationData.findByKey(sbn.getKey()) != null + || isHeadsUp(sbn.getKey()); + boolean isGroupedChild = n.getGroup() != null + && (n.flags & Notification.FLAG_GROUP_SUMMARY) == 0; + if (isGroupedChild) { + if (DEBUG) { + Log.d(TAG, "Ignoring group child: " + sbn); + } + // Don't show grouped notifications. If this is an + // update, i.e. the notification existed before but + // wasn't a group child, remove the old instance. + // Otherwise just update the ranking. + if (isUpdate) { + removeNotificationInternal(sbn.getKey(), rankingMap); + } else { + updateRankingInternal(rankingMap); + } + return; + } + if (isUpdate) { updateNotificationInternal(sbn, rankingMap); } else { addNotificationInternal(sbn, rankingMap); diff --git a/rs/java/android/renderscript/Element.java b/rs/java/android/renderscript/Element.java index 55b671d40bb7..c6b5b0d99ea2 100644 --- a/rs/java/android/renderscript/Element.java +++ b/rs/java/android/renderscript/Element.java @@ -140,17 +140,17 @@ public class Element extends BaseObj { MATRIX_3X3 (17, 36), MATRIX_2X2 (18, 16), - RS_ELEMENT (1000, 4), - RS_TYPE (1001, 4), - RS_ALLOCATION (1002, 4), - RS_SAMPLER (1003, 4), - RS_SCRIPT (1004, 4), - RS_MESH (1005, 4), - RS_PROGRAM_FRAGMENT (1006, 4), - RS_PROGRAM_VERTEX (1007, 4), - RS_PROGRAM_RASTER (1008, 4), - RS_PROGRAM_STORE (1009, 4), - RS_FONT (1010, 4); + RS_ELEMENT (1000), + RS_TYPE (1001), + RS_ALLOCATION (1002), + RS_SAMPLER (1003), + RS_SCRIPT (1004), + RS_MESH (1005), + RS_PROGRAM_FRAGMENT (1006), + RS_PROGRAM_VERTEX (1007), + RS_PROGRAM_RASTER (1008), + RS_PROGRAM_STORE (1009), + RS_FONT (1010); int mID; int mSize; @@ -158,6 +158,14 @@ public class Element extends BaseObj { mID = id; mSize = size; } + + DataType(int id) { + mID = id; + mSize = 4; + if (RenderScript.sPointerSize == 8) { + mSize = 32; + } + } } /** diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index aeb195fe945e..912a181ef944 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -18,6 +18,7 @@ package com.android.server; import android.database.ContentObserver; import android.os.BatteryStats; + import com.android.internal.app.IBatteryStats; import com.android.server.am.BatteryStatsService; import com.android.server.lights.Light; @@ -29,6 +30,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.os.BatteryManager; +import android.os.BatteryManagerInternal; import android.os.BatteryProperties; import android.os.Binder; import android.os.FileUtils; @@ -83,7 +85,7 @@ import java.io.PrintWriter; * service asynchronously itself. * </p> */ -public final class BatteryService extends Binder { +public final class BatteryService extends SystemService { private static final String TAG = BatteryService.class.getSimpleName(); private static final boolean DEBUG = false; @@ -140,10 +142,12 @@ public final class BatteryService extends Binder { private boolean mSentLowBatteryBroadcast = false; - public BatteryService(Context context, LightsManager lightsManager) { + public BatteryService(Context context) { + super(context); + mContext = context; mHandler = new Handler(true /*async*/); - mLed = new Led(context, lightsManager); + mLed = new Led(context, getLocalService(LightsManager.class)); mBatteryStats = BatteryStatsService.getService(); mCriticalBatteryLevel = mContext.getResources().getInteger( @@ -160,7 +164,10 @@ public final class BatteryService extends Binder { mInvalidChargerObserver.startObserving( "DEVPATH=/devices/virtual/switch/invalid_charger"); } + } + @Override + public void onStart() { IBinder b = ServiceManager.getService("batteryproperties"); final IBatteryPropertiesRegistrar batteryPropertiesRegistrar = IBatteryPropertiesRegistrar.Stub.asInterface(b); @@ -169,28 +176,34 @@ public final class BatteryService extends Binder { } catch (RemoteException e) { // Should never happen. } + + publishBinderService("battery", new BinderService()); + publishLocalService(BatteryManagerInternal.class, new LocalService()); } - void systemReady() { - // check our power situation now that it is safe to display the shutdown dialog. - synchronized (mLock) { - ContentObserver obs = new ContentObserver(mHandler) { - @Override - public void onChange(boolean selfChange) { - synchronized (mLock) { - updateBatteryWarningLevelLocked(); + @Override + public void onBootPhase(int phase) { + if (phase == PHASE_ACTIVITY_MANAGER_READY) { + // check our power situation now that it is safe to display the shutdown dialog. + synchronized (mLock) { + ContentObserver obs = new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + synchronized (mLock) { + updateBatteryWarningLevelLocked(); + } } - } - }; - final ContentResolver resolver = mContext.getContentResolver(); - resolver.registerContentObserver(Settings.Global.getUriFor( - Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL), - false, obs, UserHandle.USER_ALL); - updateBatteryWarningLevelLocked(); + }; + final ContentResolver resolver = mContext.getContentResolver(); + resolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL), + false, obs, UserHandle.USER_ALL); + updateBatteryWarningLevelLocked(); + } } } - void updateBatteryWarningLevelLocked() { + private void updateBatteryWarningLevelLocked() { final ContentResolver resolver = mContext.getContentResolver(); int defWarnLevel = mContext.getResources().getInteger( com.android.internal.R.integer.config_lowBatteryWarningLevel); @@ -207,15 +220,6 @@ public final class BatteryService extends Binder { processValuesLocked(true); } - /** - * Returns true if the device is plugged into any of the specified plug types. - */ - public boolean isPowered(int plugTypeSet) { - synchronized (mLock) { - return isPoweredLocked(plugTypeSet); - } - } - private boolean isPoweredLocked(int plugTypeSet) { // assume we are powered if battery state is unknown so // the "stay on while plugged in" option will work. @@ -234,34 +238,7 @@ public final class BatteryService extends Binder { return false; } - /** - * Returns the current plug type. - */ - public int getPlugType() { - synchronized (mLock) { - return mPlugType; - } - } - - /** - * Returns battery level as a percentage. - */ - public int getBatteryLevel() { - synchronized (mLock) { - return mBatteryProps.batteryLevel; - } - } - - /** - * Returns whether we currently consider the battery level to be low. - */ - public boolean getBatteryLevelLow() { - synchronized (mLock) { - return mBatteryLevelLow; - } - } - - public boolean shouldSendBatteryLowLocked() { + private boolean shouldSendBatteryLowLocked() { final boolean plugged = mPlugType != BATTERY_PLUGGED_NONE; final boolean oldPlugged = mLastPlugType != BATTERY_PLUGGED_NONE; @@ -277,15 +254,6 @@ public final class BatteryService extends Binder { && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel); } - /** - * Returns a non-zero value if an unsupported charger is attached. - */ - public int getInvalidCharger() { - synchronized (mLock) { - return mInvalidCharger; - } - } - private void shutdownIfNoPowerLocked() { // shut down gracefully if our battery is critically low and we are not powered. // wait until the system has booted before attempting to display the shutdown dialog. @@ -640,17 +608,7 @@ public final class BatteryService extends Binder { } } - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) - != PackageManager.PERMISSION_GRANTED) { - - pw.println("Permission Denial: can't dump Battery service from from pid=" - + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid()); - return; - } - + private void dumpInternal(PrintWriter pw, String[] args) { synchronized (mLock) { if (args == null || args.length == 0 || "-a".equals(args[0])) { pw.println("Current Battery Service state:"); @@ -801,4 +759,57 @@ public final class BatteryService extends Binder { } } } + + private final class BinderService extends Binder { + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + + pw.println("Permission Denial: can't dump Battery service from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + dumpInternal(pw, args); + } + } + + private final class LocalService extends BatteryManagerInternal { + @Override + public boolean isPowered(int plugTypeSet) { + synchronized (mLock) { + return isPoweredLocked(plugTypeSet); + } + } + + @Override + public int getPlugType() { + synchronized (mLock) { + return mPlugType; + } + } + + @Override + public int getBatteryLevel() { + synchronized (mLock) { + return mBatteryProps.batteryLevel; + } + } + + @Override + public boolean getBatteryLevelLow() { + synchronized (mLock) { + return mBatteryLevelLow; + } + } + + @Override + public int getInvalidCharger() { + synchronized (mLock) { + return mInvalidCharger; + } + } + } } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 9d92421a6bc4..1bd837b03b7b 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -89,7 +89,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { @Override public String toString() { - return "{pkgForDebug=" + pkgForDebug + " callerUid=" + callerUid + + return "{pkgForDebug=" + pkgForDebug + " callerUid=" + callerUid + " subId=" + subId + " events=" + Integer.toHexString(events) + "}"; } } @@ -208,11 +208,13 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { String action = intent.getAction(); Slog.d(TAG, "mBroadcastReceiver: action=" + action); if (Intent.ACTION_USER_SWITCHED.equals(action)) { - mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCHED, - intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0), 0)); + int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); + if (DBG) Slog.d(TAG, "onReceive: userHandle=" + userHandle); + mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCHED, userHandle, 0)); } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_SUBSCRIPTION_CHANGED)) { mDefaultSubId = intent.getLongExtra(PhoneConstants.SUBSCRIPTION_KEY, SubscriptionManager.getDefaultSubId()); + if (DBG) Slog.d(TAG, "onReceive: mDefaultSubId=" + mDefaultSubId); mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_DEFAULT_SUB, 0, 0)); } } @@ -340,18 +342,19 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { // the received subId value update the isLegacyApp field if ((r.subId <= 0) || (r.subId == SubscriptionManager.INVALID_SUB_ID)) { r.subId = mDefaultSubId; - r.isLegacyApp = true; // FIXME: is this needed ?? + r.isLegacyApp = true; // r.subId is to be update when default changes. } if (r.subId == SubscriptionManager.DEFAULT_SUB_ID) { r.subId = mDefaultSubId; + r.isLegacyApp = true; // r.subId is to be update when default changes. if (DBG) Slog.i(TAG, "listen: DEFAULT_SUB_ID"); } mRecords.add(r); - if (DBG) Slog.i(TAG, "listen: add new record=" + r); + if (DBG) Slog.i(TAG, "listen: add new record"); } int phoneId = SubscriptionManager.getPhoneId(subId); - int send = events & (events ^ r.events); r.events = events; + if (DBG) Slog.i(TAG, "listen: set events record=" + r); if (notifyNow && validatePhoneId(phoneId)) { if ((events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) { try { @@ -1063,6 +1066,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { pw.println(" mDataConnectionLinkProperties=" + mDataConnectionLinkProperties); pw.println(" mDataConnectionNetworkCapabilities=" + mDataConnectionNetworkCapabilities); + pw.println(" mDefaultSubId=" + mDefaultSubId); pw.println(" mCellLocation=" + mCellLocation); pw.println(" mCellInfo=" + mCellInfo); pw.println(" mDcRtInfo=" + mDcRtInfo); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 697e1f203336..34c1ecd7b72f 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2194,6 +2194,11 @@ public final class ActivityManagerService extends ActivityManagerNative LocalServices.addService(ActivityManagerInternal.class, new LocalService()); } + public void initPowerManagement() { + mStackSupervisor.initPowerManagement(); + mBatteryStatsService.initPowerManagement(); + } + @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { @@ -7625,14 +7630,24 @@ public final class ActivityManagerService extends ActivityManagerNative } } - private boolean isLockTaskAuthorized(ComponentName name) { + private boolean isLockTaskAuthorized(String pkg) { final DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); - return dpm != null && dpm.isLockTaskPermitted(name); + try { + int uid = mContext.getPackageManager().getPackageUid(pkg, + Binder.getCallingUserHandle().getIdentifier()); + return (uid == Binder.getCallingUid()) && dpm != null && dpm.isLockTaskPermitted(pkg); + } catch (NameNotFoundException e) { + return false; + } } private void startLockTaskMode(TaskRecord task) { - if (!isLockTaskAuthorized(task.intent.getComponent())) { + final String pkg; + synchronized (this) { + pkg = task.intent.getComponent().getPackageName(); + } + if (!isLockTaskAuthorized(pkg)) { return; } long ident = Binder.clearCallingIdentity(); @@ -7641,6 +7656,9 @@ public final class ActivityManagerService extends ActivityManagerNative // Since we lost lock on task, make sure it is still there. task = mStackSupervisor.anyTaskForIdLocked(task.taskId); if (task != null) { + if ((mFocusedActivity == null) || (task != mFocusedActivity.task)) { + throw new IllegalArgumentException("Invalid task, not in foreground"); + } mStackSupervisor.setLockTaskModeLocked(task); } } @@ -7651,25 +7669,25 @@ public final class ActivityManagerService extends ActivityManagerNative @Override public void startLockTaskMode(int taskId) { + final TaskRecord task; long ident = Binder.clearCallingIdentity(); try { - final TaskRecord task; synchronized (this) { task = mStackSupervisor.anyTaskForIdLocked(taskId); } - if (task != null) { - startLockTaskMode(task); - } } finally { Binder.restoreCallingIdentity(ident); } + if (task != null) { + startLockTaskMode(task); + } } @Override public void startLockTaskMode(IBinder token) { + final TaskRecord task; long ident = Binder.clearCallingIdentity(); try { - final TaskRecord task; synchronized (this) { final ActivityRecord r = ActivityRecord.forToken(token); if (r == null) { @@ -7677,24 +7695,27 @@ public final class ActivityManagerService extends ActivityManagerNative } task = r.task; } - if (task != null) { - startLockTaskMode(task); - } } finally { Binder.restoreCallingIdentity(ident); } + if (task != null) { + startLockTaskMode(task); + } } @Override public void stopLockTaskMode() { - // Check if the calling task is eligible to use lock task - final int uid = Binder.getCallingUid(); + // Verify that the user matches the package of the intent for the TaskRecord + // we are locked to. This will ensure the same caller for startLockTaskMode and + // stopLockTaskMode. try { - final String name = AppGlobals.getPackageManager().getNameForUid(uid); - if (!isLockTaskAuthorized(new ComponentName(name, name))) { - return; + String pkg = mStackSupervisor.mLockTaskModeTask.intent.getPackage(); + int uid = mContext.getPackageManager().getPackageUid(pkg, + Binder.getCallingUserHandle().getIdentifier()); + if (uid != Binder.getCallingUid()) { + throw new SecurityException("Invalid uid, expected " + uid); } - } catch (RemoteException e) { + } catch (NameNotFoundException e) { Log.d(TAG, "stopLockTaskMode " + e); return; } diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 11f855e2eb46..dd9cae90dd00 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -19,6 +19,7 @@ package com.android.server.am; import android.app.ActivityManager.TaskDescription; import android.os.PersistableBundle; import android.os.Trace; + import com.android.internal.app.ResolverActivity; import com.android.internal.util.XmlUtils; import com.android.server.AttributeCache; @@ -49,6 +50,7 @@ import android.util.Slog; import android.util.TimeUtils; import android.view.IApplicationToken; import android.view.WindowManager; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -58,6 +60,7 @@ import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashSet; +import java.util.Objects; /** * An entry in the history stack, representing an activity. @@ -83,6 +86,7 @@ final class ActivityRecord { final ActivityManagerService service; // owner final IApplicationToken.Stub appToken; // window manager token final ActivityInfo info; // all about me + final ApplicationInfo appInfo; // information about activity's app final int launchedFromUid; // always the uid who started the activity. final String launchedFromPackage; // always the package who started the activity. final int userId; // Which user is this running for? @@ -103,9 +107,6 @@ final class ActivityRecord { static final int RECENTS_ACTIVITY_TYPE = 2; int mActivityType; - final String baseDir; // where activity source (resources etc) located - final String resDir; // where public activity source (public resources etc) located - final String dataDir; // where activity data should go CharSequence nonLocalizedLabel; // the label information from the package mgr. int labelRes; // the label information from the package mgr. int icon; // resource identifier of activity's icon. @@ -184,11 +185,13 @@ final class ActivityRecord { pw.print(prefix); pw.print("taskAffinity="); pw.println(taskAffinity); pw.print(prefix); pw.print("realActivity="); pw.println(realActivity.flattenToShortString()); - pw.print(prefix); pw.print("baseDir="); pw.println(baseDir); - if (!resDir.equals(baseDir)) { - pw.print(prefix); pw.print("resDir="); pw.println(resDir); + if (appInfo != null) { + pw.print(prefix); pw.print("baseDir="); pw.println(appInfo.sourceDir); + if (!Objects.equals(appInfo.sourceDir, appInfo.publicSourceDir)) { + pw.print(prefix); pw.print("resDir="); pw.println(appInfo.publicSourceDir); + } + pw.print(prefix); pw.print("dataDir="); pw.println(appInfo.dataDir); } - pw.print(prefix); pw.print("dataDir="); pw.println(dataDir); pw.print(prefix); pw.print("stateNotNeeded="); pw.print(stateNotNeeded); pw.print(" componentSpecified="); pw.print(componentSpecified); pw.print(" mActivityType="); pw.println(mActivityType); @@ -418,9 +421,7 @@ final class ActivityRecord { taskAffinity = aInfo.taskAffinity; stateNotNeeded = (aInfo.flags& ActivityInfo.FLAG_STATE_NOT_NEEDED) != 0; - baseDir = aInfo.applicationInfo.sourceDir; - resDir = aInfo.applicationInfo.publicSourceDir; - dataDir = aInfo.applicationInfo.dataDir; + appInfo = aInfo.applicationInfo; nonLocalizedLabel = aInfo.nonLocalizedLabel; labelRes = aInfo.labelRes; if (nonLocalizedLabel == null && labelRes == 0) { @@ -488,9 +489,7 @@ final class ActivityRecord { realActivity = null; taskAffinity = null; stateNotNeeded = false; - baseDir = null; - resDir = null; - dataDir = null; + appInfo = null; processName = null; packageName = null; fullscreen = true; @@ -568,7 +567,10 @@ final class ActivityRecord { } boolean isPersistable() { - return (info.flags & ActivityInfo.FLAG_PERSISTABLE) != 0; + return (info.persistableMode == ActivityInfo.PERSIST_ROOT_ONLY || + info.persistableMode == ActivityInfo.PERSIST_ACROSS_REBOOTS) && + (intent == null || + (intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0); } void makeFinishing() { diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 66e9eb3e0134..9264186c6329 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -46,6 +46,8 @@ import android.app.ActivityManager.RunningTaskInfo; import android.app.IActivityManager.WaitResult; import android.app.ResultInfo; import android.app.StatusBarManager; +import android.app.admin.DevicePolicyManager; +import android.app.admin.IDevicePolicyManager; import android.content.ComponentName; import android.content.Context; import android.content.IIntentSender; @@ -143,6 +145,7 @@ public final class ActivityStackSupervisor implements DisplayListener { /** Status Bar Service **/ private IBinder mToken = new Binder(); private IStatusBarService mStatusBarService; + private IDevicePolicyManager mDevicePolicyManager; // For debugging to make sure the caller when acquiring/releasing our // wake lock is the system process. @@ -227,14 +230,14 @@ public final class ActivityStackSupervisor implements DisplayListener { * receivers to launch an activity and get that to run before the device * goes back to sleep. */ - final PowerManager.WakeLock mLaunchingActivity; + PowerManager.WakeLock mLaunchingActivity; /** * Set when the system is going to sleep, until we have * successfully paused the current activity and released our wake lock. * At that point the system is allowed to actually sleep. */ - final PowerManager.WakeLock mGoingToSleep; + PowerManager.WakeLock mGoingToSleep; /** Stack id of the front stack when user switched, indexed by userId. */ SparseIntArray mUserStackInFront = new SparseIntArray(2); @@ -251,16 +254,20 @@ public final class ActivityStackSupervisor implements DisplayListener { /** If non-null then the task specified remains in front and no other tasks may be started * until the task exits or #stopLockTaskMode() is called. */ - private TaskRecord mLockTaskModeTask; + TaskRecord mLockTaskModeTask; public ActivityStackSupervisor(ActivityManagerService service) { mService = service; + mHandler = new ActivityStackSupervisorHandler(mService.mHandler.getLooper()); + } + + /** + * At the time when the constructor runs, the power manager has not yet been + * initialized. So we initialize our wakelocks afterwards. + */ + void initPowerManagement() { PowerManager pm = (PowerManager)mService.mContext.getSystemService(Context.POWER_SERVICE); mGoingToSleep = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Sleep"); - mHandler = new ActivityStackSupervisorHandler(mService.mHandler.getLooper()); - if (VALIDATE_WAKE_LOCK_CALLER && Binder.getCallingUid() != Process.myUid()) { - throw new IllegalStateException("Calling must be system uid"); - } mLaunchingActivity = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Launch"); mLaunchingActivity.setReferenceCounted(false); @@ -281,6 +288,19 @@ public final class ActivityStackSupervisor implements DisplayListener { } } + private IDevicePolicyManager getDevicePolicyManager() { + synchronized (mService) { + if (mDevicePolicyManager == null) { + mDevicePolicyManager = IDevicePolicyManager.Stub.asInterface( + ServiceManager.checkService(Context.DEVICE_POLICY_SERVICE)); + if (mDevicePolicyManager == null) { + Slog.w(TAG, "warning: no DEVICE_POLICY_SERVICE"); + } + } + return mDevicePolicyManager; + } + } + void setWindowManager(WindowManagerService wm) { synchronized (mService) { mWindowManager = wm; @@ -2985,12 +3005,15 @@ public final class ActivityStackSupervisor implements DisplayListener { } void setLockTaskModeLocked(TaskRecord task) { - final Message lockTaskMsg = Message.obtain(); if (task == null) { - // Take out of lock task mode. - mLockTaskModeTask = null; - lockTaskMsg.what = LOCK_TASK_END_MSG; - mHandler.sendMessage(lockTaskMsg); + // Take out of lock task mode if necessary + if (mLockTaskModeTask != null) { + final Message lockTaskMsg = Message.obtain(); + lockTaskMsg.arg1 = mLockTaskModeTask.userId; + lockTaskMsg.what = LOCK_TASK_END_MSG; + mLockTaskModeTask = null; + mHandler.sendMessage(lockTaskMsg); + } return; } if (isLockTaskModeViolation(task)) { @@ -3000,6 +3023,10 @@ public final class ActivityStackSupervisor implements DisplayListener { mLockTaskModeTask = task; findTaskToMoveToFrontLocked(task, 0, null); resumeTopActivitiesLocked(); + + final Message lockTaskMsg = Message.obtain(); + lockTaskMsg.obj = mLockTaskModeTask.intent.getComponent().getPackageName(); + lockTaskMsg.arg1 = mLockTaskModeTask.userId; lockTaskMsg.what = LOCK_TASK_START_MSG; mHandler.sendMessage(lockTaskMsg); } @@ -3108,6 +3135,11 @@ public final class ActivityStackSupervisor implements DisplayListener { (StatusBarManager.DISABLE_MASK ^ StatusBarManager.DISABLE_BACK, mToken, mService.mContext.getPackageName()); } + if (getDevicePolicyManager() != null) { + getDevicePolicyManager().notifyLockTaskModeChanged(true, + (String)msg.obj, + msg.arg1); + } } catch (RemoteException ex) { throw new RuntimeException(ex); } @@ -3120,6 +3152,10 @@ public final class ActivityStackSupervisor implements DisplayListener { (StatusBarManager.DISABLE_NONE, mToken, mService.mContext.getPackageName()); } + if (getDevicePolicyManager() != null) { + getDevicePolicyManager().notifyLockTaskModeChanged(false, null, + msg.arg1); + } } catch (RemoteException ex) { throw new RuntimeException(ex); } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index eb253ebeaa19..f403d0875306 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -74,12 +74,19 @@ public final class BatteryStatsService extends IBatteryStats.Stub mStats.setRadioScanningTimeout(mContext.getResources().getInteger( com.android.internal.R.integer.config_radioScanningTimeout) * 1000L); + } + + /** + * At the time when the constructor runs, the power manager has not yet been + * initialized. So we initialize the low power observer later. + */ + public void initPowerManagement() { mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); mPowerManagerInternal.registerLowPowerModeObserver(this); mStats.noteLowPowerMode(mPowerManagerInternal.getLowPowerModeEnabled()); (new WakeupReasonThread()).start(); - } - + } + public void shutdown() { Slog.w("BatteryStats", "Writing battery stats before shutdown..."); synchronized (mStats) { diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index e54c95ee16fb..f79c02601c4d 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -45,6 +45,7 @@ import android.util.TimeUtils; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * A running application service. @@ -70,9 +71,6 @@ final class ServiceRecord extends Binder { final String packageName; // the package implementing intent's component final String processName; // process where this component wants to run final String permission;// permission needed to access service - final String baseDir; // where activity source (resources etc) located - final String resDir; // where public activity source (public resources etc) located - final String dataDir; // where activity data should go final boolean exported; // from ServiceInfo.exported final Runnable restarter; // used to schedule retries of starting the service final long createTime; // when this service was created @@ -209,11 +207,13 @@ final class ServiceRecord extends Binder { } long now = SystemClock.uptimeMillis(); long nowReal = SystemClock.elapsedRealtime(); - pw.print(prefix); pw.print("baseDir="); pw.println(baseDir); - if (!resDir.equals(baseDir)) { - pw.print(prefix); pw.print("resDir="); pw.println(resDir); + if (appInfo != null) { + pw.print(prefix); pw.print("baseDir="); pw.println(appInfo.sourceDir); + if (!Objects.equals(appInfo.sourceDir, appInfo.publicSourceDir)) { + pw.print(prefix); pw.print("resDir="); pw.println(appInfo.publicSourceDir); + } + pw.print(prefix); pw.print("dataDir="); pw.println(appInfo.dataDir); } - pw.print(prefix); pw.print("dataDir="); pw.println(dataDir); pw.print(prefix); pw.print("app="); pw.println(app); if (isolatedProc != null) { pw.print(prefix); pw.print("isolatedProc="); pw.println(isolatedProc); @@ -305,9 +305,6 @@ final class ServiceRecord extends Binder { packageName = sInfo.applicationInfo.packageName; processName = sInfo.processName; permission = sInfo.permission; - baseDir = sInfo.applicationInfo.sourceDir; - resDir = sInfo.applicationInfo.publicSourceDir; - dataDir = sInfo.applicationInfo.dataDir; exported = sInfo.exported; this.restarter = restarter; createTime = SystemClock.elapsedRealtime(); diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java index bb289fab3f58..c79b33d1f301 100644 --- a/services/core/java/com/android/server/am/TaskPersister.java +++ b/services/core/java/com/android/server/am/TaskPersister.java @@ -45,7 +45,7 @@ public class TaskPersister { static final boolean DEBUG = false; /** When in slow mode don't write tasks out faster than this */ - private static final long INTER_TASK_DELAY_MS = 60000; + private static final long INTER_TASK_DELAY_MS = 10000; private static final long DEBUG_INTER_TASK_DELAY_MS = 5000; private static final String RECENTS_FILENAME = "_task"; @@ -69,6 +69,7 @@ public class TaskPersister { TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor) { sTasksDir = new File(systemDir, TASKS_DIRNAME); if (!sTasksDir.exists()) { + if (DEBUG) Slog.d(TAG, "Creating tasks directory " + sTasksDir); if (!sTasksDir.mkdir()) { Slog.e(TAG, "Failure creating tasks directory " + sTasksDir); } @@ -76,6 +77,7 @@ public class TaskPersister { sImagesDir = new File(systemDir, IMAGES_DIRNAME); if (!sImagesDir.exists()) { + if (DEBUG) Slog.d(TAG, "Creating images directory " + sTasksDir); if (!sImagesDir.mkdir()) { Slog.e(TAG, "Failure creating images directory " + sImagesDir); } @@ -172,14 +174,15 @@ public class TaskPersister { TaskRecord.restoreFromXml(in, mStackSupervisor); if (DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task=" + task); if (task != null) { + task.isPersistable = true; tasks.add(task); final int taskId = task.taskId; recoveredTaskIds.add(taskId); mStackSupervisor.setNextTaskId(taskId); } } else { - Slog.e(TAG, "restoreTasksLocked Unknown xml event=" + event + " name=" - + name); + Slog.wtf(TAG, "restoreTasksLocked Unknown xml event=" + event + + " name=" + name); } } XmlUtils.skipCurrentTag(in); @@ -195,6 +198,7 @@ public class TaskPersister { } } if (!DEBUG && deleteFile) { + if (DEBUG) Slog.d(TAG, "Deleting file=" + taskFile.getName()); taskFile.delete(); } } @@ -209,7 +213,7 @@ public class TaskPersister { Arrays.sort(tasksArray, new Comparator<TaskRecord>() { @Override public int compare(TaskRecord lhs, TaskRecord rhs) { - final long diff = lhs.mLastTimeMoved - rhs.mLastTimeMoved; + final long diff = rhs.mLastTimeMoved - lhs.mLastTimeMoved; if (diff < 0) { return -1; } else if (diff > 0) { @@ -233,8 +237,7 @@ public class TaskPersister { try { taskId = Integer.valueOf(filename.substring(0, taskIdEnd)); } catch (Exception e) { - if (DEBUG) Slog.d(TAG, "removeObsoleteFile: Can't parse file=" + - file.getName()); + Slog.wtf(TAG, "removeObsoleteFile: Can't parse file=" + file.getName()); file.delete(); continue; } @@ -288,15 +291,18 @@ public class TaskPersister { synchronized(mService) { final ArrayList<TaskRecord> tasks = mService.mRecentTasks; persistentTaskIds.clear(); + if (DEBUG) Slog.d(TAG, "mRecents=" + tasks); for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { task = tasks.get(taskNdx); if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task + " persistable=" + task.isPersistable + " needsPersisting=" + task.needsPersisting); - if (task.isPersistable) { + if (task.isPersistable && !task.stack.isHomeStack()) { + if (DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task); persistentTaskIds.add(task.taskId); if (task.needsPersisting) { try { + if (DEBUG) Slog.d(TAG, "Saving task=" + task); stringWriter = saveToXml(task); break; } catch (IOException e) { @@ -305,6 +311,8 @@ public class TaskPersister { task.needsPersisting = false; } } + } else { + if (DEBUG) Slog.d(TAG, "omitting from persistentTaskIds task=" + task); } } } @@ -330,6 +338,8 @@ public class TaskPersister { // Made it through the entire list and didn't find anything new that needed // persisting. if (!DEBUG) { + if (DEBUG) Slog.d(TAG, "Calling removeObsoleteFiles persistentTaskIds=" + + persistentTaskIds); removeObsoleteFiles(persistentTaskIds); } diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index 81a0b363cac1..1cde41fb92ad 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -652,8 +652,9 @@ final class TaskRecord extends ThumbnailHolder { final int numActivities = activities.size(); for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) { final ActivityRecord r = activities.get(activityNdx); - if (!r.isPersistable() || (activityNdx > 0 && - (r.intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0)) { + if (r.info.persistableMode == ActivityInfo.PERSIST_ROOT_ONLY || !r.isPersistable() || + ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) && + activityNdx > 0) { // Stop at first non-persistable or first break in task (CLEAR_WHEN_TASK_RESET). break; } diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index a77443dbc1cf..c7f4f6a61e59 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -119,7 +119,7 @@ final class DisplayDeviceInfo { public int height; /** - * The refresh rate of the display. + * The refresh rate of the display, in frames per second. */ public float refreshRate; @@ -144,6 +144,20 @@ final class DisplayDeviceInfo { public float yDpi; /** + * This is a positive value indicating the phase offset of the VSYNC events provided by + * Choreographer relative to the display refresh. For example, if Choreographer reports + * that the refresh occurred at time N, it actually occurred at (N - appVsyncOffsetNanos). + */ + public long appVsyncOffsetNanos; + + /** + * This is how far in advance a buffer must be queued for presentation at + * a given time. If you want a buffer to appear on the screen at + * time N, you must submit the buffer before (N - bufferDeadlineNanos). + */ + public long presentationDeadlineNanos; + + /** * Display flags. */ public int flags; @@ -219,6 +233,8 @@ final class DisplayDeviceInfo { && densityDpi == other.densityDpi && xDpi == other.xDpi && yDpi == other.yDpi + && appVsyncOffsetNanos == other.appVsyncOffsetNanos + && presentationDeadlineNanos == other.presentationDeadlineNanos && flags == other.flags && touch == other.touch && rotation == other.rotation @@ -242,6 +258,8 @@ final class DisplayDeviceInfo { densityDpi = other.densityDpi; xDpi = other.xDpi; yDpi = other.yDpi; + appVsyncOffsetNanos = other.appVsyncOffsetNanos; + presentationDeadlineNanos = other.presentationDeadlineNanos; flags = other.flags; touch = other.touch; rotation = other.rotation; @@ -261,6 +279,8 @@ final class DisplayDeviceInfo { sb.append(", ").append(refreshRate).append(" fps, "); sb.append("density ").append(densityDpi); sb.append(", ").append(xDpi).append(" x ").append(yDpi).append(" dpi"); + sb.append(", appVsyncOff ").append(appVsyncOffsetNanos); + sb.append(", presDeadline ").append(presentationDeadlineNanos); sb.append(", touch ").append(touchToString(touch)); sb.append(", rotation ").append(rotation); sb.append(", type ").append(Display.typeToString(type)); diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index e80aecdcfb44..098537cd56bb 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -161,6 +161,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { mInfo.width = mPhys.width; mInfo.height = mPhys.height; mInfo.refreshRate = mPhys.refreshRate; + mInfo.appVsyncOffsetNanos = mPhys.appVsyncOffsetNanos; + mInfo.presentationDeadlineNanos = mPhys.presentationDeadlineNanos; mInfo.state = mState; // Assume that all built-in displays that have secure output (eg. HDCP) also diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index d61a35b77ffd..ed619d9c60cb 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -213,6 +213,8 @@ final class LogicalDisplay { mBaseDisplayInfo.logicalDensityDpi = deviceInfo.densityDpi; mBaseDisplayInfo.physicalXDpi = deviceInfo.xDpi; mBaseDisplayInfo.physicalYDpi = deviceInfo.yDpi; + mBaseDisplayInfo.appVsyncOffsetNanos = deviceInfo.appVsyncOffsetNanos; + mBaseDisplayInfo.presentationDeadlineNanos = deviceInfo.presentationDeadlineNanos; mBaseDisplayInfo.state = deviceInfo.state; mBaseDisplayInfo.smallestNominalAppWidth = deviceInfo.width; mBaseDisplayInfo.smallestNominalAppHeight = deviceInfo.height; diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java index bfd8372c52e3..3b23b6add8a0 100644 --- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java +++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java @@ -191,6 +191,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter { private final int mWidth; private final int mHeight; private final float mRefreshRate; + private final long mDisplayPresentationDeadlineNanos; private final int mDensityDpi; private final boolean mSecure; @@ -200,7 +201,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter { private DisplayDeviceInfo mInfo; public OverlayDisplayDevice(IBinder displayToken, String name, - int width, int height, float refreshRate, + int width, int height, float refreshRate, long presentationDeadlineNanos, int densityDpi, boolean secure, int state, SurfaceTexture surfaceTexture) { super(OverlayDisplayAdapter.this, displayToken); @@ -208,6 +209,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter { mWidth = width; mHeight = height; mRefreshRate = refreshRate; + mDisplayPresentationDeadlineNanos = presentationDeadlineNanos; mDensityDpi = densityDpi; mSecure = secure; mState = state; @@ -249,6 +251,8 @@ final class OverlayDisplayAdapter extends DisplayAdapter { mInfo.densityDpi = mDensityDpi; mInfo.xDpi = mDensityDpi; mInfo.yDpi = mDensityDpi; + mInfo.presentationDeadlineNanos = mDisplayPresentationDeadlineNanos + + 1000000000L / (int) mRefreshRate; // display's deadline + 1 frame mInfo.flags = DisplayDeviceInfo.FLAG_PRESENTATION; if (mSecure) { mInfo.flags |= DisplayDeviceInfo.FLAG_SECURE; @@ -297,12 +301,13 @@ final class OverlayDisplayAdapter extends DisplayAdapter { // Called on the UI thread. @Override - public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate, int state) { + public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate, + long presentationDeadlineNanos, int state) { synchronized (getSyncRoot()) { IBinder displayToken = SurfaceControl.createDisplay(mName, mSecure); mDevice = new OverlayDisplayDevice(displayToken, mName, - mWidth, mHeight, refreshRate, mDensityDpi, mSecure, - state, surfaceTexture); + mWidth, mHeight, refreshRate, presentationDeadlineNanos, + mDensityDpi, mSecure, state, surfaceTexture); sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED); } diff --git a/services/core/java/com/android/server/display/OverlayDisplayWindow.java b/services/core/java/com/android/server/display/OverlayDisplayWindow.java index 06891f37a6ea..9ca5fda608b2 100644 --- a/services/core/java/com/android/server/display/OverlayDisplayWindow.java +++ b/services/core/java/com/android/server/display/OverlayDisplayWindow.java @@ -303,7 +303,7 @@ final class OverlayDisplayWindow implements DumpUtils.Dump { public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { mListener.onWindowCreated(surfaceTexture, mDefaultDisplayInfo.refreshRate, - mDefaultDisplayInfo.state); + mDefaultDisplayInfo.presentationDeadlineNanos, mDefaultDisplayInfo.state); } @Override @@ -373,7 +373,7 @@ final class OverlayDisplayWindow implements DumpUtils.Dump { */ public interface Listener { public void onWindowCreated(SurfaceTexture surfaceTexture, - float refreshRate, int state); + float refreshRate, long presentationDeadlineNanos, int state); public void onWindowDestroyed(); public void onStateChanged(int state); } diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index 14ef5a9162b4..ec14cf1e1d10 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -171,6 +171,7 @@ final class VirtualDisplayAdapter extends DisplayAdapter { mInfo.densityDpi = mDensityDpi; mInfo.xDpi = mDensityDpi; mInfo.yDpi = mDensityDpi; + mInfo.presentationDeadlineNanos = 1000000000L / (int) mInfo.refreshRate; // 1 frame mInfo.flags = 0; if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) { mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java index cd57941bcc3e..a05bf2c84dcd 100644 --- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java +++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java @@ -127,7 +127,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { pw.println("mPendingStatusChangeBroadcast=" + mPendingStatusChangeBroadcast); pw.println("mPendingNotificationUpdate=" + mPendingNotificationUpdate); pw.println("mSupportsProtectedBuffers=" + mSupportsProtectedBuffers); - + // Try to dump the controller state. if (mDisplayController == null) { pw.println("mDisplayController=null"); @@ -729,6 +729,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { mInfo.width = mWidth; mInfo.height = mHeight; mInfo.refreshRate = mRefreshRate; + mInfo.presentationDeadlineNanos = 1000000000L / (int) mRefreshRate; // 1 frame mInfo.flags = mFlags; mInfo.type = Display.TYPE_WIFI; mInfo.address = mAddress; diff --git a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java index 23cf40bd0168..74eaf2a6fc13 100644 --- a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java +++ b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java @@ -72,9 +72,9 @@ final class ActiveSourceHandler { } if (!mSource.isInPresetInstallationMode()) { - int prevActiveInput = mSource.getActiveInput(); + int prevActiveInput = mSource.getActivePortId(); mSource.updateActiveDevice(deviceLogicalAddress, routingPath); - if (prevActiveInput != mSource.getActiveInput()) { + if (prevActiveInput != mSource.getActivePortId()) { // TODO: change port input here. } invokeCallback(HdmiCec.RESULT_SUCCESS); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 08d778631744..f86d655af470 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -372,15 +372,28 @@ abstract class HdmiCecLocalDevice { } /** - * Returns the ID of the active HDMI port. The active input is the port that has the active - * routing path connected directly or indirectly under the device hierarchy. + * Returns the ID of the active HDMI port. The active port is the one that has the active + * routing path connected to it directly or indirectly under the device hierarchy. */ - int getActiveInput() { + int getActivePortId() { synchronized (mLock) { return mService.pathToPortId(mActiveRoutingPath); } } + /** + * Update the active port. + * + * @param portId the new active port id + */ + void setActivePortId(int portId) { + synchronized (mLock) { + // We update active routing path instead, since we get the active port id from + // the active routing path. + mActiveRoutingPath = mService.portIdToPath(portId); + } + } + void updateActiveDevice(int logicalAddress, int physicalAddress) { synchronized (mLock) { mActiveSource = logicalAddress; diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 92ddd3d2b4d1..353f6036bcfc 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -52,6 +52,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { HdmiCecLocalDeviceTv(HdmiControlService service) { super(service, HdmiCec.DEVICE_TV); + + // TODO: load system audio mode and set it to mSystemAudioMode. } @Override @@ -85,6 +87,60 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { addAndStartAction(new DeviceSelectAction(this, targetDevice, callback)); } + /** + * Performs the action routing control. + * + * @param portId new HDMI port to route to + * @param callback callback object to report the result with + */ + void portSelect(int portId, IHdmiControlCallback callback) { + assertRunOnServiceThread(); + if (isInPresetInstallationMode()) { + invokeCallback(callback, HdmiCec.RESULT_INCORRECT_MODE); + return; + } + // Make sure this call does not stem from <Active Source> message reception, in + // which case the two ports will be the same. + if (portId == getActivePortId()) { + invokeCallback(callback, HdmiCec.RESULT_SUCCESS); + return; + } + setActivePortId(portId); + + // TODO: Return immediately if the operation is triggered by <Text/Image View On> + // TODO: Handle invalid port id / active input which should be treated as an + // internal tuner. + + removeAction(RoutingControlAction.class); + + int oldPath = mService.portIdToPath(mService.portIdToPath(getActivePortId())); + int newPath = mService.portIdToPath(portId); + HdmiCecMessage routingChange = + HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath); + mService.sendCecCommand(routingChange); + addAndStartAction(new RoutingControlAction(this, newPath, callback)); + } + + /** + * Sends key to a target CEC device. + * + * @param keyCode key code to send. Defined in {@link KeyEvent}. + * @param isPressed true if this is keypress event + */ + void sendKeyEvent(int keyCode, boolean isPressed) { + assertRunOnServiceThread(); + List<SendKeyAction> action = getActions(SendKeyAction.class); + if (!action.isEmpty()) { + action.get(0).processKeyEvent(keyCode, isPressed); + } else { + if (isPressed) { + addAndStartAction(new SendKeyAction(this, getActiveSource(), keyCode)); + } else { + Slog.w(TAG, "Discard key release event"); + } + } + } + private static void invokeCallback(IHdmiControlCallback callback, int result) { try { callback.onComplete(result); @@ -174,6 +230,15 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this)); + + // If there is AVR, initiate System Audio Auto initiation action, + // which turns on and off system audio according to last system + // audio setting. + HdmiCecDeviceInfo avrInfo = getAvrDeviceInfo(); + if (avrInfo != null) { + addAndStartAction(new SystemAudioAutoInitiationAction( + HdmiCecLocalDeviceTv.this, avrInfo.getLogicalAddress())); + } } }); addAndStartAction(action); @@ -456,4 +521,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { hotplugActions.get(0).pollAllDevicesNow(); } } + + boolean canChangeSystemAudio() { + // TODO: implement this. + // return true if no system audio control sequence is running. + return false; + } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java index 8dbfd85e7c73..361a063b729d 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java @@ -377,6 +377,17 @@ public class HdmiCecMessageBuilder { return buildCommand(src, dest, HdmiCec.MESSAGE_USER_CONTROL_RELEASED); } + /** + * Build <Give System Audio Mode Status> command. + * + * @param src source address of command + * @param dest destination address of command + * @return newly created {@link HdmiCecMessage} + */ + static HdmiCecMessage buildGiveSystemAudioModeStatus(int src, int dest) { + return buildCommand(src, dest, HdmiCec.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS); + } + /***** Please ADD new buildXXX() methods above. ******/ /** diff --git a/services/core/java/com/android/server/hdmi/HdmiConstants.java b/services/core/java/com/android/server/hdmi/HdmiConstants.java index 9c60e55b912d..5294506f2125 100644 --- a/services/core/java/com/android/server/hdmi/HdmiConstants.java +++ b/services/core/java/com/android/server/hdmi/HdmiConstants.java @@ -95,5 +95,7 @@ final class HdmiConstants { static final int POLL_ITERATION_IN_ORDER = 0x10000; static final int POLL_ITERATION_REVERSE_ORDER = 0x20000; + static final int UNKNOWN_VOLUME = -1; + private HdmiConstants() { /* cannot be instantiated */ } } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 10da7567bf17..fddb833c7c88 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -507,7 +507,7 @@ public final class HdmiControlService extends SystemService { public void run() { HdmiCecLocalDeviceTv tv = tv(); if (tv == null) { - Slog.w(TAG, "Local playback device not available"); + Slog.w(TAG, "Local tv device not available"); invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); return; } @@ -517,6 +517,41 @@ public final class HdmiControlService extends SystemService { } @Override + public void portSelect(final int portId, final IHdmiControlCallback callback) { + enforceAccessPermission(); + runOnServiceThread(new Runnable() { + @Override + public void run() { + HdmiCecLocalDeviceTv tv = tv(); + if (tv == null) { + Slog.w(TAG, "Local tv device not available"); + invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); + return; + } + tv.portSelect(portId, callback); + } + }); + } + + @Override + public void sendKeyEvent(final int keyCode, final boolean isPressed) { + enforceAccessPermission(); + runOnServiceThread(new Runnable() { + @Override + public void run() { + // TODO: sendKeyEvent is for TV device only for now. Allow other + // local devices of different types to use this as well. + HdmiCecLocalDeviceTv tv = tv(); + if (tv == null) { + Slog.w(TAG, "Local tv device not available"); + return; + } + tv.sendKeyEvent(keyCode, isPressed); + } + }); + } + + @Override public void oneTouchPlay(final IHdmiControlCallback callback) { enforceAccessPermission(); runOnServiceThread(new Runnable() { @@ -572,16 +607,6 @@ public final class HdmiControlService extends SystemService { } @Override - public void portSelect(int portId, IHdmiControlCallback callback) { - // TODO: Implement this - } - - @Override - public void sendKeyEvent(int keyCode, boolean isPressed) { - // TODO: Implement this - } - - @Override public List<HdmiPortInfo> getPortInfo() { enforceAccessPermission(); return mPortInfo; diff --git a/services/core/java/com/android/server/hdmi/RoutingControlAction.java b/services/core/java/com/android/server/hdmi/RoutingControlAction.java index 2eec8462bff5..0d657b2624c9 100644 --- a/services/core/java/com/android/server/hdmi/RoutingControlAction.java +++ b/services/core/java/com/android/server/hdmi/RoutingControlAction.java @@ -196,7 +196,7 @@ final class RoutingControlAction extends FeatureAction { // Called whenever an HDMI input of the TV shall become the active input. private boolean maybeChangeActiveInput(int path) { - if (localDevice().getActiveInput() == localDevice().pathToPortId(path)) { + if (localDevice().getActivePortId() == localDevice().pathToPortId(path)) { return false; } // TODO: Remember the currently active input diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java new file mode 100644 index 000000000000..e4d82ef88eaa --- /dev/null +++ b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.hdmi; + +import android.hardware.hdmi.HdmiCec; +import android.hardware.hdmi.HdmiCecMessage; + +import com.android.server.hdmi.HdmiControlService.SendMessageCallback; + +/** + * Action to initiate system audio once AVR is detected on Device discovery action. + */ +final class SystemAudioAutoInitiationAction extends FeatureAction { + private final int mAvrAddress; + + // State that waits for <System Audio Mode Status> once send + // <Give System Audio Mode Status> to AV Receiver. + private static final int STATE_WAITING_FOR_SYSTEM_AUDIO_MODE_STATUS = 1; + + SystemAudioAutoInitiationAction(HdmiCecLocalDevice source, int avrAddress) { + super(source); + mAvrAddress = avrAddress; + } + + @Override + boolean start() { + mState = STATE_WAITING_FOR_SYSTEM_AUDIO_MODE_STATUS; + + addTimer(mState, TIMEOUT_MS); + sendGiveSystemAudioModeStatus(); + return true; + } + + private void sendGiveSystemAudioModeStatus() { + sendCommand(HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(getSourceAddress(), + mAvrAddress), new SendMessageCallback() { + @Override + public void onSendCompleted(int error) { + if (error != HdmiConstants.SEND_RESULT_SUCCESS) { + tv().setSystemAudioMode(false); + finish(); + } + } + }); + } + + @Override + boolean processCommand(HdmiCecMessage cmd) { + if (mState != STATE_WAITING_FOR_SYSTEM_AUDIO_MODE_STATUS) { + return false; + } + + switch (cmd.getOpcode()) { + case HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_STATUS: + handleSystemAudioModeStatusMessage(); + return true; + default: + return false; + } + } + + private void handleSystemAudioModeStatusMessage() { + // If the last setting is system audio, turn on system audio whatever AVR status is. + if (tv().getSystemAudioMode()) { + if (canChangeSystemAudio()) { + addAndStartAction(new SystemAudioActionFromTv(tv(), mAvrAddress, true)); + } + } else { + // If the last setting is non-system audio, turn off system audio mode + // and update system audio status (volume or mute). + tv().setSystemAudioMode(false); + if (canChangeSystemAudio()) { + addAndStartAction(new SystemAudioStatusAction(tv(), mAvrAddress)); + } + } + finish(); + } + + @Override + void handleTimerEvent(int state) { + if (mState != state) { + return; + } + + switch (mState) { + case STATE_WAITING_FOR_SYSTEM_AUDIO_MODE_STATUS: + handleSystemAudioModeStatusTimeout(); + break; + } + } + + private void handleSystemAudioModeStatusTimeout() { + if (tv().getSystemAudioMode()) { + if (canChangeSystemAudio()) { + addAndStartAction(new SystemAudioActionFromTv(tv(), mAvrAddress, true)); + } + } else { + tv().setSystemAudioMode(false); + } + finish(); + } + + private boolean canChangeSystemAudio() { + return tv().canChangeSystemAudio(); + } +} diff --git a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java new file mode 100644 index 000000000000..75e4fefb1f17 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.hdmi; + +import android.hardware.hdmi.HdmiCec; +import android.hardware.hdmi.HdmiCecMessage; +import android.util.Slog; + +import com.android.server.hdmi.HdmiControlService.SendMessageCallback; + +/** + * Action to update audio status (volume or mute) of audio amplifier + */ +// TODO: refactor SystemAudioMode so that it uses this class instead of internal state. +final class SystemAudioStatusAction extends FeatureAction { + private static final String TAG = "SystemAudioStatusAction"; + + // State that waits for <ReportAudioStatus>. + private static final int STATE_WAIT_FOR_REPORT_AUDIO_STATUS = 1; + + private final int mAvrAddress; + + SystemAudioStatusAction(HdmiCecLocalDevice source, int avrAddress) { + super(source); + mAvrAddress = avrAddress; + } + + @Override + boolean start() { + mState = STATE_WAIT_FOR_REPORT_AUDIO_STATUS; + addTimer(mState, TIMEOUT_MS); + sendGiveAudioStatus(); + return true; + } + + private void sendGiveAudioStatus() { + sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(getSourceAddress(), mAvrAddress), + new SendMessageCallback() { + @Override + public void onSendCompleted(int error) { + if (error != HdmiConstants.SEND_RESULT_SUCCESS) { + handleSendGiveAudioStatusFailure(); + } + } + }); + } + + private void handleSendGiveAudioStatusFailure() { + // Inform to all application that the audio status (volumn, mute) of + // the audio amplifier is unknown. + tv().setAudioStatus(false, HdmiConstants.UNKNOWN_VOLUME); + + int uiCommand = tv().getSystemAudioMode() + ? HdmiConstants.UI_COMMAND_RESTORE_VOLUME_FUNCTION // SystemAudioMode: ON + : HdmiConstants.UI_COMMAND_MUTE_FUNCTION; // SystemAudioMode: OFF + sendUserControlPressedAndReleased(uiCommand); + finish(); + } + + private void sendUserControlPressedAndReleased(int uiCommand) { + sendCommand(HdmiCecMessageBuilder.buildUserControlPressed( + getSourceAddress(), mAvrAddress, uiCommand)); + sendCommand(HdmiCecMessageBuilder.buildUserControlReleased( + getSourceAddress(), mAvrAddress)); + } + + @Override + boolean processCommand(HdmiCecMessage cmd) { + if (mState != STATE_WAIT_FOR_REPORT_AUDIO_STATUS) { + return false; + } + + switch (cmd.getOpcode()) { + case HdmiCec.MESSAGE_REPORT_AUDIO_STATUS: + handleReportAudioStatus(cmd); + return true; + } + + return false; + } + + private void handleReportAudioStatus(HdmiCecMessage cmd) { + byte[] params = cmd.getParams(); + if (params.length > 0) { + boolean mute = (params[0] & 0x80) == 0x80; + int volume = params[0] & 0x7F; + tv().setAudioStatus(mute, volume); + + if ((tv().getSystemAudioMode() && mute) || (!tv().getSystemAudioMode() && !mute)) { + // Toggle AVR's mute status to match with the system audio status. + sendUserControlPressedAndReleased(HdmiConstants.UI_COMMAND_MUTE); + } + finish(); + } else { + Slog.e(TAG, "Invalid <Report Audio Status> message:" + cmd); + handleSendGiveAudioStatusFailure(); + return; + } + } + + @Override + void handleTimerEvent(int state) { + if (mState != state) { + return; + } + + handleSendGiveAudioStatusFailure(); + } +} diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java index 4aef2d3142cf..538a2520311b 100644 --- a/services/core/java/com/android/server/job/controllers/BatteryController.java +++ b/services/core/java/com/android/server/job/controllers/BatteryController.java @@ -19,19 +19,16 @@ package com.android.server.job.controllers; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.BatteryManager; -import android.os.BatteryProperty; -import android.os.RemoteException; -import android.os.ServiceManager; +import android.os.BatteryManagerInternal; import android.os.SystemClock; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.BatteryService; +import com.android.server.LocalServices; import com.android.server.job.JobSchedulerService; import com.android.server.job.StateChangedListener; @@ -158,14 +155,10 @@ public class BatteryController extends StateController { mContext.registerReceiver(this, filter); // Initialise tracker state. - BatteryService batteryService = (BatteryService) ServiceManager.getService("battery"); - if (batteryService != null) { - mBatteryHealthy = !batteryService.getBatteryLevelLow(); - mCharging = batteryService.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); - } else { - // Unavailable for some reason, we default to false and let ACTION_BATTERY_[OK,LOW] - // sort it out. - } + BatteryManagerInternal batteryManagerInternal = + LocalServices.getService(BatteryManagerInternal.class); + mBatteryHealthy = !batteryManagerInternal.getBatteryLevelLow(); + mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); } boolean isOnStablePower() { diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index dd3377142364..89ab2aeda45e 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -29,6 +29,7 @@ import android.content.pm.PackageInstallerParams; import android.content.pm.PackageManager; import android.content.pm.PackageParser; import android.content.pm.PackageParser.ApkLite; +import android.content.pm.PackageParser.PackageParserException; import android.content.pm.Signature; import android.os.Build; import android.os.Bundle; @@ -50,14 +51,10 @@ import com.android.internal.content.NativeLibraryHelper; import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; -import libcore.io.IoUtils; import libcore.io.Libcore; -import libcore.io.Streams; import java.io.File; import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; @@ -297,11 +294,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Verify that all staged packages are internally consistent for (File file : files) { - final ApkLite info = PackageParser.parseApkLite(file.getAbsolutePath(), - PackageParser.PARSE_GET_SIGNATURES); - if (info == null) { + final ApkLite info; + try { + info = PackageParser.parseApkLite(file, PackageParser.PARSE_GET_SIGNATURES); + } catch (PackageParserException e) { throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, - "Failed to parse " + file); + "Failed to parse " + file + ": " + e); } if (!seenSplits.add(info.splitName)) { @@ -356,11 +354,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { "Missing existing base package for " + mPackageName); } - final ApkLite info = PackageParser.parseApkLite(app.sourceDir, - PackageParser.PARSE_GET_SIGNATURES); - if (info == null) { + final ApkLite info; + try { + info = PackageParser.parseApkLite(new File(app.sourceDir), + PackageParser.PARSE_GET_SIGNATURES); + } catch (PackageParserException e) { throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, - "Failed to parse existing base " + app.sourceDir); + "Failed to parse existing base " + app.sourceDir + ": " + e); } assertPackageConsistent("Existing base", info.packageName, info.versionCode, diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 8d943963185f..2f40f2ab19e2 100755 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -42,6 +42,7 @@ import com.android.internal.app.ResolverActivity; import com.android.internal.content.NativeLibraryHelper; import com.android.internal.content.NativeLibraryHelper.ApkHandle; import com.android.internal.content.PackageHelper; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; @@ -1247,7 +1248,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } - public static final IPackageManager main(Context context, Installer installer, + public static final PackageManagerService main(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { PackageManagerService m = new PackageManagerService(context, installer, factoryTest, onlyCore); @@ -2242,11 +2243,12 @@ public class PackageManagerService extends IPackageManager.Stub { if ((flags & PackageManager.GET_UNINSTALLED_PACKAGES) == 0) { return null; } + // App code is gone, so we aren't worried about split paths pkg = new PackageParser.Package(packageName); pkg.applicationInfo.packageName = packageName; pkg.applicationInfo.flags = ps.pkgFlags | ApplicationInfo.FLAG_IS_DATA_ONLY; - pkg.applicationInfo.publicSourceDir = ps.resourcePathString; pkg.applicationInfo.sourceDir = ps.codePathString; + pkg.applicationInfo.publicSourceDir = ps.resourcePathString; pkg.applicationInfo.dataDir = getDataPathForPackage(packageName, 0).getPath(); pkg.applicationInfo.nativeLibraryDir = ps.nativeLibraryPathString; @@ -4081,6 +4083,7 @@ public class PackageManagerService extends IPackageManager.Stub { return false; } final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); + // TODO: generate idmap for split APKs if (mInstaller.idmap(pkg.codePath, opkg.codePath, sharedGid) != 0) { Slog.e(TAG, "Failed to generate idmap for " + pkg.codePath + " and " + opkg.codePath); return false; @@ -4196,14 +4199,18 @@ public class PackageManagerService extends IPackageManager.Stub { String scanPath = scanFile.getPath(); if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanPath); parseFlags |= mDefParseFlags; - PackageParser pp = new PackageParser(scanPath); + PackageParser pp = new PackageParser(); pp.setSeparateProcesses(mSeparateProcesses); pp.setOnlyCoreApps(mOnlyCore); + pp.setDisplayMetrics(mMetrics); + + if ((scanMode & SCAN_TRUSTED_OVERLAY) != 0) { + parseFlags |= PackageParser.PARSE_TRUSTED_OVERLAY; + } final PackageParser.Package pkg; try { - pkg = pp.parseMonolithicPackage(scanFile, mMetrics, parseFlags, - (scanMode & SCAN_TRUSTED_OVERLAY) != 0); + pkg = pp.parseMonolithicPackage(scanFile, parseFlags); } catch (PackageParserException e) { mLastScanError = e.error; return null; @@ -4362,23 +4369,30 @@ public class PackageManagerService extends IPackageManager.Stub { } } - String codePath = null; + final String codePath = pkg.codePath; + final String[] splitCodePaths = pkg.splitCodePaths; + String resPath = null; + String[] splitResPaths = null; if ((parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0 && !updatedPkgBetter) { if (ps != null && ps.resourcePathString != null) { resPath = ps.resourcePathString; + splitResPaths = deriveSplitResPaths(pkg.splitCodePaths); } else { // Should not happen at all. Just log an error. Slog.e(TAG, "Resource path not set for pkg : " + pkg.packageName); } } else { resPath = pkg.codePath; + splitResPaths = pkg.splitCodePaths; } - codePath = pkg.codePath; // Set application objects path explicitly. pkg.applicationInfo.sourceDir = codePath; pkg.applicationInfo.publicSourceDir = resPath; + pkg.applicationInfo.splitSourceDirs = splitCodePaths; + pkg.applicationInfo.splitPublicSourceDirs = splitResPaths; + // Note that we invoke the following method only if we are about to unpack an application PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_SIGNATURE, currentTime, user, abiOverride); @@ -4626,52 +4640,51 @@ public class PackageManagerService extends IPackageManager.Stub { } } - boolean performed = false; - if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) { - String path = pkg.codePath; - try { - boolean isDexOptNeededInternal = DexFile.isDexOptNeededInternal(path, - pkg.packageName, - instructionSet, - defer); - // There are three basic cases here: - // 1.) we need to dexopt, either because we are forced or it is needed - // 2.) we are defering a needed dexopt - // 3.) we are skipping an unneeded dexopt - if (forceDex || (!defer && isDexOptNeededInternal)) { - Log.i(TAG, "Running dexopt on: " + pkg.applicationInfo.packageName); - final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); - int ret = mInstaller.dexopt(path, sharedGid, !isForwardLocked(pkg), - pkg.packageName, instructionSet); - // Note that we ran dexopt, since rerunning will - // probably just result in an error again. - pkg.mDexOptNeeded = false; - if (ret < 0) { - return DEX_OPT_FAILED; + if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) { + final Collection<String> paths = pkg.getAllCodePaths(); + for (String path : paths) { + try { + boolean isDexOptNeededInternal = DexFile.isDexOptNeededInternal(path, + pkg.packageName, instructionSet, defer); + // There are three basic cases here: + // 1.) we need to dexopt, either because we are forced or it is needed + // 2.) we are defering a needed dexopt + // 3.) we are skipping an unneeded dexopt + if (forceDex || (!defer && isDexOptNeededInternal)) { + Log.i(TAG, "Running dexopt on: " + pkg.applicationInfo.packageName); + final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); + int ret = mInstaller.dexopt(path, sharedGid, !isForwardLocked(pkg), + pkg.packageName, instructionSet); + // Note that we ran dexopt, since rerunning will + // probably just result in an error again. + pkg.mDexOptNeeded = false; + if (ret < 0) { + return DEX_OPT_FAILED; + } + return DEX_OPT_PERFORMED; } - return DEX_OPT_PERFORMED; - } - if (defer && isDexOptNeededInternal) { - if (mDeferredDexOpt == null) { - mDeferredDexOpt = new HashSet<PackageParser.Package>(); + if (defer && isDexOptNeededInternal) { + if (mDeferredDexOpt == null) { + mDeferredDexOpt = new HashSet<PackageParser.Package>(); + } + mDeferredDexOpt.add(pkg); + return DEX_OPT_DEFERRED; } - mDeferredDexOpt.add(pkg); - return DEX_OPT_DEFERRED; + pkg.mDexOptNeeded = false; + return DEX_OPT_SKIPPED; + } catch (FileNotFoundException e) { + Slog.w(TAG, "Apk not found for dexopt: " + path); + return DEX_OPT_FAILED; + } catch (IOException e) { + Slog.w(TAG, "IOException reading apk: " + path, e); + return DEX_OPT_FAILED; + } catch (StaleDexCacheError e) { + Slog.w(TAG, "StaleDexCacheError when reading apk: " + path, e); + return DEX_OPT_FAILED; + } catch (Exception e) { + Slog.w(TAG, "Exception when doing dexopt : ", e); + return DEX_OPT_FAILED; } - pkg.mDexOptNeeded = false; - return DEX_OPT_SKIPPED; - } catch (FileNotFoundException e) { - Slog.w(TAG, "Apk not found for dexopt: " + path); - return DEX_OPT_FAILED; - } catch (IOException e) { - Slog.w(TAG, "IOException reading apk: " + path, e); - return DEX_OPT_FAILED; - } catch (StaleDexCacheError e) { - Slog.w(TAG, "StaleDexCacheError when reading apk: " + path, e); - return DEX_OPT_FAILED; - } catch (Exception e) { - Slog.w(TAG, "Exception when doing dexopt : ", e); - return DEX_OPT_FAILED; } } return DEX_OPT_SKIPPED; @@ -4818,7 +4831,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } if (p != null) { - usesLibraryFiles.add(p.codePath); + usesLibraryFiles.addAll(p.getAllCodePaths()); } } @@ -4906,7 +4919,7 @@ public class PackageManagerService extends IPackageManager.Stub { private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags, int scanMode, long currentTime, UserHandle user, String abiOverride) { final File scanFile = new File(pkg.codePath); - if (scanFile == null || pkg.applicationInfo.sourceDir == null || + if (pkg.applicationInfo.sourceDir == null || pkg.applicationInfo.publicSourceDir == null) { // Bail out. The resource and code paths haven't been set. Slog.w(TAG, " Code and resource paths haven't been set correctly"); @@ -5355,6 +5368,7 @@ public class PackageManagerService extends IPackageManager.Stub { * only for non-system apps and system app upgrades. */ if (pkg.applicationInfo.nativeLibraryDir != null) { + // TODO: extend to extract native code from split APKs final NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(scanFile); try { // Enable gross and lame hacks for apps that are built with old @@ -5656,7 +5670,8 @@ public class PackageManagerService extends IPackageManager.Stub { try { ksm.addSigningKeySetToPackage(pkg.packageName, pkg.mSigningKeys); if (pkg.mKeySetMapping != null) { - for (Map.Entry<String, Set<PublicKey>> entry : pkg.mKeySetMapping.entrySet()) { + for (Map.Entry<String, ArraySet<PublicKey>> entry : + pkg.mKeySetMapping.entrySet()) { if (entry.getValue() != null) { ksm.addDefinedKeySetToPackage(pkg.packageName, entry.getValue(), entry.getKey()); @@ -5908,6 +5923,8 @@ public class PackageManagerService extends IPackageManager.Stub { a.info.packageName = pkg.applicationInfo.packageName; a.info.sourceDir = pkg.applicationInfo.sourceDir; a.info.publicSourceDir = pkg.applicationInfo.publicSourceDir; + a.info.splitSourceDirs = pkg.applicationInfo.splitSourceDirs; + a.info.splitPublicSourceDirs = pkg.applicationInfo.splitPublicSourceDirs; a.info.dataDir = pkg.applicationInfo.dataDir; a.info.nativeLibraryDir = pkg.applicationInfo.nativeLibraryDir; mInstrumentation.put(a.getComponentName(), a); @@ -9021,6 +9038,10 @@ public class PackageManagerService extends IPackageManager.Stub { abstract boolean doPostDeleteLI(boolean delete); abstract boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException; + String[] getSplitCodePaths() { + return null; + } + /** * Called before the source arguments are copied. This is used mostly * for MoveParams when it needs to read the source file to put it in the @@ -9109,10 +9130,6 @@ public class PackageManagerService extends IPackageManager.Stub { } } - String getCodePath() { - return codeFileName; - } - void createCopyFile() { installDir = isFwdLocked() ? mDrmAppPrivateInstallDir : mAppInstallDir; codeFileName = createTempPackageFile(installDir).getPath(); @@ -9268,10 +9285,6 @@ public class PackageManagerService extends IPackageManager.Stub { return status; } - String getResourcePath() { - return resourceFileName; - } - private String getResourcePathFromCodePath() { final String codePath = getCodePath(); if (isFwdLocked()) { @@ -9302,6 +9315,16 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override + String getCodePath() { + return codeFileName; + } + + @Override + String getResourcePath() { + return resourceFileName; + } + + @Override String getNativeLibraryPath() { if (libraryPath == null) { libraryPath = getLibraryPathFromCodePath(); @@ -9687,7 +9710,7 @@ public class PackageManagerService extends IPackageManager.Stub { return PackageManager.INSTALL_SUCCEEDED; } - }; + } static String getAsecPackageName(String packageCid) { int idx = packageCid.lastIndexOf("-"); @@ -9768,6 +9791,20 @@ public class PackageManagerService extends IPackageManager.Stub { return codePath.substring(sidx+1, eidx); } + private static String[] deriveSplitResPaths(String[] splitCodePaths) { + String[] splitResPaths = null; + if (!ArrayUtils.isEmpty(splitCodePaths)) { + splitResPaths = new String[splitCodePaths.length]; + for (int i = 0; i < splitCodePaths.length; i++) { + final String splitCodePath = splitCodePaths[i]; + final String resName = getApkName(splitCodePath) + ".zip"; + splitResPaths[i] = new File(new File(splitCodePath).getParentFile(), + resName).getAbsolutePath(); + } + } + return splitResPaths; + } + class PackageInstalledInfo { String name; int uid; @@ -10067,6 +10104,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Utility method used to move dex files during install. private int moveDexFilesLI(String oldCodePath, PackageParser.Package newPackage) { + // TODO: extend to move split APK dex files if ((newPackage.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) { final String instructionSet = getAppInstructionSet(newPackage.applicationInfo); int retCode = mInstaller.movedex(oldCodePath, newPackage.codePath, @@ -10165,13 +10203,13 @@ public class PackageManagerService extends IPackageManager.Stub { int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY | (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0) | (onSd ? PackageParser.PARSE_ON_SDCARD : 0); - PackageParser pp = new PackageParser(tmpPackageFile.getPath()); + PackageParser pp = new PackageParser(); pp.setSeparateProcesses(mSeparateProcesses); + pp.setDisplayMetrics(mMetrics); final PackageParser.Package pkg; try { - pkg = pp.parseMonolithicPackage(tmpPackageFile, mMetrics, - parseFlags); + pkg = pp.parseMonolithicPackage(tmpPackageFile, parseFlags); } catch (PackageParserException e) { res.returnCode = e.error; return; @@ -10296,6 +10334,9 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.codePath = args.getCodePath(); pkg.applicationInfo.sourceDir = args.getCodePath(); pkg.applicationInfo.publicSourceDir = args.getResourcePath(); + pkg.applicationInfo.splitSourceDirs = args.getSplitCodePaths(); + pkg.applicationInfo.splitPublicSourceDirs = deriveSplitResPaths( + pkg.applicationInfo.splitSourceDirs); pkg.applicationInfo.nativeLibraryDir = args.getNativeLibraryPath(); if (replace) { replacePackageLI(pkg, parseFlags, scanMode, args.user, diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index b9416576cecc..1839259b6f0a 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -744,7 +744,7 @@ public class UserManagerService extends IUserManager.Stub { writeBoolean(serializer, restrictions, UserManager.ENSURE_VERIFY_APPS); writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_CELL_BROADCASTS); writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS); - writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_APPS); + writeBoolean(serializer, restrictions, UserManager.DISALLOW_APPS_CONTROL); writeBoolean(serializer, restrictions, UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA); writeBoolean(serializer, restrictions, UserManager.DISALLOW_UNMUTE_MICROPHONE); writeBoolean(serializer, restrictions, UserManager.DISALLOW_ADJUST_VOLUME); @@ -896,7 +896,7 @@ public class UserManagerService extends IUserManager.Stub { readBoolean(parser, restrictions, UserManager.ENSURE_VERIFY_APPS); readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_CELL_BROADCASTS); readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS); - readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_APPS); + readBoolean(parser, restrictions, UserManager.DISALLOW_APPS_CONTROL); readBoolean(parser, restrictions, UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA); readBoolean(parser, restrictions, UserManager.DISALLOW_UNMUTE_MICROPHONE); diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index fb4b8f0b69c6..bd80b54f6c10 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -19,10 +19,10 @@ package com.android.server.power; import com.android.internal.app.IAppOpsService; import com.android.internal.app.IBatteryStats; import com.android.internal.os.BackgroundThread; -import com.android.server.BatteryService; import com.android.server.EventLogTags; import com.android.server.LocalServices; import com.android.server.ServiceThread; +import com.android.server.am.BatteryStatsService; import com.android.server.lights.Light; import com.android.server.lights.LightsManager; import com.android.server.Watchdog; @@ -42,6 +42,7 @@ import android.hardware.display.DisplayManagerInternal; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; import android.net.Uri; import android.os.BatteryManager; +import android.os.BatteryManagerInternal; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -163,13 +164,14 @@ public final class PowerManagerService extends com.android.server.SystemService private static final int POWER_HINT_LOW_POWER_MODE = 5; private final Context mContext; + private final ServiceThread mHandlerThread; + private final PowerManagerHandler mHandler; + private LightsManager mLightsManager; - private BatteryService mBatteryService; + private BatteryManagerInternal mBatteryManagerInternal; private DisplayManagerInternal mDisplayManagerInternal; private IBatteryStats mBatteryStats; private IAppOpsService mAppOps; - private ServiceThread mHandlerThread; - private PowerManagerHandler mHandler; private WindowManagerPolicy mPolicy; private Notifier mNotifier; private WirelessChargerDetector mWirelessChargerDetector; @@ -429,6 +431,11 @@ public final class PowerManagerService extends com.android.server.SystemService public PowerManagerService(Context context) { super(context); mContext = context; + mHandlerThread = new ServiceThread(TAG, + Process.THREAD_PRIORITY_DISPLAY, false /*allowIo*/); + mHandlerThread.start(); + mHandler = new PowerManagerHandler(mHandlerThread.getLooper()); + synchronized (mLock) { mWakeLockSuspendBlocker = createSuspendBlockerLocked("PowerManagerService.WakeLocks"); mDisplaySuspendBlocker = createSuspendBlockerLocked("PowerManagerService.Display"); @@ -451,39 +458,19 @@ public final class PowerManagerService extends com.android.server.SystemService public void onStart() { publishBinderService(Context.POWER_SERVICE, new BinderService()); publishLocalService(PowerManagerInternal.class, new LocalService()); - } - - /** - * Initialize the power manager. - * Must be called before any other functions within the power manager are called. - */ - public void init(LightsManager ls, - BatteryService bs, IBatteryStats bss, - IAppOpsService appOps) { - mLightsManager = ls; - mBatteryService = bs; - mBatteryStats = bss; - mAppOps = appOps; - mDisplayManagerInternal = getLocalService(DisplayManagerInternal.class); - mHandlerThread = new ServiceThread(TAG, - Process.THREAD_PRIORITY_DISPLAY, false /*allowIo*/); - mHandlerThread.start(); - mHandler = new PowerManagerHandler(mHandlerThread.getLooper()); Watchdog.getInstance().addMonitor(this); Watchdog.getInstance().addThread(mHandler); } - void setPolicy(WindowManagerPolicy policy) { - synchronized (mLock) { - mPolicy = policy; - } - } - - public void systemReady() { + public void systemReady(IAppOpsService appOps) { synchronized (mLock) { mSystemReady = true; - mDreamManager = LocalServices.getService(DreamManagerInternal.class); + mAppOps = appOps; + mDreamManager = getLocalService(DreamManagerInternal.class); + mDisplayManagerInternal = getLocalService(DisplayManagerInternal.class); + mPolicy = getLocalService(WindowManagerPolicy.class); + mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class); PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mScreenBrightnessSettingMinimum = pm.getMinimumScreenBrightnessSetting(); @@ -494,6 +481,7 @@ public final class PowerManagerService extends com.android.server.SystemService // The notifier runs on the system server's main looper so as not to interfere // with the animations and other critical functions of the power manager. + mBatteryStats = BatteryStatsService.getService(); mNotifier = new Notifier(Looper.getMainLooper(), mContext, mBatteryStats, mAppOps, createSuspendBlockerLocked("PowerManagerService.Broadcasts"), mScreenOnBlocker, mPolicy); @@ -502,6 +490,8 @@ public final class PowerManagerService extends com.android.server.SystemService createSuspendBlockerLocked("PowerManagerService.WirelessChargerDetector"), mHandler); mSettingsObserver = new SettingsObserver(mHandler); + + mLightsManager = getLocalService(LightsManager.class); mAttentionLight = mLightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION); // Initialize display power management. @@ -1168,10 +1158,10 @@ public final class PowerManagerService extends com.android.server.SystemService final boolean wasPowered = mIsPowered; final int oldPlugType = mPlugType; final boolean oldLevelLow = mBatteryLevelLow; - mIsPowered = mBatteryService.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); - mPlugType = mBatteryService.getPlugType(); - mBatteryLevel = mBatteryService.getBatteryLevel(); - mBatteryLevelLow = mBatteryService.getBatteryLevelLow(); + mIsPowered = mBatteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); + mPlugType = mBatteryManagerInternal.getPlugType(); + mBatteryLevel = mBatteryManagerInternal.getBatteryLevel(); + mBatteryLevelLow = mBatteryManagerInternal.getBatteryLevelLow(); if (DEBUG_SPEW) { Slog.d(TAG, "updateIsPoweredLocked: wasPowered=" + wasPowered @@ -1254,7 +1244,7 @@ public final class PowerManagerService extends com.android.server.SystemService final boolean wasStayOn = mStayOn; if (mStayOnWhilePluggedInSetting != 0 && !isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked()) { - mStayOn = mBatteryService.isPowered(mStayOnWhilePluggedInSetting); + mStayOn = mBatteryManagerInternal.isPowered(mStayOnWhilePluggedInSetting); } else { mStayOn = false; } @@ -3076,10 +3066,5 @@ public final class PowerManagerService extends com.android.server.SystemService mLowPowerModeListeners.add(listener); } } - - @Override - public void setPolicy(WindowManagerPolicy policy) { - PowerManagerService.this.setPolicy(policy); - } } } diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java index e34f42bde915..d72ed9e3f73e 100644 --- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java +++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java @@ -116,6 +116,19 @@ class TvInputHardwareManager implements TvInputHal.Callback { } } + private boolean checkUidChangedLocked( + Connection connection, int callingUid, int resolvedUserId) { + Integer connectionCallingUid = connection.getCallingUidLocked(); + Integer connectionResolvedUserId = connection.getResolvedUserIdLocked(); + if (connectionCallingUid == null || connectionResolvedUserId == null) { + return true; + } + if (connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId) { + return true; + } + return false; + } + /** * Create a TvInputHardware object with a specific deviceId. One service at a time can access * the object, and if more than one process attempts to create hardware with the same deviceId, @@ -133,8 +146,7 @@ class TvInputHardwareManager implements TvInputHal.Callback { Slog.e(TAG, "Invalid deviceId : " + deviceId); return null; } - if (connection.getCallingUidLocked() != callingUid - || connection.getResolvedUserIdLocked() != resolvedUserId) { + if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) { TvInputHardwareImpl hardware = new TvInputHardwareImpl(connection.getInfoLocked()); try { callback.asBinder().linkToDeath(connection, 0); @@ -160,8 +172,7 @@ class TvInputHardwareManager implements TvInputHal.Callback { return; } if (connection.getHardwareLocked() != hardware - || connection.getCallingUidLocked() != callingUid - || connection.getResolvedUserIdLocked() != resolvedUserId) { + || checkUidChangedLocked(connection, callingUid, resolvedUserId)) { return; } connection.resetLocked(null, null, null, null); @@ -226,11 +237,11 @@ class TvInputHardwareManager implements TvInputHal.Callback { return mConfigs; } - public int getCallingUidLocked() { + public Integer getCallingUidLocked() { return mCallingUid; } - public int getResolvedUserIdLocked() { + public Integer getResolvedUserIdLocked() { return mResolvedUserId; } diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 10a67c4e421d..cbbcc58854b0 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -19,12 +19,15 @@ package com.android.server.tv; import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.ContentProviderOperation; +import android.content.ContentProviderResult; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.OperationApplicationException; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -66,11 +69,12 @@ import com.android.server.SystemService; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; - import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; /** This class provides a system service that manages television inputs. */ public final class TvInputManagerService extends SystemService { @@ -123,6 +127,44 @@ public final class TvInputManagerService extends SystemService { buildTvInputListLocked(mCurrentUserId); } } + + @Override + public void onPackageRemoved(String packageName, int uid) { + synchronized (mLock) { + UserState userState = getUserStateLocked(mCurrentUserId); + if (!userState.packageList.contains(packageName)) { + // Not a TV input package. + return; + } + } + + ArrayList<ContentProviderOperation> operations = + new ArrayList<ContentProviderOperation>(); + + String selection = TvContract.BaseTvColumns.COLUMN_PACKAGE_NAME + "=?"; + String[] selectionArgs = { packageName }; + + operations.add(ContentProviderOperation.newDelete(TvContract.Channels.CONTENT_URI) + .withSelection(selection, selectionArgs).build()); + operations.add(ContentProviderOperation.newDelete(TvContract.Programs.CONTENT_URI) + .withSelection(selection, selectionArgs).build()); + operations.add(ContentProviderOperation + .newDelete(TvContract.WatchedPrograms.CONTENT_URI) + .withSelection(selection, selectionArgs).build()); + + ContentProviderResult[] results = null; + try { + results = mContentResolver.applyBatch(TvContract.AUTHORITY, operations); + } catch (RemoteException | OperationApplicationException e) { + Slog.e(TAG, "error in applyBatch" + e); + } + + if (DEBUG) { + Slog.d(TAG, "onPackageRemoved(packageName=" + packageName + ", uid=" + uid + + ")"); + Slog.d(TAG, "results=" + results); + } + } }; monitor.register(mContext, null, UserHandle.ALL, true); @@ -145,6 +187,7 @@ public final class TvInputManagerService extends SystemService { private void buildTvInputListLocked(int userId) { UserState userState = getUserStateLocked(userId); userState.inputMap.clear(); + userState.packageList.clear(); if (DEBUG) Slog.d(TAG, "buildTvInputList"); PackageManager pm = mContext.getPackageManager(); @@ -162,6 +205,7 @@ public final class TvInputManagerService extends SystemService { TvInputInfo info = TvInputInfo.createTvInputInfo(mContext, ri); if (DEBUG) Slog.d(TAG, "add " + info.getId()); userState.inputMap.put(info.getId(), info); + userState.packageList.add(si.packageName); } catch (IOException | XmlPullParserException e) { Slog.e(TAG, "Can't load TV input " + si.name, e); } @@ -348,7 +392,7 @@ public final class TvInputManagerService extends SystemService { if (session == null) { removeSessionStateLocked(sessionToken, userId); sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId, - null, null, sessionState.mSeq, userId); + null, null, sessionState.mSeq); } else { try { session.asBinder().linkToDeath(sessionState, 0); @@ -364,7 +408,7 @@ public final class TvInputManagerService extends SystemService { clientState.mSessionTokens.add(sessionState.mSessionToken); sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId, - sessionToken, channels[0], sessionState.mSeq, userId); + sessionToken, channels[0], sessionState.mSeq); } channels[0].dispose(); } @@ -449,13 +493,13 @@ public final class TvInputManagerService extends SystemService { Slog.e(TAG, "error in createSession", e); removeSessionStateLocked(sessionToken, userId); sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId, null, null, - sessionState.mSeq, userId); + sessionState.mSeq); } channels[1].dispose(); } private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId, - IBinder sessionToken, InputChannel channel, int seq, int userId) { + IBinder sessionToken, InputChannel channel, int seq) { try { client.onSessionCreated(inputId, sessionToken, channel, seq); } catch (RemoteException exception) { @@ -672,7 +716,7 @@ public final class TvInputManagerService extends SystemService { } // Send a null token immediately while reconnecting. if (serviceState.mReconnecting == true) { - sendSessionTokenToClientLocked(client, inputId, null, null, seq, userId); + sendSessionTokenToClientLocked(client, inputId, null, null, seq); return; } @@ -784,7 +828,10 @@ public final class TvInputManagerService extends SystemService { } // Create a log entry and fill it later. + String packageName = userState.inputMap.get(sessionState.mInputId) + .getServiceInfo().packageName; ContentValues values = new ContentValues(); + values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName); values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS, currentTime); values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, 0); @@ -931,6 +978,9 @@ public final class TvInputManagerService extends SystemService { // A mapping from the TV input id to its TvInputInfo. private final Map<String, TvInputInfo> inputMap = new HashMap<String,TvInputInfo>(); + // A list of all TV input packages. + private final Set<String> packageList = new HashSet<String>(); + // A mapping from the token of a client to its state. private final Map<IBinder, ClientState> clientStateMap = new HashMap<IBinder, ClientState>(); @@ -1095,8 +1145,7 @@ public final class TvInputManagerService extends SystemService { if (sessionState.mSession == null) { removeSessionStateLocked(sessionToken, sessionState.mUserId); sendSessionTokenToClientLocked(sessionState.mClient, - sessionState.mInputId, null, null, sessionState.mSeq, - sessionState.mUserId); + sessionState.mInputId, null, null, sessionState.mSeq); } } diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 45326f77635b..f1d0585d19bf 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -391,7 +391,7 @@ public class WindowAnimator { final WindowStateAnimator winAnimator = unForceHiding.get(i); winAnimator.setAnimation(a); winAnimator.mAnimationIsEntrance = true; - if (startKeyguardExit) { + if (startKeyguardExit && mKeyguardGoingAway) { // Do one time only. mPolicy.startKeyguardExitAnimation(mCurrentTime + a.getStartOffset(), a.getDuration()); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 2d8a34b6512f..d04d668a8ef0 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -766,6 +766,8 @@ public class WindowManagerService extends IWindowManager.Stub mDisplaySettings = new DisplaySettings(context); mDisplaySettings.readSettingsLocked(); + LocalServices.addService(WindowManagerPolicy.class, mPolicy); + mPointerEventDispatcher = new PointerEventDispatcher(mInputManager.monitorInput(TAG)); mFxSession = new SurfaceSession(); @@ -779,7 +781,6 @@ public class WindowManagerService extends IWindowManager.Stub mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); - mPowerManagerInternal.setPolicy(mPolicy); // TODO: register as local service instead mPowerManagerInternal.registerLowPowerModeObserver( new PowerManagerInternal.LowPowerModeListener() { @Override diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 4574caf20322..5cf571330593 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -195,7 +195,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { = new ArrayList<ActiveAdmin>(); // This is the list of component allowed to start lock task mode. - final List<ComponentName> mLockTaskComponents = new ArrayList<ComponentName>(); + final List<String> mLockTaskPackages = new ArrayList<String>(); ComponentName mRestrictionsProvider; @@ -1014,10 +1014,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.endTag(null, "active-password"); } - for (int i=0; i<policy.mLockTaskComponents.size(); i++) { - ComponentName component = policy.mLockTaskComponents.get(i); + for (int i=0; i<policy.mLockTaskPackages.size(); i++) { + String component = policy.mLockTaskPackages.get(i); out.startTag(null, LOCK_TASK_COMPONENTS_XML); - out.attribute(null, "name", component.flattenToString()); + out.attribute(null, "name", component); out.endTag(null, LOCK_TASK_COMPONENTS_XML); } @@ -1077,7 +1077,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { type = parser.next(); int outerDepth = parser.getDepth(); - policy.mLockTaskComponents.clear(); + policy.mLockTaskPackages.clear(); while ((type=parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { @@ -1131,9 +1131,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { parser.getAttributeValue(null, "nonletter")); XmlUtils.skipCurrentTag(parser); } else if (LOCK_TASK_COMPONENTS_XML.equals(tag)) { - policy.mLockTaskComponents.add - (ComponentName.unflattenFromString - (parser.getAttributeValue(null, "name"))); + policy.mLockTaskPackages.add(parser.getAttributeValue(null, "name")); XmlUtils.skipCurrentTag(parser); } else { Slog.w(LOG_TAG, "Unknown tag: " + tag); @@ -3723,38 +3721,40 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } /** - * Sets which componets may enter lock task mode. + * Sets which packages may enter lock task mode. * * This function can only be called by the device owner or the profile owner. * @param components The list of components allowed to enter lock task mode. */ - public void setLockTaskComponents(ComponentName[] components) throws SecurityException { + public void setLockTaskPackages(String[] packages) throws SecurityException { // Get the package names of the caller. int uid = Binder.getCallingUid(); String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid); - // Check whether any of the package name is the device owner or the profile owner. - for (int i=0; i<packageNames.length; i++) { - String packageName = packageNames[i]; - int userHandle = UserHandle.getUserId(uid); - String profileOwnerPackage = getProfileOwner(userHandle); - if (isDeviceOwner(packageName) || - (profileOwnerPackage != null && profileOwnerPackage.equals(packageName))) { - - // If a package name is the device owner or the profile owner, - // we update the component list. - DevicePolicyData policy = getUserData(userHandle); - policy.mLockTaskComponents.clear(); - if (components != null) { - for (int j=0; j<components.length; j++) { - ComponentName component = components[j]; - policy.mLockTaskComponents.add(component); + synchronized (this) { + // Check whether any of the package name is the device owner or the profile owner. + for (int i=0; i<packageNames.length; i++) { + String packageName = packageNames[i]; + int userHandle = UserHandle.getUserId(uid); + String profileOwnerPackage = getProfileOwner(userHandle); + if (isDeviceOwner(packageName) || + (profileOwnerPackage != null && profileOwnerPackage.equals(packageName))) { + + // If a package name is the device owner or the profile owner, + // we update the component list. + DevicePolicyData policy = getUserData(userHandle); + policy.mLockTaskPackages.clear(); + if (packages != null) { + for (int j = 0; j < packages.length; j++) { + String pkg = packages[j]; + policy.mLockTaskPackages.add(pkg); + } } - } - // Store the settings persistently. - saveSettingsLocked(userHandle); - return; + // Store the settings persistently. + saveSettingsLocked(userHandle); + return; + } } } throw new SecurityException(); @@ -3763,36 +3763,61 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { /** * This function returns the list of components allowed to start the task lock mode. */ - public ComponentName[] getLockTaskComponents() { - int userHandle = UserHandle.USER_OWNER; - DevicePolicyData policy = getUserData(userHandle); - ComponentName[] tempArray = policy.mLockTaskComponents.toArray(new ComponentName[0]); - return tempArray; + public String[] getLockTaskPackages() { + synchronized (this) { + int userHandle = UserHandle.USER_OWNER; + DevicePolicyData policy = getUserData(userHandle); + return policy.mLockTaskPackages.toArray(new String[0]); + } } /** - * This function lets the caller know whether the given component is allowed to start the + * This function lets the caller know whether the given package is allowed to start the * lock task mode. - * @param component The component to check + * @param pkg The package to check */ - public boolean isLockTaskPermitted(ComponentName component) { + public boolean isLockTaskPermitted(String pkg) { // Get current user's devicepolicy int uid = Binder.getCallingUid(); int userHandle = UserHandle.getUserId(uid); DevicePolicyData policy = getUserData(userHandle); - for (int i=0; i<policy.mLockTaskComponents.size(); i++) { - ComponentName lockTaskComponent = policy.mLockTaskComponents.get(i); + synchronized (this) { + for (int i = 0; i < policy.mLockTaskPackages.size(); i++) { + String lockTaskPackage = policy.mLockTaskPackages.get(i); - // If the given component equals one of the component stored our device-owner-set - // list, we allow this component to start the lock task mode. - if (lockTaskComponent.getPackageName().equals(component.getPackageName())) { - return true; + // If the given package equals one of the packages stored our list, + // we allow this package to start lock task mode. + if (lockTaskPackage.equals(pkg)) { + return true; + } } } return false; } @Override + public void notifyLockTaskModeChanged(boolean isEnabled, String pkg, int userHandle) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("notifyLockTaskModeChanged can only be called by system"); + } + synchronized (this) { + final DevicePolicyData policy = getUserData(userHandle); + Bundle adminExtras = new Bundle(); + adminExtras.putBoolean(DeviceAdminReceiver.EXTRA_LOCK_TASK_ENTERING, isEnabled); + adminExtras.putString(DeviceAdminReceiver.EXTRA_LOCK_TASK_PACKAGE, pkg); + for (ActiveAdmin admin : policy.mAdminList) { + boolean ownsDevice = isDeviceOwner(admin.info.getPackageName()); + boolean ownsProfile = (getProfileOwner(userHandle) != null + && getProfileOwner(userHandle).equals(admin.info.getPackageName())); + if (ownsDevice || ownsProfile) { + sendAdminCommandLocked(admin, DeviceAdminReceiver.ACTION_LOCK_TASK_CHANGED, + adminExtras, null); + } + } + } + } + + @Override public void setGlobalSetting(ComponentName who, String setting, String value) { final ContentResolver contentResolver = mContext.getContentResolver(); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index f9465956567d..0d05c5fe7fa6 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -44,7 +44,6 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.service.dreams.DreamService; -import android.service.fingerprint.FingerprintManager; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; @@ -148,8 +147,13 @@ public final class SystemServer { private PowerManagerService mPowerManagerService; private ActivityManagerService mActivityManagerService; private DisplayManagerService mDisplayManagerService; + private PackageManagerService mPackageManagerService; + private PackageManager mPackageManager; private ContentResolver mContentResolver; + private boolean mOnlyCore; + private boolean mFirstBoot; + /** * Called to initialize native system services. */ @@ -163,6 +167,7 @@ public final class SystemServer { } public SystemServer() { + // Check for factory test mode. mFactoryTestMode = FactoryTest.getMode(); } @@ -245,7 +250,7 @@ public final class SystemServer { startBootstrapServices(); startCoreServices(); startOtherServices(); - } catch (RuntimeException ex) { + } catch (Throwable ex) { Slog.e("System", "******************************************"); Slog.e("System", "************ Failure starting system services", ex); throw ex; @@ -289,36 +294,84 @@ public final class SystemServer { mSystemContext.setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar); } + /** + * Starts the small tangle of critical services that are needed to get + * the system off the ground. These services have complex mutual dependencies + * which is why we initialize them all in one place here. Unless your service + * is also entwined in these dependencies, it should be initialized in one of + * the other functions. + */ private void startBootstrapServices() { // Wait for installd to finish starting up so that it has a chance to // create critical directories such as /data/user with the appropriate // permissions. We need this to complete before we initialize other services. mInstaller = mSystemServiceManager.startService(Installer.class); - // Power manager needs to be started early because other services need it. - // TODO: The conversion to the new pattern is incomplete. We need to switch - // the power manager's dependencies over then we can use boot phases to arrange - // initialization order and remove the mPowerManagerService field. - mPowerManagerService = mSystemServiceManager.startService(PowerManagerService.class); - // Activity manager runs the show. mActivityManagerService = mSystemServiceManager.startService( ActivityManagerService.Lifecycle.class).getService(); mActivityManagerService.setSystemServiceManager(mSystemServiceManager); - } - private void startCoreServices() { + // Power manager needs to be started early because other services need it. + // Native daemons may be watching for it to be registered so it must be ready + // to handle incoming binder calls immediately (including being able to verify + // the permissions for those calls). + mPowerManagerService = mSystemServiceManager.startService(PowerManagerService.class); + + // Now that the power manager has been started, let the activity manager + // initialize power management features. + mActivityManagerService.initPowerManagement(); + // Display manager is needed to provide display metrics before package manager // starts up. mDisplayManagerService = mSystemServiceManager.startService(DisplayManagerService.class); + + // We need the default display before we can initialize the package manager. + mSystemServiceManager.startBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY); + + // Only run "core" apps if we're encrypting the device. + String cryptState = SystemProperties.get("vold.decrypt"); + if (ENCRYPTING_STATE.equals(cryptState)) { + Slog.w(TAG, "Detected encryption in progress - only parsing core apps"); + mOnlyCore = true; + } else if (ENCRYPTED_STATE.equals(cryptState)) { + Slog.w(TAG, "Device encrypted - only parsing core apps"); + mOnlyCore = true; + } + + // Start the package manager. + Slog.i(TAG, "Package Manager"); + mPackageManagerService = PackageManagerService.main(mSystemContext, mInstaller, + mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore); + mFirstBoot = mPackageManagerService.isFirstBoot(); + mPackageManager = mSystemContext.getPackageManager(); + + // Initialize attribute cache used to cache resources from packages. + AttributeCache.init(mSystemContext); + + // Set up the Application instance for the system process and get started. + mActivityManagerService.setSystemProcess(); + } + + /** + * Starts some essential services that are not tangled up in the bootstrap process. + */ + private void startCoreServices() { + // Manages LEDs and display backlight. + mSystemServiceManager.startService(LightsService.class); + + // Tracks the battery level. Requires LightService. + mSystemServiceManager.startService(BatteryService.class); } + /** + * Starts a miscellaneous grab bag of stuff that has yet to be refactored + * and organized. + */ private void startOtherServices() { final Context context = mSystemContext; AccountManagerService accountManager = null; ContentService contentService = null; - LightsManager lights = null; - BatteryService battery = null; VibratorService vibrator = null; IAlarmManager alarm = null; MountService mountService = null; @@ -328,7 +381,6 @@ public final class SystemServer { ConnectivityService connectivity = null; NetworkScoreService networkScore = null; NsdService serviceDiscovery= null; - IPackageManager pm = null; WindowManagerService wm = null; BluetoothManagerService bluetooth = null; UsbService usb = null; @@ -341,8 +393,6 @@ public final class SystemServer { ConsumerIrService consumerIr = null; AudioService audioService = null; - boolean onlyCore = false; - boolean firstBoot = false; boolean disableStorage = SystemProperties.getBoolean("config.disable_storage", false); boolean disableMedia = SystemProperties.getBoolean("config.disable_media", false); boolean disableBluetooth = SystemProperties.getBoolean("config.disable_bluetooth", false); @@ -354,38 +404,12 @@ public final class SystemServer { boolean isEmulator = SystemProperties.get("ro.kernel.qemu").equals("1"); try { - Slog.i(TAG, "Telephony Registry"); - telephonyRegistry = new TelephonyRegistry(context); - ServiceManager.addService("telephony.registry", telephonyRegistry); - Slog.i(TAG, "Scheduling Policy"); ServiceManager.addService("scheduling_policy", new SchedulingPolicyService()); - AttributeCache.init(context); - - // We need the default display before we can initialize the package manager. - mSystemServiceManager.startBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY); - - Slog.i(TAG, "Package Manager"); - // Only run "core" apps if we're encrypting the device. - String cryptState = SystemProperties.get("vold.decrypt"); - if (ENCRYPTING_STATE.equals(cryptState)) { - Slog.w(TAG, "Detected encryption in progress - only parsing core apps"); - onlyCore = true; - } else if (ENCRYPTED_STATE.equals(cryptState)) { - Slog.w(TAG, "Device encrypted - only parsing core apps"); - onlyCore = true; - } - - pm = PackageManagerService.main(context, mInstaller, - mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, - onlyCore); - try { - firstBoot = pm.isFirstBoot(); - } catch (RemoteException e) { - } - - mActivityManagerService.setSystemProcess(); + Slog.i(TAG, "Telephony Registry"); + telephonyRegistry = new TelephonyRegistry(context); + ServiceManager.addService("telephony.registry", telephonyRegistry); Slog.i(TAG, "Entropy Mixer"); ServiceManager.addService("entropy", new EntropyMixer(context)); @@ -413,24 +437,10 @@ public final class SystemServer { Slog.i(TAG, "System Content Providers"); mActivityManagerService.installSystemProviders(); - mSystemServiceManager.startService(LightsService.class); - lights = LocalServices.getService(LightsManager.class); - - Slog.i(TAG, "Battery Service"); - battery = new BatteryService(context, lights); - ServiceManager.addService("battery", battery); - Slog.i(TAG, "Vibrator Service"); vibrator = new VibratorService(context); ServiceManager.addService("vibrator", vibrator); - // TODO: use boot phase - // only initialize the power service after we have started the - // lights service, content providers and the battery service. - mPowerManagerService.init(lights, battery, - BatteryStatsService.getService(), - mActivityManagerService.getAppOpsService()); - Slog.i(TAG, "Consumer IR Service"); consumerIr = new ConsumerIrService(context); ServiceManager.addService(Context.CONSUMER_IR_SERVICE, consumerIr); @@ -449,7 +459,7 @@ public final class SystemServer { Slog.i(TAG, "Window Manager"); wm = WindowManagerService.main(context, inputManager, mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL, - !firstBoot, onlyCore); + !mFirstBoot, mOnlyCore); ServiceManager.addService(Context.WINDOW_SERVICE, wm); ServiceManager.addService(Context.INPUT_SERVICE, inputManager); @@ -523,7 +533,7 @@ public final class SystemServer { } try { - pm.performBootDexOpt(); + mPackageManagerService.performBootDexOpt(); } catch (Throwable e) { reportWtf("performing boot dexopt", e); } @@ -561,13 +571,9 @@ public final class SystemServer { reportWtf("starting LockSettingsService service", e); } - try { - // Always start the Device Policy Manager, so that the API is compatible with - // API8. - mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class); - } catch (Throwable e) { - reportWtf("starting DevicePolicyService", e); - } + // Always start the Device Policy Manager, so that the API is compatible with + // API8. + mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class); } if (!disableSystemUI) { @@ -638,39 +644,17 @@ public final class SystemServer { reportWtf("starting NetworkPolicy Service", e); } - try { - mSystemServiceManager.startService(WIFI_P2P_SERVICE_CLASS); - } catch (Throwable e) { - reportWtf("starting Wi-Fi P2pService", e); - } + mSystemServiceManager.startService(WIFI_P2P_SERVICE_CLASS); - try { - mSystemServiceManager.startService(WIFI_PASSPOINT_SERVICE_CLASS); - } catch (Throwable e) { - reportWtf("starting Wi-Fi PasspointService", e); - } + mSystemServiceManager.startService(WIFI_PASSPOINT_SERVICE_CLASS); - try { - mSystemServiceManager.startService(WIFI_SERVICE_CLASS); - } catch (Throwable e) { - reportWtf("starting Wi-Fi Service", e); - } + mSystemServiceManager.startService(WIFI_SERVICE_CLASS); - try { - Slog.i(TAG, "Wi-Fi Scanning Service"); - mSystemServiceManager.startService( + mSystemServiceManager.startService( "com.android.server.wifi.WifiScanningService"); - } catch (Throwable e) { - reportWtf("starting Wi-Fi Scanning Service", e); - } - if (!isEmulator) { - try { - mSystemServiceManager.startService(ETHERNET_SERVICE_CLASS); - } catch (Throwable e) { - reportWtf("starting Ethernet Service", e); - } + mSystemServiceManager.startService(ETHERNET_SERVICE_CLASS); } else { // Don't start the Ethernet service on the emulator because // it interferes with qemu's SLIRP emulation, which uses @@ -714,7 +698,7 @@ public final class SystemServer { * AppWidget Provider. Make sure MountService is completely started * first before continuing. */ - if (mountService != null && !onlyCore) { + if (mountService != null && !mOnlyCore) { mountService.waitForAsecScan(); } @@ -812,14 +796,11 @@ public final class SystemServer { } if (!disableNonCoreServices) { - try { - if (pm.hasSystemFeature(PackageManager.FEATURE_USB_HOST) || - pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY)) { - // Manage USB host and device support - mSystemServiceManager.startService(USB_SERVICE_CLASS); - } - } catch (Throwable e) { - reportWtf("starting UsbService", e); + if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST) + || mPackageManager.hasSystemFeature( + PackageManager.FEATURE_USB_ACCESSORY)) { + // Manage USB host and device support + mSystemServiceManager.startService(USB_SERVICE_CLASS); } try { @@ -839,20 +820,12 @@ public final class SystemServer { mSystemServiceManager.startService(JobSchedulerService.class); if (!disableNonCoreServices) { - try { - if (pm.hasSystemFeature(PackageManager.FEATURE_BACKUP)) { - mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS); - } - } catch (Throwable e) { - Slog.e(TAG, "Failure starting Backup Service", e); + if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BACKUP)) { + mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS); } - try { - if (pm.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)) { - mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS); - } - } catch (Throwable e) { - reportWtf("starting AppWidget Service", e); + if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)) { + mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS); } try { @@ -862,13 +835,8 @@ public final class SystemServer { reportWtf("starting Recognition Service", e); } - try { - if (pm.hasSystemFeature(PackageManager.FEATURE_VOICE_RECOGNIZERS)) { - Slog.i(TAG, "Voice Recognition Service"); - mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS); - } - } catch (Throwable e) { - reportWtf("starting Voice Recognition Service", e); + if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_VOICE_RECOGNIZERS)) { + mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS); } } @@ -934,38 +902,17 @@ public final class SystemServer { } } - try { - if (pm.hasSystemFeature(PackageManager.FEATURE_PRINTING)) { - mSystemServiceManager.startService(PRINT_MANAGER_SERVICE_CLASS); - } - } catch (Throwable e) { - reportWtf("starting Print Service", e); + if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PRINTING)) { + mSystemServiceManager.startService(PRINT_MANAGER_SERVICE_CLASS); } - try { - mSystemServiceManager.startService(RestrictionsManagerService.class); - } catch (Throwable e) { - reportWtf("starting RestrictionsManagerService", e); - } + mSystemServiceManager.startService(RestrictionsManagerService.class); - try { - mSystemServiceManager.startService(MediaSessionService.class); - } catch (Throwable e) { - reportWtf("starting MediaSessionService", e); - } + mSystemServiceManager.startService(MediaSessionService.class); - try { - mSystemServiceManager.startService(HdmiControlService.class); - } catch (Throwable e) { - reportWtf("starting HdmiControlService", e); - } + mSystemServiceManager.startService(HdmiControlService.class); - try { - Slog.i(TAG, "TvInputManagerService"); - mSystemServiceManager.startService(TvInputManagerService.class); - } catch (Throwable e) { - reportWtf("starting TvInputManagerService", e); - } + mSystemServiceManager.startService(TvInputManagerService.class); if (!disableNonCoreServices) { try { @@ -976,19 +923,9 @@ public final class SystemServer { reportWtf("starting MediaRouterService", e); } - try { - Slog.i(TAG, "Trust Manager"); - mSystemServiceManager.startService(TrustManagerService.class); - } catch (Throwable e) { - Slog.e(TAG, "Failure starting TrustManagerService", e); - } + mSystemServiceManager.startService(TrustManagerService.class); - try { - Slog.i(TAG, "Fingerprint Manager"); - mSystemServiceManager.startService(FingerprintService.class); - } catch (Throwable e) { - Slog.e(TAG, "Failure starting FingerprintService", e); - } + mSystemServiceManager.startService(FingerprintService.class); try { Slog.i(TAG, "BackgroundDexOptService"); @@ -999,12 +936,7 @@ public final class SystemServer { } - try { - Slog.i(TAG, "LauncherAppsService"); - mSystemServiceManager.startService(LauncherAppsService.class); - } catch (Throwable t) { - reportWtf("starting LauncherAppsService", t); - } + mSystemServiceManager.startService(LauncherAppsService.class); } // Before things start rolling, be sure we have decided whether @@ -1061,27 +993,26 @@ public final class SystemServer { try { // TODO: use boot phase - mPowerManagerService.systemReady(); + mPowerManagerService.systemReady(mActivityManagerService.getAppOpsService()); } catch (Throwable e) { reportWtf("making Power Manager Service ready", e); } try { - pm.systemReady(); + mPackageManagerService.systemReady(); } catch (Throwable e) { reportWtf("making Package Manager Service ready", e); } try { // TODO: use boot phase and communicate these flags some other way - mDisplayManagerService.systemReady(safeMode, onlyCore); + mDisplayManagerService.systemReady(safeMode, mOnlyCore); } catch (Throwable e) { reportWtf("making Display Manager Service ready", e); } // These are needed to propagate to the runnable below. final MountService mountServiceF = mountService; - final BatteryService batteryF = battery; final NetworkManagementService networkManagementF = networkManagement; final NetworkStatsService networkStatsF = networkStats; final NetworkPolicyManagerService networkPolicyF = networkPolicy; @@ -1130,11 +1061,6 @@ public final class SystemServer { reportWtf("making Mount Service ready", e); } try { - if (batteryF != null) batteryF.systemReady(); - } catch (Throwable e) { - reportWtf("making Battery Service ready", e); - } - try { if (networkScoreF != null) networkScoreF.systemReady(); } catch (Throwable e) { reportWtf("making Network Score Service ready", e); diff --git a/telephony/java/android/telephony/SubInfoRecord.java b/telephony/java/android/telephony/SubInfoRecord.java index 670def798d72..ced8e2f72665 100644 --- a/telephony/java/android/telephony/SubInfoRecord.java +++ b/telephony/java/android/telephony/SubInfoRecord.java @@ -105,4 +105,11 @@ public class SubInfoRecord implements Parcelable { return 0; } + public String toString() { + return "{mSubId=" + mSubId + ", mIccId=" + mIccId + " mSlotId=" + mSlotId + + " mDisplayName=" + mDisplayName + " mNameSource=" + mNameSource + + " mColor=" + mColor + " mNumber=" + mNumber + + " mDispalyNumberFormat=" + mDispalyNumberFormat + " mDataRoaming=" + mDataRoaming + + " mSimIconRes=" + mSimIconRes + "}"; + } } diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 859a890b7236..79e9fd5c4c38 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -697,12 +697,16 @@ public class SubscriptionManager implements BaseColumns { public static void putPhoneIdAndSubIdExtra(Intent intent, int phoneId) { long [] subId = SubscriptionManager.getSubId(phoneId); if ((subId != null) && (subId.length >= 1)) { - if (VDBG) logd("putPhoneIdAndSubIdExtra: phoneId=" + phoneId + " subId=" + subId); - intent.putExtra(PhoneConstants.SLOT_KEY, phoneId); //FIXME: RENAME TO PHONE_ID_KEY ?? - intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId[0]); + putPhoneIdAndSubIdExtra(intent, phoneId, subId[0]); } else { logd("putPhoneIdAndSubIdExtra: no valid subs"); } } + + public static void putPhoneIdAndSubIdExtra(Intent intent, int phoneId, long subId) { + if (VDBG) logd("putPhoneIdAndSubIdExtra: phoneId=" + phoneId + " subId=" + subId); + intent.putExtra(PhoneConstants.SLOT_KEY, phoneId); //FIXME: RENAME TO PHONE_ID_KEY ?? + intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId); + } } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 3fde36e9b666..124a8ec6cf69 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -3035,28 +3035,6 @@ public class TelephonyManager { /** @hide */ @SystemApi - public int enableApnType(String type) { - try { - return getITelephony().enableApnType(type); - } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelephony#enableApnType", e); - } - return PhoneConstants.APN_REQUEST_FAILED; - } - - /** @hide */ - @SystemApi - public int disableApnType(String type) { - try { - return getITelephony().disableApnType(type); - } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelephony#disableApnType", e); - } - return PhoneConstants.APN_REQUEST_FAILED; - } - - /** @hide */ - @SystemApi public boolean enableDataConnectivity() { try { return getITelephony().enableDataConnectivity(); diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 407da87292b8..beee6167e422 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -310,27 +310,6 @@ interface ITelephony { */ void disableLocationUpdatesUsingSubId(long subId); - - /** - * Enable a specific APN type. - */ - int enableApnType(String type); - - /** - * Disable a specific APN type. - */ - int disableApnType(String type); - - /** - * Enable a specific APN type with subscription. - */ - int enableApnTypeUsingSub(long subId, String type); - - /** - * Disable a specific APN type with subscription. - */ - int disableApnTypeUsingSub(long subId, String type); - /** * Allow mobile data connections. */ diff --git a/tests/VectorDrawableTest/AndroidManifest.xml b/tests/VectorDrawableTest/AndroidManifest.xml index db2efc391463..890214f2dc03 100644 --- a/tests/VectorDrawableTest/AndroidManifest.xml +++ b/tests/VectorDrawableTest/AndroidManifest.xml @@ -33,6 +33,16 @@ </intent-filter> </activity> <activity + android:name="BitmapDrawableDupe" + android:label="Bitmap Performance of clones" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="com.android.test.dynamic.TEST" /> + </intent-filter> + + </activity> + <activity android:name="VectorDrawableAnimation" android:label="VectorTestAnimation" > <intent-filter> diff --git a/tests/VectorDrawableTest/res/drawable-nodpi/bitmap_drawable01.jpg b/tests/VectorDrawableTest/res/drawable-nodpi/bitmap_drawable01.jpg Binary files differnew file mode 100644 index 000000000000..dc8c19716be5 --- /dev/null +++ b/tests/VectorDrawableTest/res/drawable-nodpi/bitmap_drawable01.jpg diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/BitmapDrawableDupe.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/BitmapDrawableDupe.java new file mode 100644 index 000000000000..36c8f2b4adf2 --- /dev/null +++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/BitmapDrawableDupe.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.test.dynamic; + +import android.app.Activity; +import android.content.res.Resources; +import android.graphics.drawable.BitmapDrawable; +import android.os.Bundle; +import android.widget.TextView; +import android.widget.Button; +import android.widget.GridLayout; +import android.widget.ScrollView; + +import java.text.DecimalFormat; + +@SuppressWarnings({"UnusedDeclaration"}) +public class BitmapDrawableDupe extends Activity { + private static final String LOGCAT = "VectorDrawable1"; + protected int[] icon = { + R.drawable.bitmap_drawable01, + R.drawable.bitmap_drawable01, + R.drawable.bitmap_drawable01, + R.drawable.bitmap_drawable01, + R.drawable.bitmap_drawable01, + R.drawable.bitmap_drawable01, + R.drawable.bitmap_drawable01, + R.drawable.bitmap_drawable01, + R.drawable.bitmap_drawable01, + R.drawable.bitmap_drawable01, + R.drawable.bitmap_drawable01, + R.drawable.bitmap_drawable01, + R.drawable.bitmap_drawable01, + R.drawable.bitmap_drawable01, + R.drawable.bitmap_drawable01, + R.drawable.bitmap_drawable01, + R.drawable.bitmap_drawable01, + R.drawable.bitmap_drawable01, + R.drawable.bitmap_drawable01, + R.drawable.bitmap_drawable01, + R.drawable.bitmap_drawable01, + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ScrollView scrollView = new ScrollView(this); + GridLayout container = new GridLayout(this); + scrollView.addView(container); + container.setColumnCount(5); + container.setBackgroundColor(0xFF888888); + + DecimalFormat df = new DecimalFormat("#.##"); + long time = android.os.SystemClock.elapsedRealtimeNanos(); + for (int i = 0; i < icon.length; i++) { + Button button = new Button(this); + button.setWidth(200); + button.setBackgroundResource(icon[i]); + container.addView(button); + } + + setContentView(scrollView); + time = android.os.SystemClock.elapsedRealtimeNanos()-time; + TextView t = new TextView(this); + t.setText("avgS=" + df.format(time / (icon.length * 1000000.)) + " ms"); + container.addView(t); + } +} diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index f10904c871f4..3d93bbe62f67 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -589,11 +589,11 @@ static bool applyFileOverlay(Bundle *bundle, if (bundle->getVerbose()) { printf("trying overlaySet Key=%s\n",overlaySet->keyAt(overlayIndex).string()); } - size_t baseIndex = UNKNOWN_ERROR; + ssize_t baseIndex = -1; if (baseSet->get() != NULL) { baseIndex = (*baseSet)->indexOfKey(overlaySet->keyAt(overlayIndex)); } - if (baseIndex < UNKNOWN_ERROR) { + if (baseIndex >= 0) { // look for same flavor. For a given file (strings.xml, for example) // there may be a locale specific or other flavors - we want to match // the same flavor. @@ -619,10 +619,10 @@ static bool applyFileOverlay(Bundle *bundle, for (size_t overlayGroupIndex = 0; overlayGroupIndex<overlayGroupSize; overlayGroupIndex++) { - size_t baseFileIndex = + ssize_t baseFileIndex = baseGroup->getFiles().indexOfKey(overlayFiles. keyAt(overlayGroupIndex)); - if (baseFileIndex < UNKNOWN_ERROR) { + if (baseFileIndex >= 0) { if (bundle->getVerbose()) { printf("found a match (" ZD ") for overlay file %s, for flavor %s\n", (ZD_TYPE) baseFileIndex, @@ -1363,7 +1363,11 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil if (split->isBase()) { resFile = flattenedTable; - finalResTable.add(flattenedTable->getData(), flattenedTable->getSize()); + err = finalResTable.add(flattenedTable->getData(), flattenedTable->getSize()); + if (err != NO_ERROR) { + fprintf(stderr, "Generated resource table is corrupt.\n"); + return err; + } } else { sp<AaptFile> generatedManifest = new AaptFile(String8("AndroidManifest.xml"), AaptGroupEntry(), String8()); diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index efbba40f950d..1a9f1b95dada 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -2292,8 +2292,14 @@ uint32_t ResourceTable::getCustomResourceWithCreation( if (resId != 0 || !createIfNotFound) { return resId; } - String16 value("false"); + if (mAssetsPackage != package) { + mCurrentXmlPos.warning("creating resource for external package %s: %s/%s.", + String8(package).string(), String8(type).string(), String8(name).string()); + mCurrentXmlPos.printf("This will be an error in a future version of AAPT."); + } + + String16 value("false"); status_t status = addEntry(mCurrentXmlPos, package, type, name, value, NULL, NULL, true); if (status == NO_ERROR) { resId = getResId(package, type, name); @@ -3062,8 +3068,9 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& for (size_t i = 0; i < N; ++i) { if (!validResources[i]) { sp<ConfigList> c = t->getOrderedConfigs().itemAt(i); - fprintf(stderr, "%s: no entries written for %s/%s\n", log_prefix, - String8(typeName).string(), String8(c->getName()).string()); + fprintf(stderr, "%s: no entries written for %s/%s (0x%08x)\n", log_prefix, + String8(typeName).string(), String8(c->getName()).string(), + Res_MAKEID(p->getAssignedId() - 1, ti, i)); missing_entry = true; } } |