Merge "Support default, pattern, pin and password encryption types"
diff --git a/api/current.txt b/api/current.txt
index 3ea1aad..0d1cc97 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -30255,8 +30255,13 @@
public static final class AccessibilityNodeInfo.CollectionInfo {
method public int getColumnCount();
method public int getRowCount();
+ method public int getSelectionMode();
method public boolean isHierarchical();
method public static android.view.accessibility.AccessibilityNodeInfo.CollectionInfo obtain(int, int, boolean);
+ method public static android.view.accessibility.AccessibilityNodeInfo.CollectionInfo obtain(int, int, boolean, int);
+ field public static final int SELECTION_MODE_MULTIPLE = 2; // 0x2
+ field public static final int SELECTION_MODE_NONE = 0; // 0x0
+ field public static final int SELECTION_MODE_SINGLE = 1; // 0x1
}
public static final class AccessibilityNodeInfo.CollectionItemInfo {
@@ -30265,7 +30270,9 @@
method public int getRowIndex();
method public int getRowSpan();
method public boolean isHeading();
+ method public boolean isSelected();
method public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean);
+ method public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean, boolean);
}
public static final class AccessibilityNodeInfo.RangeInfo {
@@ -31628,12 +31635,10 @@
method public int getCheckedItemPosition();
method public android.util.SparseBooleanArray getCheckedItemPositions();
method public int getChoiceMode();
- method public int getFirstPositionForRow(int);
method public int getListPaddingBottom();
method public int getListPaddingLeft();
method public int getListPaddingRight();
method public int getListPaddingTop();
- method public int getRowForPosition(int);
method public android.view.View getSelectedView();
method public android.graphics.drawable.Drawable getSelector();
method public java.lang.CharSequence getTextFilter();
diff --git a/cmds/idmap/Android.mk b/cmds/idmap/Android.mk
new file mode 100644
index 0000000..ffa83f2
--- /dev/null
+++ b/cmds/idmap/Android.mk
@@ -0,0 +1,28 @@
+# Copyright (C) 2012 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := idmap.cpp create.cpp scan.cpp inspect.cpp
+
+LOCAL_SHARED_LIBRARIES := liblog libutils libandroidfw
+
+LOCAL_MODULE := idmap
+
+LOCAL_C_INCLUDES := external/zlib
+
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_EXECUTABLE)
diff --git a/cmds/idmap/create.cpp b/cmds/idmap/create.cpp
new file mode 100644
index 0000000..ae35f7b
--- /dev/null
+++ b/cmds/idmap/create.cpp
@@ -0,0 +1,222 @@
+#include "idmap.h"
+
+#include <UniquePtr.h>
+#include <androidfw/AssetManager.h>
+#include <androidfw/ResourceTypes.h>
+#include <androidfw/ZipFileRO.h>
+#include <utils/String8.h>
+
+#include <fcntl.h>
+#include <sys/stat.h>
+
+using namespace android;
+
+namespace {
+ int get_zip_entry_crc(const char *zip_path, const char *entry_name, uint32_t *crc)
+ {
+ UniquePtr<ZipFileRO> zip(ZipFileRO::open(zip_path));
+ if (zip.get() == NULL) {
+ return -1;
+ }
+ ZipEntryRO entry = zip->findEntryByName(entry_name);
+ if (entry == NULL) {
+ return -1;
+ }
+ if (!zip->getEntryInfo(entry, NULL, NULL, NULL, NULL, NULL, (long*)crc)) {
+ return -1;
+ }
+ zip->releaseEntry(entry);
+ return 0;
+ }
+
+ int open_idmap(const char *path)
+ {
+ int fd = TEMP_FAILURE_RETRY(open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644));
+ if (fd == -1) {
+ ALOGD("error: open %s: %s\n", path, strerror(errno));
+ goto fail;
+ }
+ if (fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0) {
+ ALOGD("error: fchmod %s: %s\n", path, strerror(errno));
+ goto fail;
+ }
+ if (TEMP_FAILURE_RETRY(flock(fd, LOCK_EX | LOCK_NB)) != 0) {
+ ALOGD("error: flock %s: %s\n", path, strerror(errno));
+ goto fail;
+ }
+
+ return fd;
+fail:
+ if (fd != -1) {
+ close(fd);
+ unlink(path);
+ }
+ return -1;
+ }
+
+ int write_idmap(int fd, const uint32_t *data, size_t size)
+ {
+ if (lseek(fd, SEEK_SET, 0) < 0) {
+ return -1;
+ }
+ size_t bytesLeft = size;
+ while (bytesLeft > 0) {
+ ssize_t w = TEMP_FAILURE_RETRY(write(fd, data + size - bytesLeft, bytesLeft));
+ if (w < 0) {
+ fprintf(stderr, "error: write: %s\n", strerror(errno));
+ return -1;
+ }
+ bytesLeft -= w;
+ }
+ return 0;
+ }
+
+ bool is_idmap_stale_fd(const char *target_apk_path, const char *overlay_apk_path, int idmap_fd)
+ {
+ static const size_t N = ResTable::IDMAP_HEADER_SIZE_BYTES;
+ struct stat st;
+ if (fstat(idmap_fd, &st) == -1) {
+ return true;
+ }
+ if (st.st_size < N) {
+ // file is empty or corrupt
+ return true;
+ }
+
+ char buf[N];
+ ssize_t bytesLeft = N;
+ if (lseek(idmap_fd, SEEK_SET, 0) < 0) {
+ return true;
+ }
+ for (;;) {
+ ssize_t r = TEMP_FAILURE_RETRY(read(idmap_fd, buf + N - bytesLeft, bytesLeft));
+ if (r < 0) {
+ return true;
+ }
+ bytesLeft -= r;
+ if (bytesLeft == 0) {
+ break;
+ }
+ if (r == 0) {
+ // "shouldn't happen"
+ return true;
+ }
+ }
+
+ 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,
+ &cached_target_path, &cached_overlay_path)) {
+ return true;
+ }
+
+ if (cached_target_path != target_apk_path) {
+ return true;
+ }
+ if (cached_overlay_path != overlay_apk_path) {
+ return true;
+ }
+
+ uint32_t actual_target_crc, actual_overlay_crc;
+ if (get_zip_entry_crc(target_apk_path, AssetManager::RESOURCES_FILENAME,
+ &actual_target_crc) == -1) {
+ return true;
+ }
+ if (get_zip_entry_crc(overlay_apk_path, AssetManager::RESOURCES_FILENAME,
+ &actual_overlay_crc) == -1) {
+ return true;
+ }
+
+ return cached_target_crc != actual_target_crc || cached_overlay_crc != actual_overlay_crc;
+ }
+
+ bool is_idmap_stale_path(const char *target_apk_path, const char *overlay_apk_path,
+ const char *idmap_path)
+ {
+ struct stat st;
+ if (stat(idmap_path, &st) == -1) {
+ // non-existing idmap is always stale; on other errors, abort idmap generation
+ return errno == ENOENT;
+ }
+
+ int idmap_fd = TEMP_FAILURE_RETRY(open(idmap_path, O_RDONLY));
+ if (idmap_fd == -1) {
+ return false;
+ }
+ bool is_stale = is_idmap_stale_fd(target_apk_path, overlay_apk_path, idmap_fd);
+ close(idmap_fd);
+ return is_stale;
+ }
+
+ int create_idmap(const char *target_apk_path, const char *overlay_apk_path,
+ uint32_t **data, size_t *size)
+ {
+ uint32_t target_crc, overlay_crc;
+ if (get_zip_entry_crc(target_apk_path, AssetManager::RESOURCES_FILENAME,
+ &target_crc) == -1) {
+ return -1;
+ }
+ if (get_zip_entry_crc(overlay_apk_path, AssetManager::RESOURCES_FILENAME,
+ &overlay_crc) == -1) {
+ return -1;
+ }
+
+ AssetManager am;
+ bool b = am.createIdmap(target_apk_path, overlay_apk_path, target_crc, overlay_crc,
+ data, size);
+ return b ? 0 : -1;
+ }
+
+ int create_and_write_idmap(const char *target_apk_path, const char *overlay_apk_path,
+ int fd, bool check_if_stale)
+ {
+ if (check_if_stale) {
+ if (!is_idmap_stale_fd(target_apk_path, overlay_apk_path, fd)) {
+ // already up to date -- nothing to do
+ return 0;
+ }
+ }
+
+ uint32_t *data = NULL;
+ size_t size;
+
+ if (create_idmap(target_apk_path, overlay_apk_path, &data, &size) == -1) {
+ return -1;
+ }
+
+ if (write_idmap(fd, data, size) == -1) {
+ free(data);
+ return -1;
+ }
+
+ free(data);
+ return 0;
+ }
+}
+
+int idmap_create_path(const char *target_apk_path, const char *overlay_apk_path,
+ const char *idmap_path)
+{
+ if (!is_idmap_stale_path(target_apk_path, overlay_apk_path, idmap_path)) {
+ // already up to date -- nothing to do
+ return EXIT_SUCCESS;
+ }
+
+ int fd = open_idmap(idmap_path);
+ if (fd == -1) {
+ return EXIT_FAILURE;
+ }
+
+ int r = create_and_write_idmap(target_apk_path, overlay_apk_path, fd, false);
+ close(fd);
+ if (r != 0) {
+ unlink(idmap_path);
+ }
+ return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+int idmap_create_fd(const char *target_apk_path, const char *overlay_apk_path, int fd)
+{
+ return create_and_write_idmap(target_apk_path, overlay_apk_path, fd, true) == 0 ?
+ EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/cmds/idmap/idmap.cpp b/cmds/idmap/idmap.cpp
new file mode 100644
index 0000000..46c0edc
--- /dev/null
+++ b/cmds/idmap/idmap.cpp
@@ -0,0 +1,237 @@
+#include "idmap.h"
+
+#include <private/android_filesystem_config.h> // for AID_SYSTEM
+
+#include <stdlib.h>
+#include <string.h>
+
+namespace {
+ const char *usage = "NAME\n\
+ idmap - create or display idmap files\n\
+\n\
+SYNOPSIS \n\
+ idmap --help \n\
+ idmap --fd target overlay fd \n\
+ idmap --path target overlay idmap \n\
+ idmap --scan dir-to-scan target-to-look-for target dir-to-hold-idmaps \n\
+ idmap --inspect idmap \n\
+\n\
+DESCRIPTION \n\
+ Idmap files play an integral part in the runtime resource overlay framework. An idmap \n\
+ file contains a mapping of resource identifiers between overlay package and its target \n\
+ package; this mapping is used during resource lookup. Idmap files also act as control \n\
+ files by their existence: if not present, the corresponding overlay package is ignored \n\
+ when the resource context is created. \n\
+\n\
+ Idmap files are stored in /data/resource-cache. For each pair (target package, overlay \n\
+ package), there exists exactly one idmap file, or none if the overlay should not be used. \n\
+\n\
+NOMENCLATURE \n\
+ target: the original, non-overlay, package. Each target package may be associated with \n\
+ any number of overlay packages. \n\
+\n\
+ overlay: an overlay package. Each overlay package is associated with exactly one target \n\
+ package, specified in the overlay's manifest using the <overlay target=\"...\"/> \n\
+ tag. \n\
+\n\
+OPTIONS \n\
+ --help: display this help \n\
+\n\
+ --fd: create idmap for target package 'target' (path to apk) and overlay package 'overlay' \n\
+ (path to apk); write results to file descriptor 'fd' (integer). This invocation \n\
+ version is intended to be used by a parent process with higher privileges to call \n\
+ idmap in a controlled way: the parent will open a suitable file descriptor, fork, \n\
+ drop its privileges and exec. This tool will continue execution without the extra \n\
+ privileges, but still have write access to a file it could not have opened on its \n\
+ own. \n\
+\n\
+ --path: create idmap for target package 'target' (path to apk) and overlay package \n\
+ 'overlay' (path to apk); write results to 'idmap' (path). \n\
+\n\
+ --scan: non-recursively search directory 'dir-to-scan' (path) for overlay packages with \n\
+ target package 'target-to-look-for' (package name) present at 'target' (path to \n\
+ apk). For each overlay package found, create an idmap file in 'dir-to-hold-idmaps' \n\
+ (path). \n\
+\n\
+ --inspect: decode the binary format of 'idmap' (path) and display the contents in a \n\
+ debug-friendly format. \n\
+\n\
+EXAMPLES \n\
+ Create an idmap file: \n\
+\n\
+ $ adb shell idmap --path /system/app/target.apk \\ \n\
+ /vendor/overlay/overlay.apk \\ \n\
+ /data/resource-cache/vendor@overlay@overlay.apk@idmap \n\
+\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\
+\n\
+ In this example, the overlay package provides three alternative resource values:\n\
+ xml/integer, string/str and string/str2.\n\
+\n\
+NOTES \n\
+ This tool and its expected invocation from installd is modelled on dexopt.";
+
+ bool verify_directory_readable(const char *path)
+ {
+ return access(path, R_OK | X_OK) == 0;
+ }
+
+ bool verify_directory_writable(const char *path)
+ {
+ return access(path, W_OK) == 0;
+ }
+
+ bool verify_file_readable(const char *path)
+ {
+ return access(path, R_OK) == 0;
+ }
+
+ bool verify_root_or_system()
+ {
+ uid_t uid = getuid();
+ gid_t gid = getgid();
+
+ return (uid == 0 && gid == 0) || (uid == AID_SYSTEM && gid == AID_SYSTEM);
+ }
+
+ int maybe_create_fd(const char *target_apk_path, const char *overlay_apk_path,
+ const char *idmap_str)
+ {
+ // anyone (not just root or system) may do --fd -- the file has
+ // already been opened by someone else on our behalf
+
+ char *endptr;
+ int idmap_fd = strtol(idmap_str, &endptr, 10);
+ if (*endptr != '\0') {
+ fprintf(stderr, "error: failed to parse file descriptor argument %s\n", idmap_str);
+ return -1;
+ }
+
+ if (!verify_file_readable(target_apk_path)) {
+ ALOGD("error: failed to read apk %s: %s\n", target_apk_path, strerror(errno));
+ return -1;
+ }
+
+ if (!verify_file_readable(overlay_apk_path)) {
+ ALOGD("error: failed to read apk %s: %s\n", overlay_apk_path, strerror(errno));
+ return -1;
+ }
+
+ return idmap_create_fd(target_apk_path, overlay_apk_path, idmap_fd);
+ }
+
+ int maybe_create_path(const char *target_apk_path, const char *overlay_apk_path,
+ const char *idmap_path)
+ {
+ if (!verify_root_or_system()) {
+ fprintf(stderr, "error: permission denied: not user root or user system\n");
+ return -1;
+ }
+
+ if (!verify_file_readable(target_apk_path)) {
+ ALOGD("error: failed to read apk %s: %s\n", target_apk_path, strerror(errno));
+ return -1;
+ }
+
+ if (!verify_file_readable(overlay_apk_path)) {
+ ALOGD("error: failed to read apk %s: %s\n", overlay_apk_path, strerror(errno));
+ return -1;
+ }
+
+ return idmap_create_path(target_apk_path, overlay_apk_path, idmap_path);
+ }
+
+ int maybe_scan(const char *overlay_dir, const char *target_package_name,
+ const char *target_apk_path, const char *idmap_dir)
+ {
+ if (!verify_root_or_system()) {
+ fprintf(stderr, "error: permission denied: not user root or user system\n");
+ return -1;
+ }
+
+ if (!verify_directory_readable(overlay_dir)) {
+ ALOGD("error: no read access to %s: %s\n", overlay_dir, strerror(errno));
+ return -1;
+ }
+
+ if (!verify_file_readable(target_apk_path)) {
+ ALOGD("error: failed to read apk %s: %s\n", target_apk_path, strerror(errno));
+ return -1;
+ }
+
+ if (!verify_directory_writable(idmap_dir)) {
+ ALOGD("error: no write access to %s: %s\n", idmap_dir, strerror(errno));
+ return -1;
+ }
+
+ return idmap_scan(overlay_dir, target_package_name, target_apk_path, idmap_dir);
+ }
+
+ int maybe_inspect(const char *idmap_path)
+ {
+ // anyone (not just root or system) may do --inspect
+ if (!verify_file_readable(idmap_path)) {
+ ALOGD("error: failed to read idmap %s: %s\n", idmap_path, strerror(errno));
+ return -1;
+ }
+ return idmap_inspect(idmap_path);
+ }
+}
+
+int main(int argc, char **argv)
+{
+#if 0
+ {
+ char buf[1024];
+ buf[0] = '\0';
+ for (int i = 0; i < argc; ++i) {
+ strncat(buf, argv[i], sizeof(buf) - 1);
+ strncat(buf, " ", sizeof(buf) - 1);
+ }
+ ALOGD("%s:%d: uid=%d gid=%d argv=%s\n", __FILE__, __LINE__, getuid(), getgid(), buf);
+ }
+#endif
+
+ if (argc == 2 && !strcmp(argv[1], "--help")) {
+ printf("%s\n", usage);
+ return 0;
+ }
+
+ if (argc == 5 && !strcmp(argv[1], "--fd")) {
+ return maybe_create_fd(argv[2], argv[3], argv[4]);
+ }
+
+ if (argc == 5 && !strcmp(argv[1], "--path")) {
+ return maybe_create_path(argv[2], argv[3], argv[4]);
+ }
+
+ if (argc == 6 && !strcmp(argv[1], "--scan")) {
+ return maybe_scan(argv[2], argv[3], argv[4], argv[5]);
+ }
+
+ if (argc == 3 && !strcmp(argv[1], "--inspect")) {
+ return maybe_inspect(argv[2]);
+ }
+
+ fprintf(stderr, "Usage: don't use this (cf dexopt usage).\n");
+ return EXIT_FAILURE;
+}
diff --git a/cmds/idmap/idmap.h b/cmds/idmap/idmap.h
new file mode 100644
index 0000000..f507dd8
--- /dev/null
+++ b/cmds/idmap/idmap.h
@@ -0,0 +1,34 @@
+#ifndef _IDMAP_H_
+#define _IDMAP_H_
+
+#define LOG_TAG "idmap"
+
+#include <utils/Log.h>
+
+#include <errno.h>
+#include <stdio.h>
+
+#ifndef TEMP_FAILURE_RETRY
+// Used to retry syscalls that can return EINTR.
+#define TEMP_FAILURE_RETRY(exp) ({ \
+ typeof (exp) _rc; \
+ do { \
+ _rc = (exp); \
+ } while (_rc == -1 && errno == EINTR); \
+ _rc; })
+#endif
+
+int idmap_create_path(const char *target_apk_path, const char *overlay_apk_path,
+ const char *idmap_path);
+
+int idmap_create_fd(const char *target_apk_path, const char *overlay_apk_path, int fd);
+
+// Regarding target_package_name: the idmap_scan implementation should
+// be able to extract this from the manifest in target_apk_path,
+// simplifying the external API.
+int idmap_scan(const char *overlay_dir, const char *target_package_name,
+ const char *target_apk_path, const char *idmap_dir);
+
+int idmap_inspect(const char *idmap_path);
+
+#endif // _IDMAP_H_
diff --git a/cmds/idmap/inspect.cpp b/cmds/idmap/inspect.cpp
new file mode 100644
index 0000000..a59f5d3
--- /dev/null
+++ b/cmds/idmap/inspect.cpp
@@ -0,0 +1,291 @@
+#include "idmap.h"
+
+#include <androidfw/AssetManager.h>
+#include <androidfw/ResourceTypes.h>
+#include <utils/String8.h>
+
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+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 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_;
+ size_t len_;
+ mutable size_t pos_;
+ public:
+ IdmapBuffer() : buf_((char *)MAP_FAILED), len_(0), pos_(0) {}
+
+ ~IdmapBuffer() {
+ if (buf_ != MAP_FAILED) {
+ munmap(buf_, len_);
+ }
+ }
+
+ int 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;
+ }
+ 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;
+ }
+ if ((buf_ = (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;
+ }
+ close(fd);
+ return 0;
+ }
+
+ int next(uint32_t *i, uint32_t *offset) const
+ {
+ if (!buf_) {
+ printe("failed to read next uint32_t: buffer not initialized\n");
+ return -1;
+ }
+ if (pos_ + 4 > len_) {
+ printe("failed to read next uint32_t: end of buffer reached at pos=0x%08x\n",
+ pos_);
+ return -1;
+ }
+ *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;
+ }
+
+ int nextPath(char *b, uint32_t *offset_start, uint32_t *offset_end) const
+ {
+ if (!buf_) {
+ printe("failed to read next path: buffer not initialized\n");
+ return -1;
+ }
+ if (pos_ + PATH_LENGTH > len_) {
+ printe("failed to read next path: end of buffer reached at pos=0x%08x\n", pos_);
+ return -1;
+ }
+ memcpy(b, buf_ + pos_, PATH_LENGTH);
+ *offset_start = pos_ / sizeof(uint32_t);
+ pos_ += PATH_LENGTH;
+ *offset_end = pos_ / sizeof(uint32_t) - 1;
+ return 0;
+ }
+ };
+
+ void printe(const char *fmt, ...)
+ {
+ va_list ap;
+
+ va_start(ap, fmt);
+ fprintf(stderr, "error: ");
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ }
+
+ void print_header()
+ {
+ printf("SECTION ENTRY VALUE OFFSET COMMENT\n");
+ }
+
+ void print(const char *section, const char *subsection, uint32_t value, uint32_t offset,
+ const char *fmt, ...)
+ {
+ va_list ap;
+
+ va_start(ap, fmt);
+ printf("%-12s %-12s 0x%08x 0x%-4x ", section, subsection, value, offset);
+ 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, ...)
+ {
+ va_list ap;
+
+ va_start(ap, fmt);
+ printf("%-12s %-12s .......... 0x%02x-0x%02x ", section, subsection, offset_start,
+ offset_end);
+ vprintf(fmt, ap);
+ printf("\n");
+ va_end(ap);
+ }
+
+ int 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;
+ }
+ if (package) {
+ *package = String8(String16(data.package, data.packageLen));
+ }
+ if (type) {
+ *type = String8(String16(data.type, data.typeLen));
+ }
+ if (name) {
+ *name = String8(String16(data.name, data.nameLen));
+ }
+ return 0;
+ }
+
+ int package_id(const AssetManager& am)
+ {
+ return (am.getResources().getBasePackageId(0)) << 24;
+ }
+
+ int parse_idmap_header(const IdmapBuffer& buf, AssetManager& am)
+ {
+ uint32_t i, o, e;
+ char path[PATH_LENGTH];
+
+ NEXT(buf, i, o);
+ 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;
+ }
+ print_header();
+ print("IDMAP HEADER", "magic", i, o, "");
+
+ NEXT(buf, i, o);
+ print("", "base crc", i, o, "");
+
+ NEXT(buf, i, o);
+ print("", "overlay crc", i, o, "");
+
+ if (buf.nextPath(path, &o, &e) < 0) {
+ // printe done from IdmapBuffer::nextPath
+ return -1;
+ }
+ print_path("", "base path", o, e, "%s", path);
+ if (!am.addAssetPath(String8(path), NULL)) {
+ printe("failed to add '%s' as asset path\n", path);
+ return -1;
+ }
+
+ if (buf.nextPath(path, &o, &e) < 0) {
+ // printe done from IdmapBuffer::nextPath
+ return -1;
+ }
+ print_path("", "overlay path", o, e, "%s", path);
+
+ return 0;
+ }
+
+ 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);
+
+ NEXT(buf, i, o);
+ print("DATA HEADER", "types count", i, o, "");
+ const uint32_t N = i;
+
+ 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);
+ }
+ }
+
+ return 0;
+ }
+
+ 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;
+ }
+ print("", "entry", i, o, "%s/%s", type.string(), name.string());
+ }
+ ++id_offset;
+ }
+
+ return 0;
+ }
+}
+
+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) {
+ // printe done from parse_idmap_header
+ return EXIT_FAILURE;
+ }
+ Vector<uint32_t> types;
+ if (parse_data_header(buf, am, types) < 0) {
+ // 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/idmap/scan.cpp b/cmds/idmap/scan.cpp
new file mode 100644
index 0000000..c5fc941
--- /dev/null
+++ b/cmds/idmap/scan.cpp
@@ -0,0 +1,244 @@
+#include "idmap.h"
+
+#include <UniquePtr.h>
+#include <androidfw/ResourceTypes.h>
+#include <androidfw/StreamingZipInflater.h>
+#include <androidfw/ZipFileRO.h>
+#include <private/android_filesystem_config.h> // for AID_SYSTEM
+#include <utils/SortedVector.h>
+#include <utils/String16.h>
+#include <utils/String8.h>
+
+#include <dirent.h>
+
+#define NO_OVERLAY_TAG (-1000)
+
+using namespace android;
+
+namespace {
+ struct Overlay {
+ Overlay() {}
+ Overlay(const String8& a, const String8& i, int p) :
+ apk_path(a), idmap_path(i), priority(p) {}
+
+ bool operator<(Overlay const& rhs) const
+ {
+ // Note: order is reversed by design
+ return rhs.priority < priority;
+ }
+
+ String8 apk_path;
+ String8 idmap_path;
+ int priority;
+ };
+
+ bool writePackagesList(const char *filename, const SortedVector<Overlay>& overlayVector)
+ {
+ FILE* fout = fopen(filename, "w");
+ if (fout == NULL) {
+ return false;
+ }
+
+ for (size_t i = 0; i < overlayVector.size(); ++i) {
+ const Overlay& overlay = overlayVector[i];
+ fprintf(fout, "%s %s\n", overlay.apk_path.string(), overlay.idmap_path.string());
+ }
+
+ fclose(fout);
+
+ // Make file world readable since Zygote (running as root) will read
+ // it when creating the initial AssetManger object
+ const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; // 0644
+ if (chmod(filename, mode) == -1) {
+ unlink(filename);
+ return false;
+ }
+
+ return true;
+ }
+
+ String8 flatten_path(const char *path)
+ {
+ String16 tmp(path);
+ tmp.replaceAll('/', '@');
+ return String8(tmp);
+ }
+
+ int mkdir_p(const String8& path, uid_t uid, gid_t gid)
+ {
+ static const mode_t mode =
+ S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH;
+ struct stat st;
+
+ if (stat(path.string(), &st) == 0) {
+ return 0;
+ }
+ if (mkdir_p(path.getPathDir(), uid, gid) < 0) {
+ return -1;
+ }
+ if (mkdir(path.string(), 0755) != 0) {
+ return -1;
+ }
+ if (chown(path.string(), uid, gid) == -1) {
+ return -1;
+ }
+ if (chmod(path.string(), mode) == -1) {
+ return -1;
+ }
+ return 0;
+ }
+
+ int parse_overlay_tag(const ResXMLTree& parser, const char *target_package_name)
+ {
+ const size_t N = parser.getAttributeCount();
+ String16 target;
+ int priority = -1;
+ for (size_t i = 0; i < N; ++i) {
+ size_t len;
+ String16 key(parser.getAttributeName(i, &len));
+ if (key == String16("targetPackage")) {
+ const uint16_t *p = parser.getAttributeStringValue(i, &len);
+ if (p) {
+ target = String16(p, len);
+ }
+ } else if (key == String16("priority")) {
+ Res_value v;
+ if (parser.getAttributeValue(i, &v) == sizeof(Res_value)) {
+ priority = v.data;
+ if (priority < 0 || priority > 9999) {
+ return -1;
+ }
+ }
+ }
+ }
+ if (target == String16(target_package_name)) {
+ return priority;
+ }
+ return NO_OVERLAY_TAG;
+ }
+
+ int parse_manifest(const void *data, size_t size, const char *target_package_name)
+ {
+ ResXMLTree parser(data, size);
+ if (parser.getError() != NO_ERROR) {
+ ALOGD("%s failed to init xml parser, error=0x%08x\n", __FUNCTION__, parser.getError());
+ return -1;
+ }
+
+ ResXMLParser::event_code_t type;
+ do {
+ type = parser.next();
+ if (type == ResXMLParser::START_TAG) {
+ size_t len;
+ String16 tag(parser.getElementName(&len));
+ if (tag == String16("overlay")) {
+ return parse_overlay_tag(parser, target_package_name);
+ }
+ }
+ } while (type != ResXMLParser::BAD_DOCUMENT && type != ResXMLParser::END_DOCUMENT);
+
+ return NO_OVERLAY_TAG;
+ }
+
+ int parse_apk(const char *path, const char *target_package_name)
+ {
+ UniquePtr<ZipFileRO> zip(ZipFileRO::open(path));
+ if (zip.get() == NULL) {
+ ALOGW("%s: failed to open zip %s\n", __FUNCTION__, path);
+ return -1;
+ }
+ ZipEntryRO entry;
+ if ((entry = zip->findEntryByName("AndroidManifest.xml")) == NULL) {
+ ALOGW("%s: failed to find entry AndroidManifest.xml\n", __FUNCTION__);
+ return -1;
+ }
+ size_t uncompLen = 0;
+ int method;
+ if (!zip->getEntryInfo(entry, &method, &uncompLen, NULL, NULL, NULL, NULL)) {
+ ALOGW("%s: failed to read entry info\n", __FUNCTION__);
+ return -1;
+ }
+ if (method != ZipFileRO::kCompressDeflated) {
+ ALOGW("%s: cannot handle zip compression method %d\n", __FUNCTION__, method);
+ return -1;
+ }
+ FileMap *dataMap = zip->createEntryFileMap(entry);
+ if (!dataMap) {
+ ALOGW("%s: failed to create FileMap\n", __FUNCTION__);
+ return -1;
+ }
+ char *buf = new char[uncompLen];
+ if (NULL == buf) {
+ ALOGW("%s: failed to allocate %d byte\n", __FUNCTION__, uncompLen);
+ dataMap->release();
+ return -1;
+ }
+ StreamingZipInflater inflater(dataMap, uncompLen);
+ if (inflater.read(buf, uncompLen) < 0) {
+ ALOGW("%s: failed to inflate %d byte\n", __FUNCTION__, uncompLen);
+ delete[] buf;
+ dataMap->release();
+ return -1;
+ }
+
+ int priority = parse_manifest(buf, uncompLen, target_package_name);
+ delete[] buf;
+ dataMap->release();
+ return priority;
+ }
+}
+
+int idmap_scan(const char *overlay_dir, const char *target_package_name,
+ const char *target_apk_path, const char *idmap_dir)
+{
+ String8 filename = String8(idmap_dir);
+ filename.appendPath("overlays.list");
+ if (unlink(filename.string()) != 0 && errno != ENOENT) {
+ return EXIT_FAILURE;
+ }
+
+ DIR *dir = opendir(overlay_dir);
+ if (dir == NULL) {
+ return EXIT_FAILURE;
+ }
+
+ SortedVector<Overlay> overlayVector;
+ struct dirent *dirent;
+ while ((dirent = readdir(dir)) != NULL) {
+ struct stat st;
+ char overlay_apk_path[PATH_MAX + 1];
+ snprintf(overlay_apk_path, PATH_MAX, "%s/%s", overlay_dir, dirent->d_name);
+ if (stat(overlay_apk_path, &st) < 0) {
+ continue;
+ }
+ if (!S_ISREG(st.st_mode)) {
+ continue;
+ }
+
+ int priority = parse_apk(overlay_apk_path, target_package_name);
+ if (priority < 0) {
+ continue;
+ }
+
+ String8 idmap_path(idmap_dir);
+ idmap_path.appendPath(flatten_path(overlay_apk_path + 1));
+ idmap_path.append("@idmap");
+
+ if (idmap_create_path(target_apk_path, overlay_apk_path, idmap_path.string()) != 0) {
+ ALOGE("error: failed to create idmap for target=%s overlay=%s idmap=%s\n",
+ target_apk_path, overlay_apk_path, idmap_path.string());
+ continue;
+ }
+
+ Overlay overlay(String8(overlay_apk_path), idmap_path, priority);
+ overlayVector.add(overlay);
+ }
+
+ closedir(dir);
+
+ if (!writePackagesList(filename.string(), overlayVector)) {
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index bf2a629..ee8d457 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -972,7 +972,7 @@
if (fragment.mAnimatingAway != null) {
fragment.mAnimatingAway = null;
moveToState(fragment, fragment.mStateAfterAnimating,
- 0, 0, false);
+ 0, 0, true);
}
}
});
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 345ff82..a9b6073 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -549,8 +549,9 @@
public static final int STATE_SENSOR_ON_FLAG = 1<<30;
public static final int STATE_GPS_ON_FLAG = 1<<29;
public static final int STATE_WIFI_FULL_LOCK_FLAG = 1<<28;
- public static final int STATE_WIFI_SCAN_FLAG = 1<<29;
+ public static final int STATE_WIFI_SCAN_FLAG = 1<<27;
public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<26;
+ public static final int STATE_MOBILE_RADIO_ACTIVE_FLAG = 1<<25;
public static final int STATE_WIFI_RUNNING_FLAG = 1<<24;
// These are on the lower bits used for the command; if they change
// we need to write another int of data.
@@ -882,6 +883,15 @@
*/
public abstract int getPhoneSignalStrengthCount(int strengthBin, int which);
+ /**
+ * Returns the time in microseconds that the mobile network has been active
+ * (in a high power state).
+ *
+ * {@hide}
+ */
+ public abstract long getMobileRadioActiveTime(long batteryRealtime, int which);
+
+
public static final int DATA_CONNECTION_NONE = 0;
public static final int DATA_CONNECTION_GPRS = 1;
public static final int DATA_CONNECTION_EDGE = 2;
@@ -933,6 +943,7 @@
new BitDescription(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG, "wifi_full_lock", "Wl"),
new BitDescription(HistoryItem.STATE_WIFI_SCAN_FLAG, "wifi_scan", "Ws"),
new BitDescription(HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG, "wifi_multicast", "Wm"),
+ new BitDescription(HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG, "mobile_radio", "Pr"),
new BitDescription(HistoryItem.STATE_WIFI_RUNNING_FLAG, "wifi_running", "Wr"),
new BitDescription(HistoryItem.STATE_PHONE_SCANNING_FLAG, "phone_scanning", "Psc"),
new BitDescription(HistoryItem.STATE_AUDIO_ON_FLAG, "audio", "a"),
@@ -993,6 +1004,33 @@
public abstract int getBluetoothPingCount();
+ public static final int BLUETOOTH_INACTIVE = 0;
+ public static final int BLUETOOTH_ACTIVE_LOW = 1;
+ public static final int BLUETOOTH_ACTIVE_MEDIUM = 2;
+ public static final int BLUETOOTH_ACTIVE_HIGH = 3;
+
+ static final String[] BLUETOOTH_ACTIVE_NAMES = {
+ "none", "low", "med", "high"
+ };
+
+ public static final int NUM_BLUETOOTH_ACTIVE_TYPES = BLUETOOTH_ACTIVE_HIGH+1;
+
+ /**
+ * Returns the time in microseconds that Bluetooth has been running in the
+ * given active state.
+ *
+ * {@hide}
+ */
+ public abstract long getBluetoothActiveTime(int activeType,
+ long batteryRealtime, int which);
+
+ /**
+ * Returns the number of times the Bluetooth has entered the given active state.
+ *
+ * {@hide}
+ */
+ public abstract int getBluetoothActiveCount(int activeType, int which);
+
public static final int NETWORK_MOBILE_RX_DATA = 0;
public static final int NETWORK_MOBILE_TX_DATA = 1;
public static final int NETWORK_WIFI_RX_DATA = 2;
@@ -1026,19 +1064,6 @@
public abstract long getBatteryUptime(long curTime);
/**
- * @deprecated use getRadioDataUptime
- */
- public long getRadioDataUptimeMs() {
- return getRadioDataUptime() / 1000;
- }
-
- /**
- * Returns the time that the radio was on for data transfers.
- * @return the uptime in microseconds while unplugged
- */
- public abstract long getRadioDataUptime();
-
- /**
* Returns the current battery realtime in microseconds.
*
* @param curTime the amount of elapsed realtime in microseconds.
@@ -1374,7 +1399,7 @@
wifiRunningTime / 1000, bluetoothOnTime / 1000,
mobileRxTotalBytes, mobileTxTotalBytes, wifiRxTotalBytes, wifiTxTotalBytes,
fullWakeLockTimeTotal, partialWakeLockTimeTotal,
- getInputEventCount(which));
+ getInputEventCount(which), getMobileRadioActiveTime(batteryRealtime, which));
// Dump screen brightness stats
Object[] args = new Object[NUM_SCREEN_BRIGHTNESS_BINS];
@@ -1395,7 +1420,7 @@
args[i] = getPhoneSignalStrengthCount(i, which);
}
dumpLine(pw, 0 /* uid */, category, SIGNAL_STRENGTH_COUNT_DATA, args);
-
+
// Dump network type stats
args = new Object[NUM_DATA_CONNECTION_TYPES];
for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
@@ -1408,7 +1433,7 @@
dumpLine(pw, 0 /* uid */, category, DATA_CONNECTION_COUNT_DATA, args);
if (which == STATS_SINCE_UNPLUGGED) {
- dumpLine(pw, 0 /* uid */, category, BATTERY_LEVEL_DATA, getDischargeStartLevel(),
+ dumpLine(pw, 0 /* uid */, category, BATTERY_LEVEL_DATA, getDischargeStartLevel(),
getDischargeCurrentLevel());
}
@@ -1906,9 +1931,8 @@
sb.setLength(0);
sb.append(prefix);
- sb.append(" Radio data uptime when unplugged: ");
- sb.append(getRadioDataUptime() / 1000);
- sb.append(" ms");
+ sb.append(" Mobile radio active time: ");
+ formatTimeMs(sb, getMobileRadioActiveTime(batteryRealtime, which) / 1000);
pw.println(sb.toString());
sb.setLength(0);
diff --git a/core/java/android/service/dreams/DreamManagerInternal.java b/core/java/android/service/dreams/DreamManagerInternal.java
new file mode 100644
index 0000000..17ea996
--- /dev/null
+++ b/core/java/android/service/dreams/DreamManagerInternal.java
@@ -0,0 +1,39 @@
+/*
+ * 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.service.dreams;
+
+/**
+ * Dream manager local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class DreamManagerInternal {
+ /**
+ * Called by the power manager to start a dream.
+ */
+ public abstract void startDream();
+
+ /**
+ * Called by the power manager to stop a dream.
+ */
+ public abstract void stopDream();
+
+ /**
+ * Called by the power manager to determine whether a dream is running.
+ */
+ public abstract boolean isDreaming();
+}
diff --git a/core/java/android/view/SurfaceSession.java b/core/java/android/view/SurfaceSession.java
index 0dfd94a..3cf5af4 100644
--- a/core/java/android/view/SurfaceSession.java
+++ b/core/java/android/view/SurfaceSession.java
@@ -24,11 +24,11 @@
*/
public final class SurfaceSession {
// Note: This field is accessed by native code.
- private int mNativeClient; // SurfaceComposerClient*
+ private long mNativeClient; // SurfaceComposerClient*
- private static native int nativeCreate();
- private static native void nativeDestroy(int ptr);
- private static native void nativeKill(int ptr);
+ private static native long nativeCreate();
+ private static native void nativeDestroy(long ptr);
+ private static native void nativeKill(long ptr);
/** Create a new connection with the surface flinger. */
public SurfaceSession() {
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 7603305..4fdbc1e 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -2283,6 +2283,7 @@
parcel.writeInt(mCollectionInfo.getRowCount());
parcel.writeInt(mCollectionInfo.getColumnCount());
parcel.writeInt(mCollectionInfo.isHierarchical() ? 1 : 0);
+ parcel.writeInt(mCollectionInfo.getSelectionMode());
} else {
parcel.writeInt(0);
}
@@ -2294,6 +2295,7 @@
parcel.writeInt(mCollectionItemInfo.getRowIndex());
parcel.writeInt(mCollectionItemInfo.getRowSpan());
parcel.writeInt(mCollectionItemInfo.isHeading() ? 1 : 0);
+ parcel.writeInt(mCollectionItemInfo.isSelected() ? 1 : 0);
} else {
parcel.writeInt(0);
}
@@ -2420,7 +2422,8 @@
mCollectionInfo = CollectionInfo.obtain(
parcel.readInt(),
parcel.readInt(),
- parcel.readInt() == 1);
+ parcel.readInt() == 1,
+ parcel.readInt());
}
if (parcel.readInt() == 1) {
@@ -2429,6 +2432,7 @@
parcel.readInt(),
parcel.readInt(),
parcel.readInt(),
+ parcel.readInt() == 1,
parcel.readInt() == 1);
}
}
@@ -2786,6 +2790,15 @@
* </p>
*/
public static final class CollectionInfo {
+ /** Selection mode where items are not selectable. */
+ public static final int SELECTION_MODE_NONE = 0;
+
+ /** Selection mode where a single item may be selected. */
+ public static final int SELECTION_MODE_SINGLE = 1;
+
+ /** Selection mode where multiple items may be selected. */
+ public static final int SELECTION_MODE_MULTIPLE = 2;
+
private static final int MAX_POOL_SIZE = 20;
private static final SynchronizedPool<CollectionInfo> sPool =
@@ -2794,17 +2807,17 @@
private int mRowCount;
private int mColumnCount;
private boolean mHierarchical;
+ private int mSelectionMode;
/**
* Obtains a pooled instance that is a clone of another one.
*
* @param other The instance to clone.
- *
* @hide
*/
public static CollectionInfo obtain(CollectionInfo other) {
- return CollectionInfo.obtain(other.mRowCount, other.mColumnCount,
- other.mHierarchical);
+ return CollectionInfo.obtain(other.mRowCount, other.mColumnCount, other.mHierarchical,
+ other.mSelectionMode);
}
/**
@@ -2814,15 +2827,35 @@
* @param columnCount The number of columns.
* @param hierarchical Whether the collection is hierarchical.
*/
- public static CollectionInfo obtain(int rowCount, int columnCount, boolean hierarchical) {
- final CollectionInfo info = sPool.acquire();
+ public static CollectionInfo obtain(int rowCount, int columnCount,
+ boolean hierarchical) {
+ return obtain(rowCount, columnCount, hierarchical, SELECTION_MODE_NONE);
+ }
+
+ /**
+ * Obtains a pooled instance.
+ *
+ * @param rowCount The number of rows.
+ * @param columnCount The number of columns.
+ * @param hierarchical Whether the collection is hierarchical.
+ * @param selectionMode The collection's selection mode, one of:
+ * <ul>
+ * <li>{@link #SELECTION_MODE_NONE}
+ * <li>{@link #SELECTION_MODE_SINGLE}
+ * <li>{@link #SELECTION_MODE_MULTIPLE}
+ * </ul>
+ */
+ public static CollectionInfo obtain(int rowCount, int columnCount,
+ boolean hierarchical, int selectionMode) {
+ final CollectionInfo info = sPool.acquire();
if (info == null) {
- return new CollectionInfo(rowCount, columnCount, hierarchical);
+ return new CollectionInfo(rowCount, columnCount, hierarchical, selectionMode);
}
info.mRowCount = rowCount;
info.mColumnCount = columnCount;
info.mHierarchical = hierarchical;
+ info.mSelectionMode = selectionMode;
return info;
}
@@ -2832,12 +2865,14 @@
* @param rowCount The number of rows.
* @param columnCount The number of columns.
* @param hierarchical Whether the collection is hierarchical.
+ * @param selectionMode The collection's selection mode.
*/
- private CollectionInfo(int rowCount, int columnCount,
- boolean hierarchical) {
+ private CollectionInfo(int rowCount, int columnCount, boolean hierarchical,
+ int selectionMode) {
mRowCount = rowCount;
mColumnCount = columnCount;
mHierarchical = hierarchical;
+ mSelectionMode = selectionMode;
}
/**
@@ -2868,6 +2903,20 @@
}
/**
+ * Gets the collection's selection mode.
+ *
+ * @return The collection's selection mode, one of:
+ * <ul>
+ * <li>{@link #SELECTION_MODE_NONE}
+ * <li>{@link #SELECTION_MODE_SINGLE}
+ * <li>{@link #SELECTION_MODE_MULTIPLE}
+ * </ul>
+ */
+ public int getSelectionMode() {
+ return mSelectionMode;
+ }
+
+ /**
* Recycles this instance.
*/
void recycle() {
@@ -2879,6 +2928,7 @@
mRowCount = 0;
mColumnCount = 0;
mHierarchical = false;
+ mSelectionMode = SELECTION_MODE_NONE;
}
}
@@ -2904,12 +2954,11 @@
* Obtains a pooled instance that is a clone of another one.
*
* @param other The instance to clone.
- *
* @hide
*/
public static CollectionItemInfo obtain(CollectionItemInfo other) {
- return CollectionItemInfo.obtain(other.mRowIndex, other.mRowSpan,
- other.mColumnIndex, other.mColumnSpan, other.mHeading);
+ return CollectionItemInfo.obtain(other.mRowIndex, other.mRowSpan, other.mColumnIndex,
+ other.mColumnSpan, other.mHeading, other.mSelected);
}
/**
@@ -2921,11 +2970,27 @@
* @param columnSpan The number of columns the item spans.
* @param heading Whether the item is a heading.
*/
- public static CollectionItemInfo obtain(int rowIndex, int rowSpan, int columnIndex,
- int columnSpan, boolean heading) {
+ public static CollectionItemInfo obtain(int rowIndex, int rowSpan,
+ int columnIndex, int columnSpan, boolean heading) {
+ return obtain(rowIndex, rowSpan, columnIndex, columnSpan, heading, false);
+ }
+
+ /**
+ * Obtains a pooled instance.
+ *
+ * @param rowIndex The row index at which the item is located.
+ * @param rowSpan The number of rows the item spans.
+ * @param columnIndex The column index at which the item is located.
+ * @param columnSpan The number of columns the item spans.
+ * @param heading Whether the item is a heading.
+ * @param selected Whether the item is selected.
+ */
+ public static CollectionItemInfo obtain(int rowIndex, int rowSpan,
+ int columnIndex, int columnSpan, boolean heading, boolean selected) {
final CollectionItemInfo info = sPool.acquire();
if (info == null) {
- return new CollectionItemInfo(rowIndex, rowSpan, columnIndex, columnSpan, heading);
+ return new CollectionItemInfo(
+ rowIndex, rowSpan, columnIndex, columnSpan, heading, selected);
}
info.mRowIndex = rowIndex;
@@ -2933,6 +2998,7 @@
info.mColumnIndex = columnIndex;
info.mColumnSpan = columnSpan;
info.mHeading = heading;
+ info.mSelected = selected;
return info;
}
@@ -2941,6 +3007,7 @@
private int mRowIndex;
private int mColumnSpan;
private int mRowSpan;
+ private boolean mSelected;
/**
* Creates a new instance.
@@ -2951,13 +3018,14 @@
* @param columnSpan The number of columns the item spans.
* @param heading Whether the item is a heading.
*/
- private CollectionItemInfo(int rowIndex, int rowSpan,
- int columnIndex, int columnSpan, boolean heading) {
+ private CollectionItemInfo(int rowIndex, int rowSpan, int columnIndex, int columnSpan,
+ boolean heading, boolean selected) {
mRowIndex = rowIndex;
mRowSpan = rowSpan;
mColumnIndex = columnIndex;
mColumnSpan = columnSpan;
mHeading = heading;
+ mSelected = selected;
}
/**
@@ -3007,6 +3075,15 @@
}
/**
+ * Gets if the collection item is selected.
+ *
+ * @return If the item is selected.
+ */
+ public boolean isSelected() {
+ return mSelected;
+ }
+
+ /**
* Recycles this instance.
*/
void recycle() {
@@ -3020,6 +3097,7 @@
mRowIndex = 0;
mRowSpan = 0;
mHeading = false;
+ mSelected = false;
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
index 97db84b..b4944be 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
@@ -312,18 +312,25 @@
}
}
+ int disconnectedNodeCount = 0;
// Check for disconnected nodes or ones from another window.
for (int i = 0; i < mCacheImpl.size(); i++) {
AccessibilityNodeInfo info = mCacheImpl.valueAt(i);
if (!seen.contains(info)) {
if (info.getWindowId() == windowId) {
- Log.e(LOG_TAG, "Disconneced node: " + info);
+ if (DEBUG) {
+ Log.e(LOG_TAG, "Disconnected node: " + info);
+ }
+ disconnectedNodeCount++;
} else {
Log.e(LOG_TAG, "Node from: " + info.getWindowId() + " not from:"
+ windowId + " " + info);
}
}
}
+ if (disconnectedNodeCount > 0) {
+ Log.e(LOG_TAG, String.format("Found %d disconnected nodes", disconnectedNodeCount));
+ }
}
}
}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 0a755ca..63ce5a3 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -61,6 +61,7 @@
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
@@ -421,7 +422,7 @@
/**
* Handles scrolling between positions within the list.
*/
- SubPositionScroller mPositionScroller;
+ AbsPositionScroller mPositionScroller;
/**
* The offset in pixels form the top of the AdapterView to the top
@@ -1491,6 +1492,21 @@
}
}
+ int getSelectionModeForAccessibility() {
+ final int choiceMode = getChoiceMode();
+ switch (choiceMode) {
+ case CHOICE_MODE_NONE:
+ return CollectionInfo.SELECTION_MODE_NONE;
+ case CHOICE_MODE_SINGLE:
+ return CollectionInfo.SELECTION_MODE_SINGLE;
+ case CHOICE_MODE_MULTIPLE:
+ case CHOICE_MODE_MULTIPLE_MODAL:
+ return CollectionInfo.SELECTION_MODE_MULTIPLE;
+ default:
+ return CollectionInfo.SELECTION_MODE_NONE;
+ }
+ }
+
@Override
public boolean performAccessibilityAction(int action, Bundle arguments) {
if (super.performAccessibilityAction(action, arguments)) {
@@ -4374,447 +4390,6 @@
}
}
- class PositionScroller implements Runnable {
- private static final int SCROLL_DURATION = 200;
-
- private static final int MOVE_DOWN_POS = 1;
- private static final int MOVE_UP_POS = 2;
- private static final int MOVE_DOWN_BOUND = 3;
- private static final int MOVE_UP_BOUND = 4;
- private static final int MOVE_OFFSET = 5;
-
- private int mMode;
- private int mTargetPos;
- private int mBoundPos;
- private int mLastSeenPos;
- private int mScrollDuration;
- private final int mExtraScroll;
-
- private int mOffsetFromTop;
-
- PositionScroller() {
- mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
- }
-
- void start(final int position) {
- stop();
-
- if (mDataChanged) {
- // Wait until we're back in a stable state to try this.
- mPositionScrollAfterLayout = new Runnable() {
- @Override public void run() {
- start(position);
- }
- };
- return;
- }
-
- final int childCount = getChildCount();
- if (childCount == 0) {
- // Can't scroll without children.
- return;
- }
-
- final int firstPos = mFirstPosition;
- final int lastPos = firstPos + childCount - 1;
-
- int viewTravelCount;
- int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
- if (clampedPosition < firstPos) {
- viewTravelCount = firstPos - clampedPosition + 1;
- mMode = MOVE_UP_POS;
- } else if (clampedPosition > lastPos) {
- viewTravelCount = clampedPosition - lastPos + 1;
- mMode = MOVE_DOWN_POS;
- } else {
- scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION);
- return;
- }
-
- if (viewTravelCount > 0) {
- mScrollDuration = SCROLL_DURATION / viewTravelCount;
- } else {
- mScrollDuration = SCROLL_DURATION;
- }
- mTargetPos = clampedPosition;
- mBoundPos = INVALID_POSITION;
- mLastSeenPos = INVALID_POSITION;
-
- postOnAnimation(this);
- }
-
- void start(final int position, final int boundPosition) {
- stop();
-
- if (boundPosition == INVALID_POSITION) {
- start(position);
- return;
- }
-
- if (mDataChanged) {
- // Wait until we're back in a stable state to try this.
- mPositionScrollAfterLayout = new Runnable() {
- @Override public void run() {
- start(position, boundPosition);
- }
- };
- return;
- }
-
- final int childCount = getChildCount();
- if (childCount == 0) {
- // Can't scroll without children.
- return;
- }
-
- final int firstPos = mFirstPosition;
- final int lastPos = firstPos + childCount - 1;
-
- int viewTravelCount;
- int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
- if (clampedPosition < firstPos) {
- final int boundPosFromLast = lastPos - boundPosition;
- if (boundPosFromLast < 1) {
- // Moving would shift our bound position off the screen. Abort.
- return;
- }
-
- final int posTravel = firstPos - clampedPosition + 1;
- final int boundTravel = boundPosFromLast - 1;
- if (boundTravel < posTravel) {
- viewTravelCount = boundTravel;
- mMode = MOVE_UP_BOUND;
- } else {
- viewTravelCount = posTravel;
- mMode = MOVE_UP_POS;
- }
- } else if (clampedPosition > lastPos) {
- final int boundPosFromFirst = boundPosition - firstPos;
- if (boundPosFromFirst < 1) {
- // Moving would shift our bound position off the screen. Abort.
- return;
- }
-
- final int posTravel = clampedPosition - lastPos + 1;
- final int boundTravel = boundPosFromFirst - 1;
- if (boundTravel < posTravel) {
- viewTravelCount = boundTravel;
- mMode = MOVE_DOWN_BOUND;
- } else {
- viewTravelCount = posTravel;
- mMode = MOVE_DOWN_POS;
- }
- } else {
- scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
- return;
- }
-
- if (viewTravelCount > 0) {
- mScrollDuration = SCROLL_DURATION / viewTravelCount;
- } else {
- mScrollDuration = SCROLL_DURATION;
- }
- mTargetPos = clampedPosition;
- mBoundPos = boundPosition;
- mLastSeenPos = INVALID_POSITION;
-
- postOnAnimation(this);
- }
-
- void startWithOffset(int position, int offset) {
- startWithOffset(position, offset, SCROLL_DURATION);
- }
-
- void startWithOffset(final int position, int offset, final int duration) {
- stop();
-
- if (mDataChanged) {
- // Wait until we're back in a stable state to try this.
- final int postOffset = offset;
- mPositionScrollAfterLayout = new Runnable() {
- @Override public void run() {
- startWithOffset(position, postOffset, duration);
- }
- };
- return;
- }
-
- final int childCount = getChildCount();
- if (childCount == 0) {
- // Can't scroll without children.
- return;
- }
-
- offset += getPaddingTop();
-
- mTargetPos = Math.max(0, Math.min(getCount() - 1, position));
- mOffsetFromTop = offset;
- mBoundPos = INVALID_POSITION;
- mLastSeenPos = INVALID_POSITION;
- mMode = MOVE_OFFSET;
-
- final int firstPos = mFirstPosition;
- final int lastPos = firstPos + childCount - 1;
-
- int viewTravelCount;
- if (mTargetPos < firstPos) {
- viewTravelCount = firstPos - mTargetPos;
- } else if (mTargetPos > lastPos) {
- viewTravelCount = mTargetPos - lastPos;
- } else {
- // On-screen, just scroll.
- final int targetTop = getChildAt(mTargetPos - firstPos).getTop();
- smoothScrollBy(targetTop - offset, duration, true);
- return;
- }
-
- // Estimate how many screens we should travel
- final float screenTravelCount = (float) viewTravelCount / childCount;
- mScrollDuration = screenTravelCount < 1 ?
- duration : (int) (duration / screenTravelCount);
- mLastSeenPos = INVALID_POSITION;
-
- postOnAnimation(this);
- }
-
- /**
- * Scroll such that targetPos is in the visible padded region without scrolling
- * boundPos out of view. Assumes targetPos is onscreen.
- */
- void scrollToVisible(int targetPos, int boundPos, int duration) {
- final int firstPos = mFirstPosition;
- final int childCount = getChildCount();
- final int lastPos = firstPos + childCount - 1;
- final int paddedTop = mListPadding.top;
- final int paddedBottom = getHeight() - mListPadding.bottom;
-
- if (targetPos < firstPos || targetPos > lastPos) {
- Log.w(TAG, "scrollToVisible called with targetPos " + targetPos +
- " not visible [" + firstPos + ", " + lastPos + "]");
- }
- if (boundPos < firstPos || boundPos > lastPos) {
- // boundPos doesn't matter, it's already offscreen.
- boundPos = INVALID_POSITION;
- }
-
- final View targetChild = getChildAt(targetPos - firstPos);
- final int targetTop = targetChild.getTop();
- final int targetBottom = targetChild.getBottom();
- int scrollBy = 0;
-
- if (targetBottom > paddedBottom) {
- scrollBy = targetBottom - paddedBottom;
- }
- if (targetTop < paddedTop) {
- scrollBy = targetTop - paddedTop;
- }
-
- if (scrollBy == 0) {
- return;
- }
-
- if (boundPos >= 0) {
- final View boundChild = getChildAt(boundPos - firstPos);
- final int boundTop = boundChild.getTop();
- final int boundBottom = boundChild.getBottom();
- final int absScroll = Math.abs(scrollBy);
-
- if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) {
- // Don't scroll the bound view off the bottom of the screen.
- scrollBy = Math.max(0, boundBottom - paddedBottom);
- } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) {
- // Don't scroll the bound view off the top of the screen.
- scrollBy = Math.min(0, boundTop - paddedTop);
- }
- }
-
- smoothScrollBy(scrollBy, duration);
- }
-
- void stop() {
- removeCallbacks(this);
- }
-
- @Override
- public void run() {
- final int listHeight = getHeight();
- final int firstPos = mFirstPosition;
-
- switch (mMode) {
- case MOVE_DOWN_POS: {
- final int lastViewIndex = getChildCount() - 1;
- final int lastPos = firstPos + lastViewIndex;
-
- if (lastViewIndex < 0) {
- return;
- }
-
- if (lastPos == mLastSeenPos) {
- // No new views, let things keep going.
- postOnAnimation(this);
- return;
- }
-
- final View lastView = getChildAt(lastViewIndex);
- final int lastViewHeight = lastView.getHeight();
- final int lastViewTop = lastView.getTop();
- final int lastViewPixelsShowing = listHeight - lastViewTop;
- final int extraScroll = lastPos < mItemCount - 1 ?
- Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom;
-
- final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll;
- smoothScrollBy(scrollBy, mScrollDuration, true);
-
- mLastSeenPos = lastPos;
- if (lastPos < mTargetPos) {
- postOnAnimation(this);
- }
- break;
- }
-
- case MOVE_DOWN_BOUND: {
- final int nextViewIndex = 1;
- final int childCount = getChildCount();
-
- if (firstPos == mBoundPos || childCount <= nextViewIndex
- || firstPos + childCount >= mItemCount) {
- return;
- }
- final int nextPos = firstPos + nextViewIndex;
-
- if (nextPos == mLastSeenPos) {
- // No new views, let things keep going.
- postOnAnimation(this);
- return;
- }
-
- final View nextView = getChildAt(nextViewIndex);
- final int nextViewHeight = nextView.getHeight();
- final int nextViewTop = nextView.getTop();
- final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll);
- if (nextPos < mBoundPos) {
- smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
- mScrollDuration, true);
-
- mLastSeenPos = nextPos;
-
- postOnAnimation(this);
- } else {
- if (nextViewTop > extraScroll) {
- smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true);
- }
- }
- break;
- }
-
- case MOVE_UP_POS: {
- if (firstPos == mLastSeenPos) {
- // No new views, let things keep going.
- postOnAnimation(this);
- return;
- }
-
- final View firstView = getChildAt(0);
- if (firstView == null) {
- return;
- }
- final int firstViewTop = firstView.getTop();
- final int extraScroll = firstPos > 0 ?
- Math.max(mExtraScroll, mListPadding.top) : mListPadding.top;
-
- smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true);
-
- mLastSeenPos = firstPos;
-
- if (firstPos > mTargetPos) {
- postOnAnimation(this);
- }
- break;
- }
-
- case MOVE_UP_BOUND: {
- final int lastViewIndex = getChildCount() - 2;
- if (lastViewIndex < 0) {
- return;
- }
- final int lastPos = firstPos + lastViewIndex;
-
- if (lastPos == mLastSeenPos) {
- // No new views, let things keep going.
- postOnAnimation(this);
- return;
- }
-
- final View lastView = getChildAt(lastViewIndex);
- final int lastViewHeight = lastView.getHeight();
- final int lastViewTop = lastView.getTop();
- final int lastViewPixelsShowing = listHeight - lastViewTop;
- final int extraScroll = Math.max(mListPadding.top, mExtraScroll);
- mLastSeenPos = lastPos;
- if (lastPos > mBoundPos) {
- smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true);
- postOnAnimation(this);
- } else {
- final int bottom = listHeight - extraScroll;
- final int lastViewBottom = lastViewTop + lastViewHeight;
- if (bottom > lastViewBottom) {
- smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true);
- }
- }
- break;
- }
-
- case MOVE_OFFSET: {
- if (mLastSeenPos == firstPos) {
- // No new views, let things keep going.
- postOnAnimation(this);
- return;
- }
-
- mLastSeenPos = firstPos;
-
- final int childCount = getChildCount();
- final int position = mTargetPos;
- final int lastPos = firstPos + childCount - 1;
-
- int viewTravelCount = 0;
- if (position < firstPos) {
- viewTravelCount = firstPos - position + 1;
- } else if (position > lastPos) {
- viewTravelCount = position - lastPos;
- }
-
- // Estimate how many screens we should travel
- final float screenTravelCount = (float) viewTravelCount / childCount;
-
- final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
- if (position < firstPos) {
- final int distance = (int) (-getHeight() * modifier);
- final int duration = (int) (mScrollDuration * modifier);
- smoothScrollBy(distance, duration, true);
- postOnAnimation(this);
- } else if (position > lastPos) {
- final int distance = (int) (getHeight() * modifier);
- final int duration = (int) (mScrollDuration * modifier);
- smoothScrollBy(distance, duration, true);
- postOnAnimation(this);
- } else {
- // On-screen, just scroll.
- final int targetTop = getChildAt(position - firstPos).getTop();
- final int distance = targetTop - mOffsetFromTop;
- final int duration = (int) (mScrollDuration *
- ((float) Math.abs(distance) / getHeight()));
- smoothScrollBy(distance, duration, true);
- }
- break;
- }
-
- default:
- break;
- }
- }
- }
-
/**
* The amount of friction applied to flings. The default value
* is {@link ViewConfiguration#getScrollFriction}.
@@ -4837,13 +4412,20 @@
}
/**
+ * Override this for better control over position scrolling.
+ */
+ AbsPositionScroller createPositionScroller() {
+ return new PositionScroller();
+ }
+
+ /**
* Smoothly scroll to the specified adapter position. The view will
* scroll such that the indicated position is displayed.
* @param position Scroll to this adapter position.
*/
public void smoothScrollToPosition(int position) {
if (mPositionScroller == null) {
- mPositionScroller = new SubPositionScroller();
+ mPositionScroller = createPositionScroller();
}
mPositionScroller.start(position);
}
@@ -4862,7 +4444,7 @@
*/
public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
if (mPositionScroller == null) {
- mPositionScroller = new SubPositionScroller();
+ mPositionScroller = createPositionScroller();
}
mPositionScroller.startWithOffset(position, offset, duration);
}
@@ -4880,7 +4462,7 @@
*/
public void smoothScrollToPositionFromTop(int position, int offset) {
if (mPositionScroller == null) {
- mPositionScroller = new SubPositionScroller();
+ mPositionScroller = createPositionScroller();
}
mPositionScroller.startWithOffset(position, offset, offset);
}
@@ -4896,7 +4478,7 @@
*/
public void smoothScrollToPosition(int position, int boundPosition) {
if (mPositionScroller == null) {
- mPositionScroller = new SubPositionScroller();
+ mPositionScroller = createPositionScroller();
}
mPositionScroller.start(position, boundPosition);
}
@@ -6997,26 +6579,6 @@
}
/**
- * Returns the height of a row, which is computed as the maximum height of
- * the items in the row.
- *
- * @param row the row index
- * @return row height in pixels
- */
- private int getHeightForRow(int row) {
- final int firstRowPosition = getFirstPositionForRow(row);
- final int lastRowPosition = getFirstPositionForRow(row + 1);
- int maxHeight = 0;
- for (int i = firstRowPosition; i < lastRowPosition; i++) {
- final int height = getHeightForPosition(i);
- if (height > maxHeight) {
- maxHeight = height;
- }
- }
- return maxHeight;
- }
-
- /**
* Returns the height of the view for the specified position.
*
* @param position the item position
@@ -7026,10 +6588,12 @@
final int firstVisiblePosition = getFirstVisiblePosition();
final int childCount = getChildCount();
final int index = position - firstVisiblePosition;
- if (position >= 0 && position < childCount) {
+ if (index >= 0 && index < childCount) {
+ // Position is on-screen, use existing view.
final View view = getChildAt(index);
return view.getHeight();
} else {
+ // Position is off-screen, obtain & recycle view.
final View view = obtainView(position, mIsScrap);
view.measure(mWidthMeasureSpec, MeasureSpec.UNSPECIFIED);
final int height = view.getMeasuredHeight();
@@ -7039,26 +6603,6 @@
}
/**
- * Returns the row for the specified item position.
- *
- * @param position the item position
- * @return the row index
- */
- public int getRowForPosition(int position) {
- return position;
- }
-
- /**
- * Returns the first item position within the specified row.
- *
- * @param row the row
- * @return the item position
- */
- public int getFirstPositionForRow(int row) {
- return row;
- }
-
- /**
* Sets the selected item and positions the selection y pixels from the top edge
* of the ListView. (If in touch mode, the item will not be selected but it will
* still be positioned appropriately.)
@@ -7097,10 +6641,474 @@
}
}
- class SubPositionScroller {
+ /**
+ * Abstract positon scroller used to handle smooth scrolling.
+ */
+ static abstract class AbsPositionScroller {
+ public abstract void start(int position);
+ public abstract void start(int position, int boundPosition);
+ public abstract void startWithOffset(int position, int offset);
+ public abstract void startWithOffset(int position, int offset, int duration);
+ public abstract void stop();
+ }
+
+ /**
+ * Default position scroller that simulates a fling.
+ */
+ class PositionScroller extends AbsPositionScroller implements Runnable {
+ private static final int SCROLL_DURATION = 200;
+
+ private static final int MOVE_DOWN_POS = 1;
+ private static final int MOVE_UP_POS = 2;
+ private static final int MOVE_DOWN_BOUND = 3;
+ private static final int MOVE_UP_BOUND = 4;
+ private static final int MOVE_OFFSET = 5;
+
+ private int mMode;
+ private int mTargetPos;
+ private int mBoundPos;
+ private int mLastSeenPos;
+ private int mScrollDuration;
+ private final int mExtraScroll;
+
+ private int mOffsetFromTop;
+
+ PositionScroller() {
+ mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
+ }
+
+ @Override
+ public void start(final int position) {
+ stop();
+
+ if (mDataChanged) {
+ // Wait until we're back in a stable state to try this.
+ mPositionScrollAfterLayout = new Runnable() {
+ @Override public void run() {
+ start(position);
+ }
+ };
+ return;
+ }
+
+ final int childCount = getChildCount();
+ if (childCount == 0) {
+ // Can't scroll without children.
+ return;
+ }
+
+ final int firstPos = mFirstPosition;
+ final int lastPos = firstPos + childCount - 1;
+
+ int viewTravelCount;
+ int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
+ if (clampedPosition < firstPos) {
+ viewTravelCount = firstPos - clampedPosition + 1;
+ mMode = MOVE_UP_POS;
+ } else if (clampedPosition > lastPos) {
+ viewTravelCount = clampedPosition - lastPos + 1;
+ mMode = MOVE_DOWN_POS;
+ } else {
+ scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION);
+ return;
+ }
+
+ if (viewTravelCount > 0) {
+ mScrollDuration = SCROLL_DURATION / viewTravelCount;
+ } else {
+ mScrollDuration = SCROLL_DURATION;
+ }
+ mTargetPos = clampedPosition;
+ mBoundPos = INVALID_POSITION;
+ mLastSeenPos = INVALID_POSITION;
+
+ postOnAnimation(this);
+ }
+
+ @Override
+ public void start(final int position, final int boundPosition) {
+ stop();
+
+ if (boundPosition == INVALID_POSITION) {
+ start(position);
+ return;
+ }
+
+ if (mDataChanged) {
+ // Wait until we're back in a stable state to try this.
+ mPositionScrollAfterLayout = new Runnable() {
+ @Override public void run() {
+ start(position, boundPosition);
+ }
+ };
+ return;
+ }
+
+ final int childCount = getChildCount();
+ if (childCount == 0) {
+ // Can't scroll without children.
+ return;
+ }
+
+ final int firstPos = mFirstPosition;
+ final int lastPos = firstPos + childCount - 1;
+
+ int viewTravelCount;
+ int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
+ if (clampedPosition < firstPos) {
+ final int boundPosFromLast = lastPos - boundPosition;
+ if (boundPosFromLast < 1) {
+ // Moving would shift our bound position off the screen. Abort.
+ return;
+ }
+
+ final int posTravel = firstPos - clampedPosition + 1;
+ final int boundTravel = boundPosFromLast - 1;
+ if (boundTravel < posTravel) {
+ viewTravelCount = boundTravel;
+ mMode = MOVE_UP_BOUND;
+ } else {
+ viewTravelCount = posTravel;
+ mMode = MOVE_UP_POS;
+ }
+ } else if (clampedPosition > lastPos) {
+ final int boundPosFromFirst = boundPosition - firstPos;
+ if (boundPosFromFirst < 1) {
+ // Moving would shift our bound position off the screen. Abort.
+ return;
+ }
+
+ final int posTravel = clampedPosition - lastPos + 1;
+ final int boundTravel = boundPosFromFirst - 1;
+ if (boundTravel < posTravel) {
+ viewTravelCount = boundTravel;
+ mMode = MOVE_DOWN_BOUND;
+ } else {
+ viewTravelCount = posTravel;
+ mMode = MOVE_DOWN_POS;
+ }
+ } else {
+ scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
+ return;
+ }
+
+ if (viewTravelCount > 0) {
+ mScrollDuration = SCROLL_DURATION / viewTravelCount;
+ } else {
+ mScrollDuration = SCROLL_DURATION;
+ }
+ mTargetPos = clampedPosition;
+ mBoundPos = boundPosition;
+ mLastSeenPos = INVALID_POSITION;
+
+ postOnAnimation(this);
+ }
+
+ @Override
+ public void startWithOffset(int position, int offset) {
+ startWithOffset(position, offset, SCROLL_DURATION);
+ }
+
+ @Override
+ public void startWithOffset(final int position, int offset, final int duration) {
+ stop();
+
+ if (mDataChanged) {
+ // Wait until we're back in a stable state to try this.
+ final int postOffset = offset;
+ mPositionScrollAfterLayout = new Runnable() {
+ @Override public void run() {
+ startWithOffset(position, postOffset, duration);
+ }
+ };
+ return;
+ }
+
+ final int childCount = getChildCount();
+ if (childCount == 0) {
+ // Can't scroll without children.
+ return;
+ }
+
+ offset += getPaddingTop();
+
+ mTargetPos = Math.max(0, Math.min(getCount() - 1, position));
+ mOffsetFromTop = offset;
+ mBoundPos = INVALID_POSITION;
+ mLastSeenPos = INVALID_POSITION;
+ mMode = MOVE_OFFSET;
+
+ final int firstPos = mFirstPosition;
+ final int lastPos = firstPos + childCount - 1;
+
+ int viewTravelCount;
+ if (mTargetPos < firstPos) {
+ viewTravelCount = firstPos - mTargetPos;
+ } else if (mTargetPos > lastPos) {
+ viewTravelCount = mTargetPos - lastPos;
+ } else {
+ // On-screen, just scroll.
+ final int targetTop = getChildAt(mTargetPos - firstPos).getTop();
+ smoothScrollBy(targetTop - offset, duration, true);
+ return;
+ }
+
+ // Estimate how many screens we should travel
+ final float screenTravelCount = (float) viewTravelCount / childCount;
+ mScrollDuration = screenTravelCount < 1 ?
+ duration : (int) (duration / screenTravelCount);
+ mLastSeenPos = INVALID_POSITION;
+
+ postOnAnimation(this);
+ }
+
+ /**
+ * Scroll such that targetPos is in the visible padded region without scrolling
+ * boundPos out of view. Assumes targetPos is onscreen.
+ */
+ private void scrollToVisible(int targetPos, int boundPos, int duration) {
+ final int firstPos = mFirstPosition;
+ final int childCount = getChildCount();
+ final int lastPos = firstPos + childCount - 1;
+ final int paddedTop = mListPadding.top;
+ final int paddedBottom = getHeight() - mListPadding.bottom;
+
+ if (targetPos < firstPos || targetPos > lastPos) {
+ Log.w(TAG, "scrollToVisible called with targetPos " + targetPos +
+ " not visible [" + firstPos + ", " + lastPos + "]");
+ }
+ if (boundPos < firstPos || boundPos > lastPos) {
+ // boundPos doesn't matter, it's already offscreen.
+ boundPos = INVALID_POSITION;
+ }
+
+ final View targetChild = getChildAt(targetPos - firstPos);
+ final int targetTop = targetChild.getTop();
+ final int targetBottom = targetChild.getBottom();
+ int scrollBy = 0;
+
+ if (targetBottom > paddedBottom) {
+ scrollBy = targetBottom - paddedBottom;
+ }
+ if (targetTop < paddedTop) {
+ scrollBy = targetTop - paddedTop;
+ }
+
+ if (scrollBy == 0) {
+ return;
+ }
+
+ if (boundPos >= 0) {
+ final View boundChild = getChildAt(boundPos - firstPos);
+ final int boundTop = boundChild.getTop();
+ final int boundBottom = boundChild.getBottom();
+ final int absScroll = Math.abs(scrollBy);
+
+ if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) {
+ // Don't scroll the bound view off the bottom of the screen.
+ scrollBy = Math.max(0, boundBottom - paddedBottom);
+ } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) {
+ // Don't scroll the bound view off the top of the screen.
+ scrollBy = Math.min(0, boundTop - paddedTop);
+ }
+ }
+
+ smoothScrollBy(scrollBy, duration);
+ }
+
+ @Override
+ public void stop() {
+ removeCallbacks(this);
+ }
+
+ @Override
+ public void run() {
+ final int listHeight = getHeight();
+ final int firstPos = mFirstPosition;
+
+ switch (mMode) {
+ case MOVE_DOWN_POS: {
+ final int lastViewIndex = getChildCount() - 1;
+ final int lastPos = firstPos + lastViewIndex;
+
+ if (lastViewIndex < 0) {
+ return;
+ }
+
+ if (lastPos == mLastSeenPos) {
+ // No new views, let things keep going.
+ postOnAnimation(this);
+ return;
+ }
+
+ final View lastView = getChildAt(lastViewIndex);
+ final int lastViewHeight = lastView.getHeight();
+ final int lastViewTop = lastView.getTop();
+ final int lastViewPixelsShowing = listHeight - lastViewTop;
+ final int extraScroll = lastPos < mItemCount - 1 ?
+ Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom;
+
+ final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll;
+ smoothScrollBy(scrollBy, mScrollDuration, true);
+
+ mLastSeenPos = lastPos;
+ if (lastPos < mTargetPos) {
+ postOnAnimation(this);
+ }
+ break;
+ }
+
+ case MOVE_DOWN_BOUND: {
+ final int nextViewIndex = 1;
+ final int childCount = getChildCount();
+
+ if (firstPos == mBoundPos || childCount <= nextViewIndex
+ || firstPos + childCount >= mItemCount) {
+ return;
+ }
+ final int nextPos = firstPos + nextViewIndex;
+
+ if (nextPos == mLastSeenPos) {
+ // No new views, let things keep going.
+ postOnAnimation(this);
+ return;
+ }
+
+ final View nextView = getChildAt(nextViewIndex);
+ final int nextViewHeight = nextView.getHeight();
+ final int nextViewTop = nextView.getTop();
+ final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll);
+ if (nextPos < mBoundPos) {
+ smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
+ mScrollDuration, true);
+
+ mLastSeenPos = nextPos;
+
+ postOnAnimation(this);
+ } else {
+ if (nextViewTop > extraScroll) {
+ smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true);
+ }
+ }
+ break;
+ }
+
+ case MOVE_UP_POS: {
+ if (firstPos == mLastSeenPos) {
+ // No new views, let things keep going.
+ postOnAnimation(this);
+ return;
+ }
+
+ final View firstView = getChildAt(0);
+ if (firstView == null) {
+ return;
+ }
+ final int firstViewTop = firstView.getTop();
+ final int extraScroll = firstPos > 0 ?
+ Math.max(mExtraScroll, mListPadding.top) : mListPadding.top;
+
+ smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true);
+
+ mLastSeenPos = firstPos;
+
+ if (firstPos > mTargetPos) {
+ postOnAnimation(this);
+ }
+ break;
+ }
+
+ case MOVE_UP_BOUND: {
+ final int lastViewIndex = getChildCount() - 2;
+ if (lastViewIndex < 0) {
+ return;
+ }
+ final int lastPos = firstPos + lastViewIndex;
+
+ if (lastPos == mLastSeenPos) {
+ // No new views, let things keep going.
+ postOnAnimation(this);
+ return;
+ }
+
+ final View lastView = getChildAt(lastViewIndex);
+ final int lastViewHeight = lastView.getHeight();
+ final int lastViewTop = lastView.getTop();
+ final int lastViewPixelsShowing = listHeight - lastViewTop;
+ final int extraScroll = Math.max(mListPadding.top, mExtraScroll);
+ mLastSeenPos = lastPos;
+ if (lastPos > mBoundPos) {
+ smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true);
+ postOnAnimation(this);
+ } else {
+ final int bottom = listHeight - extraScroll;
+ final int lastViewBottom = lastViewTop + lastViewHeight;
+ if (bottom > lastViewBottom) {
+ smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true);
+ }
+ }
+ break;
+ }
+
+ case MOVE_OFFSET: {
+ if (mLastSeenPos == firstPos) {
+ // No new views, let things keep going.
+ postOnAnimation(this);
+ return;
+ }
+
+ mLastSeenPos = firstPos;
+
+ final int childCount = getChildCount();
+ final int position = mTargetPos;
+ final int lastPos = firstPos + childCount - 1;
+
+ int viewTravelCount = 0;
+ if (position < firstPos) {
+ viewTravelCount = firstPos - position + 1;
+ } else if (position > lastPos) {
+ viewTravelCount = position - lastPos;
+ }
+
+ // Estimate how many screens we should travel
+ final float screenTravelCount = (float) viewTravelCount / childCount;
+
+ final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
+ if (position < firstPos) {
+ final int distance = (int) (-getHeight() * modifier);
+ final int duration = (int) (mScrollDuration * modifier);
+ smoothScrollBy(distance, duration, true);
+ postOnAnimation(this);
+ } else if (position > lastPos) {
+ final int distance = (int) (getHeight() * modifier);
+ final int duration = (int) (mScrollDuration * modifier);
+ smoothScrollBy(distance, duration, true);
+ postOnAnimation(this);
+ } else {
+ // On-screen, just scroll.
+ final int targetTop = getChildAt(position - firstPos).getTop();
+ final int distance = targetTop - mOffsetFromTop;
+ final int duration = (int) (mScrollDuration *
+ ((float) Math.abs(distance) / getHeight()));
+ smoothScrollBy(distance, duration, true);
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * Abstract position scroller that handles sub-position scrolling but has no
+ * understanding of layout.
+ */
+ abstract class AbsSubPositionScroller extends AbsPositionScroller {
private static final int DEFAULT_SCROLL_DURATION = 200;
- private SubScroller mSubScroller;
+ private final SubScroller mSubScroller = new SubScroller();
/**
* The target offset in pixels between the top of the list and the top
@@ -7171,9 +7179,6 @@
return;
}
- if (mSubScroller == null) {
- mSubScroller = new SubScroller();
- }
mSubScroller.startScroll(startSubRow, endSubRow, duration);
postOnAnimation(mAnimationFrame);
@@ -7228,28 +7233,67 @@
return Math.max(boundSubRow, targetSubRow);
}
- public void start(int position, int boundPosition) {
- scrollToPosition(position, false, 0, boundPosition, DEFAULT_SCROLL_DURATION);
- }
-
- public void startWithOffset(int position, int offset, int duration) {
- scrollToPosition(position, true, offset, INVALID_POSITION, duration);
- }
-
+ @Override
public void start(int position) {
scrollToPosition(position, false, 0, INVALID_POSITION, DEFAULT_SCROLL_DURATION);
}
+ @Override
+ public void start(int position, int boundPosition) {
+ scrollToPosition(position, false, 0, boundPosition, DEFAULT_SCROLL_DURATION);
+ }
+
+ @Override
+ public void startWithOffset(int position, int offset) {
+ scrollToPosition(position, true, offset, INVALID_POSITION, DEFAULT_SCROLL_DURATION);
+ }
+
+ @Override
+ public void startWithOffset(int position, int offset, int duration) {
+ scrollToPosition(position, true, offset, INVALID_POSITION, duration);
+ }
+
+ @Override
public void stop() {
removeCallbacks(mAnimationFrame);
}
+ /**
+ * Returns the height of a row, which is computed as the maximum height of
+ * the items in the row.
+ *
+ * @param row the row index
+ * @return row height in pixels
+ */
+ public abstract int getHeightForRow(int row);
+
+ /**
+ * Returns the row for the specified item position.
+ *
+ * @param position the item position
+ * @return the row index
+ */
+ public abstract int getRowForPosition(int position);
+
+ /**
+ * Returns the first item position within the specified row.
+ *
+ * @param row the row
+ * @return the position of the first item in the row
+ */
+ public abstract int getFirstPositionForRow(int row);
+
private void onAnimationFrame() {
final boolean shouldPost = mSubScroller.computePosition();
final float subRow = mSubScroller.getPosition();
final int row = (int) subRow;
final int position = getFirstPositionForRow(row);
+ if (position >= getCount()) {
+ // Invalid position, abort scrolling.
+ return;
+ }
+
final int rowHeight = getHeightForRow(row);
final int offset = (int) (rowHeight * (subRow - row));
final int addOffset = (int) (mOffset * mSubScroller.getInterpolatedValue());
@@ -7271,7 +7315,7 @@
/**
* Scroller capable of returning floating point positions.
*/
- private static class SubScroller {
+ static class SubScroller {
private final Interpolator mInterpolator;
private float mStartPosition;
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index 0b424f7..6743002 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -35,6 +35,8 @@
import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo;
import android.view.animation.GridLayoutAnimationController;
+import android.widget.AbsListView.AbsPositionScroller;
+import android.widget.ListView.ListViewPositionScroller;
import android.widget.RemoteViews.RemoteView;
import java.lang.annotation.Retention;
@@ -1027,13 +1029,8 @@
}
@Override
- public int getRowForPosition(int position) {
- return position / mNumColumns;
- }
-
- @Override
- public int getFirstPositionForRow(int row) {
- return row * mNumColumns;
+ AbsPositionScroller createPositionScroller() {
+ return new GridViewPositionScroller();
}
@Override
@@ -2327,7 +2324,9 @@
final int columnsCount = getNumColumns();
final int rowsCount = getCount() / columnsCount;
- final CollectionInfo collectionInfo = CollectionInfo.obtain(columnsCount, rowsCount, false);
+ final int selectionMode = getSelectionModeForAccessibility();
+ final CollectionInfo collectionInfo = CollectionInfo.obtain(
+ columnsCount, rowsCount, false, selectionMode);
info.setCollectionInfo(collectionInfo);
}
@@ -2354,7 +2353,38 @@
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
final boolean isHeading = lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
- final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(column, 1, row, 1, isHeading);
+ final boolean isSelected = isItemChecked(position);
+ final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(
+ column, 1, row, 1, isHeading, isSelected);
info.setCollectionItemInfo(itemInfo);
}
+
+ /**
+ * Sub-position scroller that understands the layout of a GridView.
+ */
+ class GridViewPositionScroller extends AbsSubPositionScroller {
+ @Override
+ public int getRowForPosition(int position) {
+ return position / mNumColumns;
+ }
+
+ @Override
+ public int getFirstPositionForRow(int row) {
+ return row * mNumColumns;
+ }
+
+ @Override
+ public int getHeightForRow(int row) {
+ final int firstRowPosition = row * mNumColumns;
+ final int lastRowPosition = Math.min(getCount(), firstRowPosition + mNumColumns);
+ int maxHeight = 0;
+ for (int i = firstRowPosition; i < lastRowPosition; i++) {
+ final int height = getHeightForPosition(i);
+ if (height > maxHeight) {
+ maxHeight = height;
+ }
+ }
+ return maxHeight;
+ }
+ }
}
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index f937cd6..cef2138 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -3775,13 +3775,8 @@
}
@Override
- public int getRowForPosition(int position) {
- return position;
- }
-
- @Override
- public int getFirstPositionForRow(int row) {
- return row;
+ AbsPositionScroller createPositionScroller() {
+ return new ListViewPositionScroller();
}
@Override
@@ -3796,7 +3791,8 @@
info.setClassName(ListView.class.getName());
final int count = getCount();
- final CollectionInfo collectionInfo = CollectionInfo.obtain(1, count, false);
+ final int selectionMode = getSelectionModeForAccessibility();
+ final CollectionInfo collectionInfo = CollectionInfo.obtain(1, count, false, selectionMode);
info.setCollectionInfo(collectionInfo);
}
@@ -3807,7 +3803,29 @@
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
final boolean isHeading = lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
- final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(0, 1, position, 1, isHeading);
+ final boolean isSelected = isItemChecked(position);
+ final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(
+ 0, 1, position, 1, isHeading, isSelected);
info.setCollectionItemInfo(itemInfo);
}
+
+ /**
+ * Sub-position scroller that understands the layout of a ListView.
+ */
+ class ListViewPositionScroller extends AbsSubPositionScroller {
+ @Override
+ public int getRowForPosition(int position) {
+ return position;
+ }
+
+ @Override
+ public int getFirstPositionForRow(int row) {
+ return row;
+ }
+
+ @Override
+ public int getHeightForRow(int row) {
+ return getHeightForPosition(row);
+ }
+ }
}
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 9a04760..18d5668 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -51,6 +51,7 @@
void noteScreenOff();
void noteInputEvent();
void noteUserActivity(int uid, int event);
+ void noteDataConnectionActive(String label, boolean active);
void notePhoneOn();
void notePhoneOff();
void notePhoneSignalStrength(in SignalStrength signalStrength);
@@ -63,6 +64,7 @@
void noteWifiStopped(in WorkSource ws);
void noteBluetoothOn();
void noteBluetoothOff();
+ void noteBluetoothActiveState(int actType);
void noteFullWifiLockAcquired(int uid);
void noteFullWifiLockReleased(int uid);
void noteWifiScanStarted(int uid);
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index c22a5e9..8a15c99 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -640,7 +640,8 @@
final long mobileTx = mStats.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType);
final long mobileData = mobileRx + mobileTx;
- final long radioDataUptimeMs = mStats.getRadioDataUptime() / 1000;
+ final long radioDataUptimeMs
+ = mStats.getMobileRadioActiveTime(mBatteryRealtime, mStatsType) / 1000;
final double mobilePps = radioDataUptimeMs != 0
? mobileData / (double)radioDataUptimeMs
: (((double)MOBILE_BPS) / 8 / 2048);
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 82dcbdb..e50e281 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -55,11 +55,9 @@
import com.android.internal.util.JournaledFile;
import com.google.android.collect.Sets;
-import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
-import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -88,7 +86,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 79 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 84 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS = 2000;
@@ -276,6 +274,13 @@
boolean mBluetoothOn;
StopwatchTimer mBluetoothOnTimer;
+ int mBluetoothActiveType = -1;
+ final StopwatchTimer[] mBluetoothActiveTimer =
+ new StopwatchTimer[NUM_BLUETOOTH_ACTIVE_TYPES];
+
+ boolean mMobileRadioActive;
+ StopwatchTimer mMobileRadioActiveTimer;
+
/** Bluetooth headset object */
BluetoothHeadset mBtHeadset;
@@ -310,9 +315,6 @@
long mLastWriteTime = 0; // Milliseconds
- private long mRadioDataUptime;
- private long mRadioDataStart;
-
private int mBluetoothPingCount;
private int mBluetoothPingStart = -1;
@@ -1432,44 +1434,6 @@
return kwlt;
}
- /**
- * Radio uptime in microseconds when transferring data. This value is very approximate.
- * @return
- */
- private long getCurrentRadioDataUptime() {
- try {
- File awakeTimeFile = new File("/sys/devices/virtual/net/rmnet0/awake_time_ms");
- if (!awakeTimeFile.exists()) return 0;
- BufferedReader br = new BufferedReader(new FileReader(awakeTimeFile));
- String line = br.readLine();
- br.close();
- return Long.parseLong(line) * 1000;
- } catch (NumberFormatException nfe) {
- // Nothing
- } catch (IOException ioe) {
- // Nothing
- }
- return 0;
- }
-
- /**
- * @deprecated use getRadioDataUptime
- */
- public long getRadioDataUptimeMs() {
- return getRadioDataUptime() / 1000;
- }
-
- /**
- * Returns the duration that the cell radio was up for data transfers.
- */
- public long getRadioDataUptime() {
- if (mRadioDataStart == -1) {
- return mRadioDataUptime;
- } else {
- return getCurrentRadioDataUptime() - mRadioDataStart;
- }
- }
-
private int getCurrentBluetoothPingCount() {
if (mBtHeadset != null) {
List<BluetoothDevice> deviceList = mBtHeadset.getConnectedDevices();
@@ -1520,14 +1484,16 @@
}
// Part of initial delta int that specifies the time delta.
- static final int DELTA_TIME_MASK = 0xfffff;
- static final int DELTA_TIME_LONG = 0xfffff; // The delta is a following long
- static final int DELTA_TIME_INT = 0xffffe; // The delta is a following int
- static final int DELTA_TIME_ABS = 0xffffd; // Following is an entire abs update.
+ static final int DELTA_TIME_MASK = 0x7ffff;
+ static final int DELTA_TIME_LONG = 0x7ffff; // The delta is a following long
+ static final int DELTA_TIME_INT = 0x7fffe; // The delta is a following int
+ static final int DELTA_TIME_ABS = 0x7fffd; // Following is an entire abs update.
// Flag in delta int: a new battery level int follows.
- static final int DELTA_BATTERY_LEVEL_FLAG = 0x00100000;
+ static final int DELTA_BATTERY_LEVEL_FLAG = 0x00080000;
// Flag in delta int: a new full state and battery status int follows.
- static final int DELTA_STATE_FLAG = 0x00200000;
+ static final int DELTA_STATE_FLAG = 0x00100000;
+ // Flag in delta int: a new full state2 int follows.
+ static final int DELTA_STATE2_FLAG = 0x00200000;
// Flag in delta int: contains a wakelock tag.
static final int DELTA_WAKELOCK_FLAG = 0x00400000;
// Flag in delta int: contains an event description.
@@ -1963,10 +1929,6 @@
mUnpluggables.get(i).unplug(elapsedRealtime, batteryUptime, batteryRealtime);
}
- // Track radio awake time
- mRadioDataStart = getCurrentRadioDataUptime();
- mRadioDataUptime = 0;
-
// Track bt headset ping count
mBluetoothPingStart = getCurrentBluetoothPingCount();
mBluetoothPingCount = 0;
@@ -1977,10 +1939,6 @@
mUnpluggables.get(i).plug(elapsedRealtime, batteryUptime, batteryRealtime);
}
- // Track radio awake time
- mRadioDataUptime = getRadioDataUptime();
- mRadioDataStart = -1;
-
// Track bt headset ping count
mBluetoothPingCount = getBluetoothPingCount();
mBluetoothPingStart = -1;
@@ -2378,6 +2336,27 @@
getUidStatsLocked(uid).noteUserActivityLocked(event);
}
+ public void noteDataConnectionActive(String label, boolean active) {
+ try {
+ int type = Integer.parseInt(label);
+ if (ConnectivityManager.isNetworkTypeMobile(type)) {
+ if (mMobileRadioActive != active) {
+ if (active) mHistoryCur.states |= HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG;
+ else mHistoryCur.states &= ~HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Mobile network active " + active + " to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ mMobileRadioActive = active;
+ if (active) mMobileRadioActiveTimer.startRunningLocked(this);
+ else mMobileRadioActiveTimer.stopRunningLocked(this);
+ }
+ }
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Bad data connection label: " + label, e);
+ return;
+ }
+ }
+
public void notePhoneOnLocked() {
if (!mPhoneOn) {
mHistoryCur.states |= HistoryItem.STATE_PHONE_IN_CALL_FLAG;
@@ -2763,6 +2742,17 @@
}
}
+ public void noteBluetoothActiveStateLocked(int actType) {
+ if (DEBUG) Log.i(TAG, "Bluetooth active -> " + actType);
+ if (mBluetoothActiveType != actType) {
+ if (mBluetoothActiveType >= 0) {
+ mBluetoothActiveTimer[mBluetoothActiveType].stopRunningLocked(this);
+ }
+ mBluetoothActiveType = actType;
+ mBluetoothActiveTimer[actType].startRunningLocked(this);
+ }
+ }
+
int mWifiFullLockNesting = 0;
public void noteFullWifiLockAcquiredLocked(int uid) {
@@ -2971,6 +2961,10 @@
return mPhoneDataConnectionsTimer[dataType].getCountLocked(which);
}
+ @Override public long getMobileRadioActiveTime(long batteryRealtime, int which) {
+ return mMobileRadioActiveTimer.getTotalTimeLocked(batteryRealtime, which);
+ }
+
@Override public long getWifiOnTime(long batteryRealtime, int which) {
return mWifiOnTimer.getTotalTimeLocked(batteryRealtime, which);
}
@@ -2983,6 +2977,16 @@
return mBluetoothOnTimer.getTotalTimeLocked(batteryRealtime, which);
}
+ @Override public long getBluetoothActiveTime(int actType,
+ long batteryRealtime, int which) {
+ return mBluetoothActiveTimer[actType].getTotalTimeLocked(
+ batteryRealtime, which);
+ }
+
+ @Override public int getBluetoothActiveCount(int actType, int which) {
+ return mBluetoothActiveTimer[actType].getCountLocked(which);
+ }
+
@Override
public long getNetworkActivityBytes(int type, int which) {
if (type >= 0 && type < mNetworkByteActivityCounters.length) {
@@ -4966,9 +4970,13 @@
mNetworkByteActivityCounters[i] = new LongSamplingCounter(mUnpluggables);
mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mUnpluggables);
}
+ mMobileRadioActiveTimer = new StopwatchTimer(null, -400, null, mUnpluggables);
mWifiOnTimer = new StopwatchTimer(null, -3, null, mUnpluggables);
mGlobalWifiRunningTimer = new StopwatchTimer(null, -4, null, mUnpluggables);
mBluetoothOnTimer = new StopwatchTimer(null, -5, null, mUnpluggables);
+ for (int i=0; i<NUM_BLUETOOTH_ACTIVE_TYPES; i++) {
+ mBluetoothActiveTimer[i] = new StopwatchTimer(null, -500-i, null, mUnpluggables);
+ }
mAudioOnTimer = new StopwatchTimer(null, -6, null, mUnpluggables);
mVideoOnTimer = new StopwatchTimer(null, -7, null, mUnpluggables);
mOnBattery = mOnBatteryInternal = false;
@@ -5225,9 +5233,13 @@
mNetworkByteActivityCounters[i].reset(false);
mNetworkPacketActivityCounters[i].reset(false);
}
+ mMobileRadioActiveTimer.reset(this, false);
mWifiOnTimer.reset(this, false);
mGlobalWifiRunningTimer.reset(this, false);
mBluetoothOnTimer.reset(this, false);
+ for (int i=0; i<NUM_BLUETOOTH_ACTIVE_TYPES; i++) {
+ mBluetoothActiveTimer[i].reset(this, false);
+ }
for (int i=0; i<mUidStats.size(); i++) {
if (mUidStats.valueAt(i).reset()) {
@@ -6119,12 +6131,17 @@
mNetworkByteActivityCounters[i].readSummaryFromParcelLocked(in);
mNetworkPacketActivityCounters[i].readSummaryFromParcelLocked(in);
}
+ mMobileRadioActive = false;
+ mMobileRadioActiveTimer.readSummaryFromParcelLocked(in);
mWifiOn = false;
mWifiOnTimer.readSummaryFromParcelLocked(in);
mGlobalWifiRunning = false;
mGlobalWifiRunningTimer.readSummaryFromParcelLocked(in);
mBluetoothOn = false;
mBluetoothOnTimer.readSummaryFromParcelLocked(in);
+ for (int i=0; i<NUM_BLUETOOTH_ACTIVE_TYPES; i++) {
+ mBluetoothActiveTimer[i].readSummaryFromParcelLocked(in);
+ }
int NKW = in.readInt();
if (NKW > 10000) {
@@ -6340,9 +6357,13 @@
mNetworkByteActivityCounters[i].writeSummaryFromParcelLocked(out);
mNetworkPacketActivityCounters[i].writeSummaryFromParcelLocked(out);
}
+ mMobileRadioActiveTimer.writeSummaryFromParcelLocked(out, NOWREAL);
mWifiOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
mGlobalWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL);
mBluetoothOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ for (int i=0; i<NUM_BLUETOOTH_ACTIVE_TYPES; i++) {
+ mBluetoothActiveTimer[i].writeSummaryFromParcelLocked(out, NOWREAL);
+ }
out.writeInt(mKernelWakelockStats.size());
for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) {
@@ -6574,12 +6595,18 @@
mNetworkByteActivityCounters[i] = new LongSamplingCounter(mUnpluggables, in);
mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mUnpluggables, in);
}
+ mMobileRadioActive = false;
+ mMobileRadioActiveTimer = new StopwatchTimer(null, -400, null, mUnpluggables, in);
mWifiOn = false;
mWifiOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in);
mGlobalWifiRunning = false;
mGlobalWifiRunningTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in);
mBluetoothOn = false;
mBluetoothOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in);
+ for (int i=0; i<NUM_BLUETOOTH_ACTIVE_TYPES; i++) {
+ mBluetoothActiveTimer[i] = new StopwatchTimer(null, -500-i,
+ null, mUnpluggables, in);
+ }
mUptime = in.readLong();
mUptimeStart = in.readLong();
mLastUptime = 0;
@@ -6604,9 +6631,6 @@
mDischargeAmountScreenOffSinceCharge = in.readInt();
mLastWriteTime = in.readLong();
- mRadioDataUptime = in.readLong();
- mRadioDataStart = -1;
-
mBluetoothPingCount = in.readInt();
mBluetoothPingStart = -1;
@@ -6685,9 +6709,13 @@
mNetworkByteActivityCounters[i].writeToParcel(out);
mNetworkPacketActivityCounters[i].writeToParcel(out);
}
+ mMobileRadioActiveTimer.writeToParcel(out, batteryRealtime);
mWifiOnTimer.writeToParcel(out, batteryRealtime);
mGlobalWifiRunningTimer.writeToParcel(out, batteryRealtime);
mBluetoothOnTimer.writeToParcel(out, batteryRealtime);
+ for (int i=0; i<NUM_BLUETOOTH_ACTIVE_TYPES; i++) {
+ mBluetoothActiveTimer[i].writeToParcel(out, batteryRealtime);
+ }
out.writeLong(mUptime);
out.writeLong(mUptimeStart);
out.writeLong(mRealtime);
@@ -6709,9 +6737,6 @@
out.writeInt(mDischargeAmountScreenOffSinceCharge);
out.writeLong(mLastWriteTime);
- // Write radio uptime for data
- out.writeLong(getRadioDataUptime());
-
out.writeInt(getBluetoothPingCount());
if (inclUids) {
@@ -6786,12 +6811,18 @@
pr.println("*** Data connection type #" + i + ":");
mPhoneDataConnectionsTimer[i].logState(pr, " ");
}
+ pr.println("*** Mobile network active timer:");
+ mMobileRadioActiveTimer.logState(pr, " ");
pr.println("*** Wifi timer:");
mWifiOnTimer.logState(pr, " ");
pr.println("*** WifiRunning timer:");
mGlobalWifiRunningTimer.logState(pr, " ");
pr.println("*** Bluetooth timer:");
mBluetoothOnTimer.logState(pr, " ");
+ for (int i=0; i<NUM_BLUETOOTH_ACTIVE_TYPES; i++) {
+ pr.println("*** Bluetooth active type #" + i + ":");
+ mBluetoothActiveTimer[i].logState(pr, " ");
+ }
}
super.dumpLocked(context, pw, isUnpluggedOnly, reqUid, historyOnly);
}
diff --git a/core/java/com/android/server/SystemService.java b/core/java/com/android/server/SystemService.java
index 0c89f94..194a084 100644
--- a/core/java/com/android/server/SystemService.java
+++ b/core/java/com/android/server/SystemService.java
@@ -85,14 +85,6 @@
}
/**
- * Services are not yet available. This is a good place to do setup work that does
- * not require other services.
- *
- * @param context The system context.
- */
- public void onCreate(Context context) {}
-
- /**
* Called when the dependencies listed in the @Service class-annotation are available
* and after the chosen start phase.
* When this method returns, the service should be published.
diff --git a/core/jni/android_view_SurfaceSession.cpp b/core/jni/android_view_SurfaceSession.cpp
index 87e339c..609c565 100644
--- a/core/jni/android_view_SurfaceSession.cpp
+++ b/core/jni/android_view_SurfaceSession.cpp
@@ -35,22 +35,22 @@
sp<SurfaceComposerClient> android_view_SurfaceSession_getClient(
JNIEnv* env, jobject surfaceSessionObj) {
return reinterpret_cast<SurfaceComposerClient*>(
- env->GetIntField(surfaceSessionObj, gSurfaceSessionClassInfo.mNativeClient));
+ env->GetLongField(surfaceSessionObj, gSurfaceSessionClassInfo.mNativeClient));
}
-static jint nativeCreate(JNIEnv* env, jclass clazz) {
+static jlong nativeCreate(JNIEnv* env, jclass clazz) {
SurfaceComposerClient* client = new SurfaceComposerClient();
client->incStrong((void*)nativeCreate);
- return reinterpret_cast<jint>(client);
+ return reinterpret_cast<jlong>(client);
}
-static void nativeDestroy(JNIEnv* env, jclass clazz, jint ptr) {
+static void nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) {
SurfaceComposerClient* client = reinterpret_cast<SurfaceComposerClient*>(ptr);
client->decStrong((void*)nativeCreate);
}
-static void nativeKill(JNIEnv* env, jclass clazz, jint ptr) {
+static void nativeKill(JNIEnv* env, jclass clazz, jlong ptr) {
SurfaceComposerClient* client = reinterpret_cast<SurfaceComposerClient*>(ptr);
client->dispose();
}
@@ -58,11 +58,11 @@
static JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
- { "nativeCreate", "()I",
+ { "nativeCreate", "()J",
(void*)nativeCreate },
- { "nativeDestroy", "(I)V",
+ { "nativeDestroy", "(J)V",
(void*)nativeDestroy },
- { "nativeKill", "(I)V",
+ { "nativeKill", "(J)V",
(void*)nativeKill }
};
@@ -72,7 +72,7 @@
LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
jclass clazz = env->FindClass("android/view/SurfaceSession");
- gSurfaceSessionClassInfo.mNativeClient = env->GetFieldID(clazz, "mNativeClient", "I");
+ gSurfaceSessionClassInfo.mNativeClient = env->GetFieldID(clazz, "mNativeClient", "J");
return 0;
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 9349730..ba08a2e 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -2352,7 +2352,7 @@
if (ConnectivityManager.isNetworkTypeMobile(type)) {
timeout = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE,
- 0);
+ 5);
// Canonicalize mobile network type
type = ConnectivityManager.TYPE_MOBILE;
} else if (ConnectivityManager.TYPE_WIFI == type) {
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index eebd1c5..fc68205 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -73,6 +73,9 @@
import com.android.server.location.LocationFudger;
import com.android.server.location.LocationProviderInterface;
import com.android.server.location.LocationProviderProxy;
+import com.android.server.location.LocationRequestStatistics;
+import com.android.server.location.LocationRequestStatistics.PackageProviderKey;
+import com.android.server.location.LocationRequestStatistics.PackageStatistics;
import com.android.server.location.MockProvider;
import com.android.server.location.PassiveProvider;
@@ -178,6 +181,8 @@
private final HashMap<String, ArrayList<UpdateRecord>> mRecordsByProvider =
new HashMap<String, ArrayList<UpdateRecord>>();
+ private final LocationRequestStatistics mRequestStatistics = new LocationRequestStatistics();
+
// mapping from provider name to last known location
private final HashMap<String, Location> mLastLocation = new HashMap<String, Location>();
@@ -568,7 +573,7 @@
if (isAllowedByCurrentUserSettingsLocked(updateRecord.mProvider)) {
requestingLocation = true;
LocationProviderInterface locationProvider
- = mProvidersByName.get(updateRecord.mProvider);
+ = mProvidersByName.get(updateRecord.mProvider);
ProviderProperties properties = locationProvider != null
? locationProvider.getProperties() : null;
if (properties != null
@@ -813,7 +818,7 @@
long identity = Binder.clearCallingIdentity();
receiver.decrementPendingBroadcastsLocked();
Binder.restoreCallingIdentity(identity);
- }
+ }
}
}
}
@@ -1288,13 +1293,18 @@
if (!records.contains(this)) {
records.add(this);
}
+
+ // Update statistics for historical location requests by package/provider
+ mRequestStatistics.startRequesting(
+ mReceiver.mPackageName, provider, request.getInterval());
}
/**
- * Method to be called when a record will no longer be used. Calling this multiple times
- * must have the same effect as calling it once.
+ * Method to be called when a record will no longer be used.
*/
void disposeLocked(boolean removeReceiver) {
+ mRequestStatistics.stopRequesting(mReceiver.mPackageName, mProvider);
+
// remove from mRecordsByProvider
ArrayList<UpdateRecord> globalRecords = mRecordsByProvider.get(this.mProvider);
if (globalRecords != null) {
@@ -1541,6 +1551,7 @@
if (oldRecords != null) {
// Call dispose() on the obsolete update records.
for (UpdateRecord record : oldRecords.values()) {
+ // Update statistics for historical location requests by package/provider
record.disposeLocked(false);
}
// Accumulate providers
@@ -1762,7 +1773,7 @@
@Override
public ProviderProperties getProviderProperties(String provider) {
if (mProvidersByName.get(provider) == null) {
- return null;
+ return null;
}
checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(),
@@ -1965,7 +1976,7 @@
// Fetch latest status update time
long newStatusUpdateTime = p.getStatusUpdateTime();
- // Get latest status
+ // Get latest status
Bundle extras = new Bundle();
int status = p.getStatus(extras);
@@ -2179,7 +2190,7 @@
}
if (mContext.checkCallingPermission(ACCESS_MOCK_LOCATION) !=
- PackageManager.PERMISSION_GRANTED) {
+ PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires ACCESS_MOCK_LOCATION permission");
}
}
@@ -2351,13 +2362,20 @@
for (Receiver receiver : mReceivers.values()) {
pw.println(" " + receiver);
}
- pw.println(" Records by Provider:");
+ pw.println(" Active Records by Provider:");
for (Map.Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) {
pw.println(" " + entry.getKey() + ":");
for (UpdateRecord record : entry.getValue()) {
pw.println(" " + record);
}
}
+ pw.println(" Historical Records by Provider:");
+ for (Map.Entry<PackageProviderKey, PackageStatistics> entry
+ : mRequestStatistics.statistics.entrySet()) {
+ PackageProviderKey key = entry.getKey();
+ PackageStatistics stats = entry.getValue();
+ pw.println(" " + key.packageName + ": " + key.providerName + ": " + stats);
+ }
pw.println(" Last Known Locations:");
for (Map.Entry<String, Location> entry : mLastLocation.entrySet()) {
String provider = entry.getKey();
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index ad7ec99..f1a3ba5 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -151,6 +151,8 @@
private final Handler mMainHandler = new Handler();
+ private IBatteryStats mBatteryStats;
+
private Thread mThread;
private CountDownLatch mConnectedSignal = new CountDownLatch(1);
@@ -226,6 +228,17 @@
if (DBG) Slog.d(TAG, "Prepared");
}
+ private IBatteryStats getBatteryStats() {
+ synchronized (this) {
+ if (mBatteryStats != null) {
+ return mBatteryStats;
+ }
+ mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
+ BatteryStats.SERVICE_NAME));
+ return mBatteryStats;
+ }
+ }
+
@Override
public void registerObserver(INetworkManagementEventObserver observer) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
@@ -323,6 +336,10 @@
* Notify our observers of a change in the data activity state of the interface
*/
private void notifyInterfaceClassActivity(String label, boolean active) {
+ try {
+ getBatteryStats().noteDataConnectionActive(label, active);
+ } catch (RemoteException e) {
+ }
final int length = mObservers.beginBroadcast();
for (int i = 0; i < length; i++) {
try {
@@ -359,8 +376,7 @@
if (mBandwidthControlEnabled) {
try {
- IBatteryStats.Stub.asInterface(ServiceManager.getService(BatteryStats.SERVICE_NAME))
- .noteNetworkStatsEnabled();
+ getBatteryStats().noteNetworkStatsEnabled();
} catch (RemoteException e) {
}
}
@@ -1192,6 +1208,8 @@
throw e.rethrowAsParcelableException();
}
mActiveIdleTimers.put(iface, new IdleTimerParams(timeout, label));
+ // Networks start up.
+ notifyInterfaceClassActivity(label, true);
}
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 8a29ac7..2941a18 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -230,7 +230,14 @@
mStats.noteUserActivityLocked(uid, event);
}
}
-
+
+ public void noteDataConnectionActive(String label, boolean active) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteDataConnectionActive(label, active);
+ }
+ }
+
public void notePhoneOn() {
enforceCallingPermission();
synchronized (mStats) {
@@ -373,6 +380,13 @@
}
}
+ public void noteBluetoothActiveState(int actType) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteBluetoothActiveStateLocked(actType);
+ }
+ }
+
public void noteFullWifiLockAcquired(int uid) {
enforceCallingPermission();
synchronized (mStats) {
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index f5acc4c..ffb113c 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -18,7 +18,9 @@
import com.android.internal.util.DumpUtils;
import com.android.server.FgThread;
+import com.android.server.SystemService;
+import android.Manifest;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -35,6 +37,8 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
+import android.service.dreams.DreamManagerInternal;
+import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
import android.util.Slog;
@@ -50,7 +54,7 @@
*
* @hide
*/
-public final class DreamManagerService extends IDreamManager.Stub {
+public final class DreamManagerService extends SystemService {
private static final boolean DEBUG = false;
private static final String TAG = "DreamManagerService";
@@ -67,6 +71,7 @@
private boolean mCurrentDreamIsTest;
public DreamManagerService(Context context) {
+ super(context);
mContext = context;
mHandler = new DreamHandler(FgThread.get().getLooper());
mController = new DreamController(context, mHandler, mControllerListener);
@@ -74,27 +79,27 @@
mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
}
- public void systemRunning() {
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- synchronized (mLock) {
- stopDreamLocked();
- }
- }
- }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);
+ @Override
+ public void onStart() {
+ publishBinderService(DreamService.DREAM_SERVICE, new BinderService());
+ publishLocalService(DreamManagerInternal.class, new LocalService());
}
@Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (mContext.checkCallingOrSelfPermission("android.permission.DUMP")
- != PackageManager.PERMISSION_GRANTED) {
- pw.println("Permission Denial: can't dump DreamManager from pid="
- + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid());
- return;
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ synchronized (mLock) {
+ stopDreamLocked();
+ }
+ }
+ }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);
}
+ }
+ private void dumpInternal(PrintWriter pw) {
pw.println("DREAM MANAGER (dumpsys dreams)");
pw.println();
@@ -112,156 +117,57 @@
}, pw, 200);
}
- @Override // Binder call
- public ComponentName[] getDreamComponents() {
- checkPermission(android.Manifest.permission.READ_DREAM_STATE);
-
- final int userId = UserHandle.getCallingUserId();
- final long ident = Binder.clearCallingIdentity();
- try {
- return getDreamComponentsForUser(userId);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- @Override // Binder call
- public void setDreamComponents(ComponentName[] componentNames) {
- checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
-
- final int userId = UserHandle.getCallingUserId();
- final long ident = Binder.clearCallingIdentity();
- try {
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.SCREENSAVER_COMPONENTS,
- componentsToString(componentNames),
- userId);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- @Override // Binder call
- public ComponentName getDefaultDreamComponent() {
- checkPermission(android.Manifest.permission.READ_DREAM_STATE);
-
- final int userId = UserHandle.getCallingUserId();
- final long ident = Binder.clearCallingIdentity();
- try {
- String name = Settings.Secure.getStringForUser(mContext.getContentResolver(),
- Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT,
- userId);
- return name == null ? null : ComponentName.unflattenFromString(name);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- @Override // Binder call
- public boolean isDreaming() {
- checkPermission(android.Manifest.permission.READ_DREAM_STATE);
-
+ private boolean isDreamingInternal() {
synchronized (mLock) {
return mCurrentDreamToken != null && !mCurrentDreamIsTest;
}
}
- @Override // Binder call
- public void dream() {
- checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
-
- final long ident = Binder.clearCallingIdentity();
- try {
- // Ask the power manager to nap. It will eventually call back into
- // startDream() if/when it is appropriate to start dreaming.
- // Because napping could cause the screen to turn off immediately if the dream
- // cannot be started, we keep one eye open and gently poke user activity.
- long time = SystemClock.uptimeMillis();
- mPowerManager.userActivity(time, true /*noChangeLights*/);
- mPowerManager.nap(time);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ private void requestDreamInternal() {
+ // Ask the power manager to nap. It will eventually call back into
+ // startDream() if/when it is appropriate to start dreaming.
+ // Because napping could cause the screen to turn off immediately if the dream
+ // cannot be started, we keep one eye open and gently poke user activity.
+ long time = SystemClock.uptimeMillis();
+ mPowerManager.userActivity(time, true /*noChangeLights*/);
+ mPowerManager.nap(time);
}
- @Override // Binder call
- public void testDream(ComponentName dream) {
- checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
+ private void requestAwakenInternal() {
+ // Treat an explicit request to awaken as user activity so that the
+ // device doesn't immediately go to sleep if the timeout expired,
+ // for example when being undocked.
+ long time = SystemClock.uptimeMillis();
+ mPowerManager.userActivity(time, false /*noChangeLights*/);
+ stopDreamInternal();
+ }
- if (dream == null) {
- throw new IllegalArgumentException("dream must not be null");
+ private void finishSelfInternal(IBinder token) {
+ if (DEBUG) {
+ Slog.d(TAG, "Dream finished: " + token);
}
- final int callingUserId = UserHandle.getCallingUserId();
- final int currentUserId = ActivityManager.getCurrentUser();
- if (callingUserId != currentUserId) {
- // This check is inherently prone to races but at least it's something.
- Slog.w(TAG, "Aborted attempt to start a test dream while a different "
- + " user is active: callingUserId=" + callingUserId
- + ", currentUserId=" + currentUserId);
- return;
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
- startDreamLocked(dream, true /*isTest*/, callingUserId);
+ // Note that a dream finishing and self-terminating is not
+ // itself considered user activity. If the dream is ending because
+ // the user interacted with the device then user activity will already
+ // have been poked so the device will stay awake a bit longer.
+ // If the dream is ending on its own for other reasons and no wake
+ // locks are held and the user activity timeout has expired then the
+ // device may simply go to sleep.
+ synchronized (mLock) {
+ if (mCurrentDreamToken == token) {
+ stopDreamLocked();
}
- } finally {
- Binder.restoreCallingIdentity(ident);
}
}
- @Override // Binder call
- public void awaken() {
- checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
-
- final long ident = Binder.clearCallingIdentity();
- try {
- // Treat an explicit request to awaken as user activity so that the
- // device doesn't immediately go to sleep if the timeout expired,
- // for example when being undocked.
- long time = SystemClock.uptimeMillis();
- mPowerManager.userActivity(time, false /*noChangeLights*/);
- stopDream();
- } finally {
- Binder.restoreCallingIdentity(ident);
+ private void testDreamInternal(ComponentName dream, int userId) {
+ synchronized (mLock) {
+ startDreamLocked(dream, true /*isTest*/, userId);
}
}
- @Override // Binder call
- public void finishSelf(IBinder token) {
- // Requires no permission, called by Dream from an arbitrary process.
- if (token == null) {
- throw new IllegalArgumentException("token must not be null");
- }
-
- final long ident = Binder.clearCallingIdentity();
- try {
- if (DEBUG) {
- Slog.d(TAG, "Dream finished: " + token);
- }
-
- // Note that a dream finishing and self-terminating is not
- // itself considered user activity. If the dream is ending because
- // the user interacted with the device then user activity will already
- // have been poked so the device will stay awake a bit longer.
- // If the dream is ending on its own for other reasons and no wake
- // locks are held and the user activity timeout has expired then the
- // device may simply go to sleep.
- synchronized (mLock) {
- if (mCurrentDreamToken == token) {
- stopDreamLocked();
- }
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- /**
- * Called by the power manager to start a dream.
- */
- public void startDream() {
+ private void startDreamInternal() {
int userId = ActivityManager.getCurrentUser();
ComponentName dream = chooseDreamForUser(userId);
if (dream != null) {
@@ -271,10 +177,7 @@
}
}
- /**
- * Called by the power manager to stop a dream.
- */
- public void stopDream() {
+ private void stopDreamInternal() {
synchronized (mLock) {
stopDreamLocked();
}
@@ -305,7 +208,7 @@
// fallback to the default dream component if necessary
if (validComponents.isEmpty()) {
- ComponentName defaultDream = getDefaultDreamComponent();
+ ComponentName defaultDream = getDefaultDreamComponentForUser(userId);
if (defaultDream != null) {
Slog.w(TAG, "Falling back to default dream " + defaultDream);
validComponents.add(defaultDream);
@@ -314,6 +217,20 @@
return validComponents.toArray(new ComponentName[validComponents.size()]);
}
+ private void setDreamComponentsForUser(int userId, ComponentName[] componentNames) {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.SCREENSAVER_COMPONENTS,
+ componentsToString(componentNames),
+ userId);
+ }
+
+ private ComponentName getDefaultDreamComponentForUser(int userId) {
+ String name = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT,
+ userId);
+ return name == null ? null : ComponentName.unflattenFromString(name);
+ }
+
private boolean serviceExists(ComponentName name) {
try {
return name != null && mContext.getPackageManager().getServiceInfo(name, 0) != null;
@@ -423,4 +340,155 @@
super(looper, null, true /*async*/);
}
}
+
+ private final class BinderService extends IDreamManager.Stub {
+ @Override // Binder call
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump DreamManager from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ dumpInternal(pw);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ public ComponentName[] getDreamComponents() {
+ checkPermission(android.Manifest.permission.READ_DREAM_STATE);
+
+ final int userId = UserHandle.getCallingUserId();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return getDreamComponentsForUser(userId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ public void setDreamComponents(ComponentName[] componentNames) {
+ checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
+
+ final int userId = UserHandle.getCallingUserId();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ setDreamComponentsForUser(userId, componentNames);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ public ComponentName getDefaultDreamComponent() {
+ checkPermission(android.Manifest.permission.READ_DREAM_STATE);
+
+ final int userId = UserHandle.getCallingUserId();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return getDefaultDreamComponentForUser(userId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ public boolean isDreaming() {
+ checkPermission(android.Manifest.permission.READ_DREAM_STATE);
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return isDreamingInternal();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ public void dream() {
+ checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ requestDreamInternal();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ public void testDream(ComponentName dream) {
+ if (dream == null) {
+ throw new IllegalArgumentException("dream must not be null");
+ }
+ checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
+
+ final int callingUserId = UserHandle.getCallingUserId();
+ final int currentUserId = ActivityManager.getCurrentUser();
+ if (callingUserId != currentUserId) {
+ // This check is inherently prone to races but at least it's something.
+ Slog.w(TAG, "Aborted attempt to start a test dream while a different "
+ + " user is active: callingUserId=" + callingUserId
+ + ", currentUserId=" + currentUserId);
+ return;
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ testDreamInternal(dream, callingUserId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ public void awaken() {
+ checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ requestAwakenInternal();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ public void finishSelf(IBinder token) {
+ // Requires no permission, called by Dream from an arbitrary process.
+ if (token == null) {
+ throw new IllegalArgumentException("token must not be null");
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ finishSelfInternal(token);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ private final class LocalService extends DreamManagerInternal {
+ @Override
+ public void startDream() {
+ startDreamInternal();
+ }
+
+ @Override
+ public void stopDream() {
+ stopDreamInternal();
+ }
+
+ @Override
+ public boolean isDreaming() {
+ return isDreamingInternal();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/location/LocationRequestStatistics.java b/services/core/java/com/android/server/location/LocationRequestStatistics.java
new file mode 100644
index 0000000..85231bb
--- /dev/null
+++ b/services/core/java/com/android/server/location/LocationRequestStatistics.java
@@ -0,0 +1,205 @@
+package com.android.server.location;
+
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.util.HashMap;
+
+/**
+ * Holds statistics for location requests (active requests by provider).
+ *
+ * <p>Must be externally synchronized.
+ */
+public class LocationRequestStatistics {
+ private static final String TAG = "LocationStats";
+
+ // Maps package name nad provider to location request statistics.
+ public final HashMap<PackageProviderKey, PackageStatistics> statistics
+ = new HashMap<PackageProviderKey, PackageStatistics>();
+
+ /**
+ * Signals that a package has started requesting locations.
+ *
+ * @param packageName Name of package that has requested locations.
+ * @param providerName Name of provider that is requested (e.g. "gps").
+ * @param intervalMs The interval that is requested in ms.
+ */
+ public void startRequesting(String packageName, String providerName, long intervalMs) {
+ PackageProviderKey key = new PackageProviderKey(packageName, providerName);
+ PackageStatistics stats = statistics.get(key);
+ if (stats == null) {
+ stats = new PackageStatistics();
+ statistics.put(key, stats);
+ }
+ stats.startRequesting(intervalMs);
+ }
+
+ /**
+ * Signals that a package has stopped requesting locations.
+ *
+ * @param packageName Name of package that has stopped requesting locations.
+ * @param providerName Provider that is no longer being requested.
+ */
+ public void stopRequesting(String packageName, String providerName) {
+ PackageProviderKey key = new PackageProviderKey(packageName, providerName);
+ PackageStatistics stats = statistics.get(key);
+ if (stats != null) {
+ stats.stopRequesting();
+ } else {
+ // This shouldn't be a possible code path.
+ Log.e(TAG, "Couldn't find package statistics when removing location request.");
+ }
+ }
+
+ /**
+ * A key that holds both package and provider names.
+ */
+ public static class PackageProviderKey {
+ /**
+ * Name of package requesting location.
+ */
+ public final String packageName;
+ /**
+ * Name of provider being requested (e.g. "gps").
+ */
+ public final String providerName;
+
+ public PackageProviderKey(String packageName, String providerName) {
+ this.packageName = packageName;
+ this.providerName = providerName;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof PackageProviderKey)) {
+ return false;
+ }
+
+ PackageProviderKey otherKey = (PackageProviderKey) other;
+ return packageName.equals(otherKey.packageName)
+ && providerName.equals(otherKey.providerName);
+ }
+
+ @Override
+ public int hashCode() {
+ return packageName.hashCode() + 31 * providerName.hashCode();
+ }
+ }
+
+ /**
+ * Usage statistics for a package/provider pair.
+ */
+ public static class PackageStatistics {
+ // Time when this package first requested location.
+ private final long mInitialElapsedTimeMs;
+ // Number of active location requests this package currently has.
+ private int mNumActiveRequests;
+ // Time when this package most recently went from not requesting location to requesting.
+ private long mLastActivitationElapsedTimeMs;
+ // The fastest interval this package has ever requested.
+ private long mFastestIntervalMs;
+ // The slowest interval this package has ever requested.
+ private long mSlowestIntervalMs;
+ // The total time this app has requested location (not including currently running requests).
+ private long mTotalDurationMs;
+
+ private PackageStatistics() {
+ mInitialElapsedTimeMs = SystemClock.elapsedRealtime();
+ mNumActiveRequests = 0;
+ mTotalDurationMs = 0;
+ mFastestIntervalMs = Long.MAX_VALUE;
+ mSlowestIntervalMs = 0;
+ }
+
+ private void startRequesting(long intervalMs) {
+ if (mNumActiveRequests == 0) {
+ mLastActivitationElapsedTimeMs = SystemClock.elapsedRealtime();
+ }
+
+ if (intervalMs < mFastestIntervalMs) {
+ mFastestIntervalMs = intervalMs;
+ }
+
+ if (intervalMs > mSlowestIntervalMs) {
+ mSlowestIntervalMs = intervalMs;
+ }
+
+ mNumActiveRequests++;
+ }
+
+ private void stopRequesting() {
+ if (mNumActiveRequests <= 0) {
+ // Shouldn't be a possible code path
+ Log.e(TAG, "Reference counting corrupted in usage statistics.");
+ return;
+ }
+
+ mNumActiveRequests--;
+ if (mNumActiveRequests == 0) {
+ long lastDurationMs
+ = SystemClock.elapsedRealtime() - mLastActivitationElapsedTimeMs;
+ mTotalDurationMs += lastDurationMs;
+ }
+ }
+
+ /**
+ * Returns the duration that this request has been active.
+ */
+ public long getDurationMs() {
+ long currentDurationMs = mTotalDurationMs;
+ if (mNumActiveRequests > 0) {
+ currentDurationMs
+ += SystemClock.elapsedRealtime() - mLastActivitationElapsedTimeMs;
+ }
+ return currentDurationMs;
+ }
+
+ /**
+ * Returns the time since the initial request in ms.
+ */
+ public long getTimeSinceFirstRequestMs() {
+ return SystemClock.elapsedRealtime() - mInitialElapsedTimeMs;
+ }
+
+ /**
+ * Returns the fastest interval that has been tracked.
+ */
+ public long getFastestIntervalMs() {
+ return mFastestIntervalMs;
+ }
+
+ /**
+ * Returns the slowest interval that has been tracked.
+ */
+ public long getSlowestIntervalMs() {
+ return mSlowestIntervalMs;
+ }
+
+ /**
+ * Returns true if a request is active for these tracked statistics.
+ */
+ public boolean isActive() {
+ return mNumActiveRequests > 0;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder s = new StringBuilder();
+ if (mFastestIntervalMs == mSlowestIntervalMs) {
+ s.append("Interval ").append(mFastestIntervalMs / 1000).append(" seconds");
+ } else {
+ s.append("Min interval ").append(mFastestIntervalMs / 1000).append(" seconds");
+ s.append(": Max interval ").append(mSlowestIntervalMs / 1000).append(" seconds");
+ }
+ s.append(": Duration requested ")
+ .append((getDurationMs() / 1000) / 60)
+ .append(" out of the last ")
+ .append((getTimeSinceFirstRequestMs() / 1000) / 60)
+ .append(" minutes");
+ if (isActive()) {
+ s.append(": Currently active");
+ }
+ return s.toString();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index b2344e6..ecde184 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -20,12 +20,12 @@
import com.android.internal.app.IBatteryStats;
import com.android.server.BatteryService;
import com.android.server.EventLogTags;
+import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
import com.android.server.twilight.TwilightManager;
import com.android.server.Watchdog;
-import com.android.server.dreams.DreamManagerService;
import android.Manifest;
import android.content.BroadcastReceiver;
@@ -57,6 +57,7 @@
import android.os.UserHandle;
import android.os.WorkSource;
import android.provider.Settings;
+import android.service.dreams.DreamManagerInternal;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
@@ -181,7 +182,7 @@
private DisplayPowerController mDisplayPowerController;
private WirelessChargerDetector mWirelessChargerDetector;
private SettingsObserver mSettingsObserver;
- private DreamManagerService mDreamManager;
+ private DreamManagerInternal mDreamManager;
private Light mAttentionLight;
private final Object mLock = new Object();
@@ -433,10 +434,10 @@
}
}
- public void systemReady(TwilightManager twilight, DreamManagerService dreamManager) {
+ public void systemReady() {
synchronized (mLock) {
mSystemReady = true;
- mDreamManager = dreamManager;
+ mDreamManager = LocalServices.getService(DreamManagerInternal.class);
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mScreenBrightnessSettingMinimum = pm.getMinimumScreenBrightnessSetting();
@@ -454,7 +455,8 @@
// The display power controller runs on the power manager service's
// own handler thread to ensure timely operation.
mDisplayPowerController = new DisplayPowerController(mHandler.getLooper(),
- mContext, mNotifier, mLightsManager, twilight, sensorManager,
+ mContext, mNotifier, mLightsManager,
+ LocalServices.getService(TwilightManager.class), sensorManager,
mDisplaySuspendBlocker, mDisplayBlanker,
mDisplayPowerControllerCallbacks, mHandler);
diff --git a/services/core/java/com/android/server/wm/DimLayer.java b/services/core/java/com/android/server/wm/DimLayer.java
index 574ae2d..aa7d485 100644
--- a/services/core/java/com/android/server/wm/DimLayer.java
+++ b/services/core/java/com/android/server/wm/DimLayer.java
@@ -127,6 +127,11 @@
void setBounds(Rect bounds) {
mBounds.set(bounds);
+ if (isDimming() && !mLastBounds.equals(bounds)) {
+ // Clearing mAlpha forces show to redisplay with new size.
+ mAlpha = 0;
+ show();
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 415a06b..68834d8 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -170,22 +170,13 @@
}
void updateDisplayInfo() {
- // Save old size.
- int oldWidth = mDisplayInfo.logicalWidth;
- int oldHeight = mDisplayInfo.logicalHeight;
mDisplay.getDisplayInfo(mDisplayInfo);
-
for (int i = mStacks.size() - 1; i >= 0; --i) {
- final TaskStack stack = mStacks.get(i);
- if (!stack.isFullscreen()) {
- stack.resizeBounds(oldWidth, oldHeight, mDisplayInfo.logicalWidth,
- mDisplayInfo.logicalHeight);
- }
+ mStacks.get(i).updateDisplayInfo();
}
}
void getLogicalDisplayRect(Rect out) {
- updateDisplayInfo();
// Uses same calculation as in LogicalDisplay#configureDisplayInTransactionLocked.
final int orientation = mDisplayInfo.rotation;
boolean rotated = (orientation == Surface.ROTATION_90
@@ -291,11 +282,12 @@
}
boolean isDimming() {
- boolean result = false;
for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
- result |= mStacks.get(stackNdx).isDimming();
+ if (mStacks.get(stackNdx).isDimming()) {
+ return true;
+ }
}
- return result;
+ return false;
}
void stopDimmingIfNeeded() {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 7d8cff4..c70bc62 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -47,12 +47,17 @@
* mTaskHistory in the ActivityStack with the same mStackId */
private final ArrayList<Task> mTasks = new ArrayList<Task>();
- /** Content limits relative to the DisplayContent this sits in. Empty indicates fullscreen,
- * Nonempty is size of this TaskStack but is also used to scale if DisplayContent changes. */
- Rect mBounds = new Rect();
+ /** For comparison with DisplayContent bounds. */
+ private Rect mTmpRect = new Rect();
+
+ /** Content limits relative to the DisplayContent this sits in. */
+ private Rect mBounds = new Rect();
+
+ /** Whether mBounds is fullscreen */
+ private boolean mFullscreen = true;
/** Used to support {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND} */
- DimLayer mDimLayer;
+ private DimLayer mDimLayer;
/** The particular window with FLAG_DIM_BEHIND set. If null, hide mDimLayer. */
WindowStateAnimator mDimWinAnimator;
@@ -86,7 +91,7 @@
return mTasks;
}
- private void resizeWindows() {
+ void resizeWindows() {
final boolean underStatusBar = mBounds.top == 0;
final ArrayList<WindowState> resizingWindows = mService.mResizingWindows;
@@ -108,7 +113,13 @@
}
boolean setBounds(Rect bounds) {
- if (mBounds.equals(bounds)) {
+ boolean oldFullscreen = mFullscreen;
+ if (mDisplayContent != null) {
+ mDisplayContent.getLogicalDisplayRect(mTmpRect);
+ mFullscreen = mTmpRect.equals(bounds);
+ }
+
+ if (mBounds.equals(bounds) && oldFullscreen == mFullscreen) {
return false;
}
@@ -116,25 +127,22 @@
mAnimationBackgroundSurface.setBounds(bounds);
mBounds.set(bounds);
- resizeWindows();
return true;
}
void getBounds(Rect out) {
- if (mDisplayContent != null) {
- if (mBounds.isEmpty()) {
- mDisplayContent.getLogicalDisplayRect(out);
- } else {
- out.set(mBounds);
- }
- out.intersect(mDisplayContent.mContentRect);
- } else {
- out.set(mBounds);
+ out.set(mBounds);
+ }
+
+ void updateDisplayInfo() {
+ if (mFullscreen && mDisplayContent != null) {
+ mDisplayContent.getLogicalDisplayRect(mTmpRect);
+ setBounds(mTmpRect);
}
}
boolean isFullscreen() {
- return mBounds.isEmpty();
+ return mFullscreen;
}
boolean isAnimating() {
@@ -152,19 +160,6 @@
return false;
}
- void resizeBounds(float oldWidth, float oldHeight, float newWidth, float newHeight) {
- if (oldWidth == newWidth && oldHeight == newHeight) {
- return;
- }
- float widthScale = newWidth / oldWidth;
- float heightScale = newHeight / oldHeight;
- mBounds.left = (int)(mBounds.left * widthScale + 0.5);
- mBounds.top = (int)(mBounds.top * heightScale + 0.5);
- mBounds.right = (int)(mBounds.right * widthScale + 0.5);
- mBounds.bottom = (int)(mBounds.bottom * heightScale + 0.5);
- resizeWindows();
- }
-
/**
* Put a Task in this stack. Used for adding and moving.
* @param task The task to add.
@@ -233,6 +228,7 @@
mDisplayContent = displayContent;
mDimLayer = new DimLayer(mService, this, displayContent);
mAnimationBackgroundSurface = new DimLayer(mService, this, displayContent);
+ updateDisplayInfo();
}
void detachDisplay() {
@@ -371,11 +367,11 @@
(mDisplayContent.mDeferredActions & DisplayContent.DEFER_DETACH) != 0 &&
!isAnimating()) {
mDisplayContent.mDeferredActions &= ~DisplayContent.DEFER_DETACH;
- mService.detachStack(mStackId);
if ((mDisplayContent.mDeferredActions & DisplayContent.DEFER_REMOVAL) != 0) {
mDisplayContent.mDeferredActions &= ~DisplayContent.DEFER_REMOVAL;
mService.onDisplayRemoved(mDisplayContent.getDisplayId());
}
+ mService.detachStack(mStackId);
}
for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
final Task task = mTasks.get(taskNdx);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4b92bf7..bf88d9f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4988,6 +4988,7 @@
+ " not found.");
}
if (stack.setBounds(bounds)) {
+ stack.resizeWindows();
stack.getDisplayContent().layoutNeeded = true;
performLayoutAndPlaceSurfacesLocked();
}
@@ -9959,6 +9960,7 @@
}
// TODO(multidisplay): rotation on main screen only.
+ displayContent.updateDisplayInfo();
screenRotationAnimation = new ScreenRotationAnimation(mContext, displayContent,
mFxSession, inTransaction, mPolicy.isDefaultOrientationForced());
mAnimator.setScreenRotationAnimationLocked(displayId, screenRotationAnimation);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 85e5e23..943471a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -74,7 +74,6 @@
import com.android.server.search.SearchManagerService;
import com.android.server.statusbar.StatusBarManagerService;
import com.android.server.storage.DeviceStorageMonitorService;
-import com.android.server.twilight.TwilightManager;
import com.android.server.twilight.TwilightService;
import com.android.server.usb.UsbService;
import com.android.server.wallpaper.WallpaperManagerService;
@@ -303,7 +302,6 @@
DockObserver dock = null;
UsbService usb = null;
SerialService serial = null;
- TwilightManager twilight = null;
RecognitionManagerService recognition = null;
NetworkTimeUpdateService networkTimeUpdater = null;
CommonTimeManagementService commonTimeMgmtService = null;
@@ -460,7 +458,6 @@
CountryDetectorService countryDetector = null;
TextServicesManagerService tsms = null;
LockSettingsService lockSettings = null;
- DreamManagerService dreamy = null;
AssetAtlasService atlas = null;
MediaRouterService mediaRouter = null;
@@ -776,7 +773,6 @@
}
mSystemServiceManager.startService(TwilightService.class);
- twilight = LocalServices.getService(TwilightManager.class);
mSystemServiceManager.startService(UiModeManagerService.class);
@@ -852,16 +848,10 @@
}
}
- if (!disableNonCoreServices &&
- context.getResources().getBoolean(R.bool.config_dreamsSupported)) {
- try {
- Slog.i(TAG, "Dreams Service");
- // Dreams (interactive idle-time views, a/k/a screen savers)
- dreamy = new DreamManagerService(context);
- ServiceManager.addService(DreamService.DREAM_SERVICE, dreamy);
- } catch (Throwable e) {
- reportWtf("starting DreamManagerService", e);
- }
+ if (!disableNonCoreServices
+ && context.getResources().getBoolean(R.bool.config_dreamsSupported)) {
+ // Dreams (interactive idle-time views, a/k/a screen savers)
+ mSystemServiceManager.startService(DreamManagerService.class);
}
if (!disableNonCoreServices) {
@@ -956,7 +946,7 @@
try {
// TODO: use boot phase
- mPowerManagerService.systemReady(twilight, dreamy);
+ mPowerManagerService.systemReady();
} catch (Throwable e) {
reportWtf("making Power Manager Service ready", e);
}
@@ -993,7 +983,6 @@
final CommonTimeManagementService commonTimeMgmtServiceF = commonTimeMgmtService;
final TextServicesManagerService textServiceManagerServiceF = tsms;
final StatusBarManagerService statusBarF = statusBar;
- final DreamManagerService dreamyF = dreamy;
final AssetAtlasService atlasF = atlas;
final InputManagerService inputManagerF = inputManager;
final TelephonyRegistry telephonyRegistryF = telephonyRegistry;
@@ -1106,11 +1095,6 @@
reportWtf("Notifying TextServicesManagerService running", e);
}
try {
- if (dreamyF != null) dreamyF.systemRunning();
- } catch (Throwable e) {
- reportWtf("Notifying DreamManagerService running", e);
- }
- try {
if (atlasF != null) atlasF.systemRunning();
} catch (Throwable e) {
reportWtf("Notifying AssetAtlasService running", e);
diff --git a/services/tests/servicestests/src/com/android/server/location/LocationRequestStatisticsTest.java b/services/tests/servicestests/src/com/android/server/location/LocationRequestStatisticsTest.java
new file mode 100644
index 0000000..33f604d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/location/LocationRequestStatisticsTest.java
@@ -0,0 +1,175 @@
+package com.android.server.location;
+
+import com.android.server.location.LocationRequestStatistics.PackageProviderKey;
+import com.android.server.location.LocationRequestStatistics.PackageStatistics;
+
+import android.os.SystemClock;
+import android.test.AndroidTestCase;
+
+/**
+ * Unit tests for {@link LocationRequestStatistics}.
+ */
+public class LocationRequestStatisticsTest extends AndroidTestCase {
+ private static final String PACKAGE1 = "package1";
+ private static final String PACKAGE2 = "package2";
+ private static final String PROVIDER1 = "provider1";
+ private static final String PROVIDER2 = "provider2";
+ private static final long INTERVAL1 = 5000;
+ private static final long INTERVAL2 = 100000;
+
+ private LocationRequestStatistics mStatistics;
+ private long mStartElapsedRealtimeMs;
+
+ @Override
+ public void setUp() {
+ mStatistics = new LocationRequestStatistics();
+ mStartElapsedRealtimeMs = SystemClock.elapsedRealtime();
+ }
+
+ /**
+ * Tests that adding a single package works correctly.
+ */
+ public void testSinglePackage() {
+ mStatistics.startRequesting(PACKAGE1, PROVIDER1, INTERVAL1);
+
+ assertEquals(1, mStatistics.statistics.size());
+ PackageProviderKey key = mStatistics.statistics.keySet().iterator().next();
+ assertEquals(PACKAGE1, key.packageName);
+ assertEquals(PROVIDER1, key.providerName);
+ PackageStatistics stats = mStatistics.statistics.values().iterator().next();
+ verifyStatisticsTimes(stats);
+ assertEquals(INTERVAL1, stats.getFastestIntervalMs());
+ assertEquals(INTERVAL1, stats.getSlowestIntervalMs());
+ assertTrue(stats.isActive());
+ }
+
+ /**
+ * Tests that adding a single package works correctly when it is stopped and restarted.
+ */
+ public void testSinglePackage_stopAndRestart() {
+ mStatistics.startRequesting(PACKAGE1, PROVIDER1, INTERVAL1);
+ mStatistics.stopRequesting(PACKAGE1, PROVIDER1);
+ mStatistics.startRequesting(PACKAGE1, PROVIDER1, INTERVAL1);
+
+ assertEquals(1, mStatistics.statistics.size());
+ PackageProviderKey key = mStatistics.statistics.keySet().iterator().next();
+ assertEquals(PACKAGE1, key.packageName);
+ assertEquals(PROVIDER1, key.providerName);
+ PackageStatistics stats = mStatistics.statistics.values().iterator().next();
+ verifyStatisticsTimes(stats);
+ assertEquals(INTERVAL1, stats.getFastestIntervalMs());
+ assertEquals(INTERVAL1, stats.getSlowestIntervalMs());
+ assertTrue(stats.isActive());
+
+ mStatistics.stopRequesting(PACKAGE1, PROVIDER1);
+ assertFalse(stats.isActive());
+ }
+
+ /**
+ * Tests that adding a single package works correctly when multiple intervals are used.
+ */
+ public void testSinglePackage_multipleIntervals() {
+ mStatistics.startRequesting(PACKAGE1, PROVIDER1, INTERVAL1);
+ mStatistics.startRequesting(PACKAGE1, PROVIDER1, INTERVAL2);
+
+ assertEquals(1, mStatistics.statistics.size());
+ PackageProviderKey key = mStatistics.statistics.keySet().iterator().next();
+ assertEquals(PACKAGE1, key.packageName);
+ assertEquals(PROVIDER1, key.providerName);
+ PackageStatistics stats = mStatistics.statistics.values().iterator().next();
+ verifyStatisticsTimes(stats);
+ assertEquals(INTERVAL1, stats.getFastestIntervalMs());
+ assertTrue(stats.isActive());
+
+ mStatistics.stopRequesting(PACKAGE1, PROVIDER1);
+ assertTrue(stats.isActive());
+ mStatistics.stopRequesting(PACKAGE1, PROVIDER1);
+ assertFalse(stats.isActive());
+ }
+
+ /**
+ * Tests that adding a single package works correctly when multiple providers are used.
+ */
+ public void testSinglePackage_multipleProviders() {
+ mStatistics.startRequesting(PACKAGE1, PROVIDER1, INTERVAL1);
+ mStatistics.startRequesting(PACKAGE1, PROVIDER2, INTERVAL2);
+
+ assertEquals(2, mStatistics.statistics.size());
+ PackageProviderKey key1 = new PackageProviderKey(PACKAGE1, PROVIDER1);
+ PackageStatistics stats1 = mStatistics.statistics.get(key1);
+ verifyStatisticsTimes(stats1);
+ assertEquals(INTERVAL1, stats1.getSlowestIntervalMs());
+ assertEquals(INTERVAL1, stats1.getFastestIntervalMs());
+ assertTrue(stats1.isActive());
+ PackageProviderKey key2 = new PackageProviderKey(PACKAGE1, PROVIDER2);
+ PackageStatistics stats2 = mStatistics.statistics.get(key2);
+ verifyStatisticsTimes(stats2);
+ assertEquals(INTERVAL2, stats2.getSlowestIntervalMs());
+ assertEquals(INTERVAL2, stats2.getFastestIntervalMs());
+ assertTrue(stats2.isActive());
+
+ mStatistics.stopRequesting(PACKAGE1, PROVIDER1);
+ assertFalse(stats1.isActive());
+ assertTrue(stats2.isActive());
+ mStatistics.stopRequesting(PACKAGE1, PROVIDER2);
+ assertFalse(stats1.isActive());
+ assertFalse(stats2.isActive());
+ }
+
+ /**
+ * Tests that adding multiple packages works correctly.
+ */
+ public void testMultiplePackages() {
+ mStatistics.startRequesting(PACKAGE1, PROVIDER1, INTERVAL1);
+ mStatistics.startRequesting(PACKAGE1, PROVIDER2, INTERVAL1);
+ mStatistics.startRequesting(PACKAGE1, PROVIDER2, INTERVAL2);
+ mStatistics.startRequesting(PACKAGE2, PROVIDER1, INTERVAL1);
+
+ assertEquals(3, mStatistics.statistics.size());
+ PackageProviderKey key1 = new PackageProviderKey(PACKAGE1, PROVIDER1);
+ PackageStatistics stats1 = mStatistics.statistics.get(key1);
+ verifyStatisticsTimes(stats1);
+ assertEquals(INTERVAL1, stats1.getSlowestIntervalMs());
+ assertEquals(INTERVAL1, stats1.getFastestIntervalMs());
+ assertTrue(stats1.isActive());
+
+ PackageProviderKey key2 = new PackageProviderKey(PACKAGE1, PROVIDER2);
+ PackageStatistics stats2 = mStatistics.statistics.get(key2);
+ verifyStatisticsTimes(stats2);
+ assertEquals(INTERVAL2, stats2.getSlowestIntervalMs());
+ assertEquals(INTERVAL1, stats2.getFastestIntervalMs());
+ assertTrue(stats2.isActive());
+
+ PackageProviderKey key3 = new PackageProviderKey(PACKAGE2, PROVIDER1);
+ PackageStatistics stats3 = mStatistics.statistics.get(key3);
+ verifyStatisticsTimes(stats3);
+ assertEquals(INTERVAL1, stats3.getSlowestIntervalMs());
+ assertEquals(INTERVAL1, stats3.getFastestIntervalMs());
+ assertTrue(stats3.isActive());
+
+ mStatistics.stopRequesting(PACKAGE1, PROVIDER1);
+ assertFalse(stats1.isActive());
+ assertTrue(stats2.isActive());
+ assertTrue(stats3.isActive());
+
+ mStatistics.stopRequesting(PACKAGE1, PROVIDER2);
+ assertFalse(stats1.isActive());
+ assertTrue(stats2.isActive());
+ assertTrue(stats3.isActive());
+ mStatistics.stopRequesting(PACKAGE1, PROVIDER2);
+ assertFalse(stats2.isActive());
+
+ mStatistics.stopRequesting(PACKAGE2, PROVIDER1);
+ assertFalse(stats1.isActive());
+ assertFalse(stats2.isActive());
+ assertFalse(stats3.isActive());
+ }
+
+ private void verifyStatisticsTimes(PackageStatistics stats) {
+ long durationMs = stats.getDurationMs();
+ long timeSinceFirstRequestMs = stats.getTimeSinceFirstRequestMs();
+ long maxDeltaMs = SystemClock.elapsedRealtime() - mStartElapsedRealtimeMs;
+ assertTrue("Duration is too large", durationMs <= maxDeltaMs);
+ assertTrue("Time since first request is too large", timeSinceFirstRequestMs <= maxDeltaMs);
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index 4be11b8..59bdf64 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -97,6 +97,7 @@
public static final int CMD_ENABLE_MOBILE_PROVISIONING = BASE + 37;
public static final int CMD_IS_PROVISIONING_APN = BASE + 38;
public static final int EVENT_PROVISIONING_APN_ALARM = BASE + 39;
+ public static final int CMD_NET_STAT_POLL = BASE + 40;
/***** Constants *****/