Merge "Improve ContractsContract.RawContacts documentation"
diff --git a/api/current.xml b/api/current.xml
index 7acbccb..1142088 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -14690,6 +14690,25 @@
<parameter name="value" type="java.lang.String">
</parameter>
</method>
+<method name="testHasFeatures"
+ return="android.accounts.AccountManagerFuture<java.lang.Boolean>"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="account" type="android.accounts.Account">
+</parameter>
+<parameter name="features" type="java.lang.String[]">
+</parameter>
+<parameter name="callback" type="android.accounts.AccountManagerCallback<java.lang.Boolean>">
+</parameter>
+<parameter name="handler" type="android.os.Handler">
+</parameter>
+</method>
<method name="updateCredentials"
return="android.accounts.AccountManagerFuture<android.os.Bundle>"
abstract="false"
@@ -23739,6 +23758,19 @@
</implements>
<implements name="android.content.DialogInterface.OnDismissListener">
</implements>
+<method name="getSearchableInfo"
+ return="android.app.SearchableInfo"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="componentName" type="android.content.ComponentName">
+</parameter>
+</method>
<method name="getSearchablesInGlobalSearch"
return="java.util.List<android.app.SearchableInfo>"
abstract="false"
@@ -23895,6 +23927,28 @@
visibility="public"
>
</field>
+<field name="EXTRA_SELECT_QUERY"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""select_query""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="INTENT_ACTION_GLOBAL_SEARCH"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.search.action.GLOBAL_SEARCH""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="INTENT_ACTION_SEARCHABLES_CHANGED"
type="java.lang.String"
transient="false"
@@ -51681,17 +51735,6 @@
<exception name="SQLException" type="android.database.SQLException">
</exception>
</method>
-<method name="resetCompiledSqlCache"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
<method name="setLocale"
return="void"
abstract="false"
@@ -51718,19 +51761,6 @@
<parameter name="lockingEnabled" type="boolean">
</parameter>
</method>
-<method name="setMaxSqlCacheSize"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="cacheSize" type="int">
-</parameter>
-</method>
<method name="setMaximumSize"
return="long"
abstract="false"
@@ -83861,6 +83891,50 @@
visibility="public"
>
</field>
+<field name="TYPE_MOBILE_DUN"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TYPE_MOBILE_HIPRI"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="5"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TYPE_MOBILE_MMS"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TYPE_MOBILE_SUPL"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="TYPE_WIFI"
type="int"
transient="false"
diff --git a/cmds/dumpstate/Android.mk b/cmds/dumpstate/Android.mk
index 27891ec..431a556 100644
--- a/cmds/dumpstate/Android.mk
+++ b/cmds/dumpstate/Android.mk
@@ -15,15 +15,4 @@
include $(BUILD_EXECUTABLE)
-COMMANDS = dumpcrash
-SYMLINKS := $(addprefix $(TARGET_OUT_EXECUTABLES)/,$(COMMANDS))
-$(SYMLINKS): DUMPSTATE_BINARY := dumpstate
-$(SYMLINKS): $(LOCAL_INSTALLED_MODULE) $(LOCAL_PATH)/Android.mk
- @echo "Symlink: $@ -> $(DUMPSTATE_BINARY)"
- @mkdir -p $(dir $@)
- @rm -rf $@
- $(hide) ln -sf $(DUMPSTATE_BINARY) $@
-
-ALL_DEFAULT_INSTALLED_MODULES += $(SYMLINKS)
-
endif
diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c
index a2b5d8d..9300915 100644
--- a/cmds/dumpstate/dumpstate.c
+++ b/cmds/dumpstate/dumpstate.c
@@ -14,361 +14,254 @@
* limitations under the License.
*/
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <unistd.h>
-#include <sys/stat.h>
-#include <limits.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/time.h>
#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <unistd.h>
-#include <cutils/sockets.h>
+#include <cutils/properties.h>
+
#include "private/android_filesystem_config.h"
-
#define LOG_TAG "dumpstate"
#include <utils/Log.h>
#include "dumpstate.h"
-static char* const gzip_args[] = { "gzip", "-6", 0 };
-static int start_pattern[] = { 150, 0 };
-static int end_pattern[] = { 75, 50, 75, 50, 75, 0 };
-
-static struct tm now;
-
-static void dump_kernel_log(const char *path, const char *title) ;
+/* read before root is shed */
+static char cmdline_buf[16384] = "(unknown)";
+static const char *dump_traces_path = NULL;
/* dumps the current system state to stdout */
-static void dumpstate(int full) {
- if (full) {
- PRINT("========================================================");
- PRINT("== dumpstate");
- PRINT("========================================================");
- PRINT("------ MEMORY INFO ------");
- DUMP("/proc/meminfo");
- PRINT("------ CPU INFO ------");
- EXEC7("top", "-n", "1", "-d", "1", "-m", "30", "-t");
- PRINT("------ PROCRANK ------");
- EXEC_XBIN("procrank");
- PRINT("------ VIRTUAL MEMORY STATS ------");
- DUMP("/proc/vmstat");
- PRINT("------ VMALLOC INFO ------");
- DUMP("/proc/vmallocinfo");
- PRINT("------ SLAB INFO ------");
- DUMP("/proc/slabinfo");
- PRINT("------ ZONEINFO ------");
- DUMP("/proc/zoneinfo");
- PRINT("------ SYSTEM LOG ------");
- EXEC4("logcat", "-v", "time", "-d", "*:v");
- PRINT("------ VM TRACES ------");
- DUMP("/data/anr/traces.txt");
- PRINT("------ EVENT LOG TAGS ------");
- DUMP("/etc/event-log-tags");
- PRINT("------ EVENT LOG ------");
- EXEC6("logcat", "-b", "events", "-v", "time", "-d", "*:v");
- PRINT("------ RADIO LOG ------");
- EXEC6("logcat", "-b", "radio", "-v", "time", "-d", "*:v");
- PRINT("------ NETWORK STATE ------");
- PRINT("Interfaces:");
- EXEC("netcfg");
- PRINT("");
- PRINT("Routes:");
- DUMP("/proc/net/route");
+static void dumpstate() {
+ time_t now = time(NULL);
+ char build[PROPERTY_VALUE_MAX], fingerprint[PROPERTY_VALUE_MAX];
+ char radio[PROPERTY_VALUE_MAX], bootloader[PROPERTY_VALUE_MAX];
+ char network[PROPERTY_VALUE_MAX], date[80];
+
+ property_get("ro.build.display.id", build, "(unknown)");
+ property_get("ro.build.fingerprint", fingerprint, "(unknown)");
+ property_get("ro.baseband", radio, "(unknown)");
+ property_get("ro.bootloader", bootloader, "(unknown)");
+ property_get("gsm.operator.alpha", network, "(unknown)");
+ strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", localtime(&now));
+
+ printf("========================================================\n");
+ printf("== dumpstate: %s\n", date);
+ printf("========================================================\n");
+
+ printf("\n");
+ printf("Build: %s\n", build);
+ printf("Bootloader: %s\n", bootloader);
+ printf("Radio: %s\n", radio);
+ printf("Network: %s\n", network);
+
+ printf("Kernel: ");
+ dump_file(NULL, "/proc/version");
+ printf("Command line: %s\n", strtok(cmdline_buf, "\n"));
+ printf("\n");
+
+ dump_file("MEMORY INFO", "/proc/meminfo");
+ run_command("CPU INFO", 10, "top", "-n", "1", "-d", "1", "-m", "30", "-t", NULL);
+ run_command("PROCRANK", 20, "procrank", NULL);
+ dump_file("VIRTUAL MEMORY STATS", "/proc/vmstat");
+ dump_file("VMALLOC INFO", "/proc/vmallocinfo");
+ dump_file("SLAB INFO", "/proc/slabinfo");
+ dump_file("ZONEINFO", "/proc/zoneinfo");
+
+ run_command("SYSTEM LOG", 20, "logcat", "-v", "time", "-d", "*:v", NULL);
+
+ /* show the traces we collected in main(), if that was done */
+ if (dump_traces_path != NULL) {
+ dump_file("VM TRACES JUST NOW", dump_traces_path);
+ }
+
+ /* only show ANR traces if they're less than 15 minutes old */
+ struct stat st;
+ char anr_traces_path[PATH_MAX];
+ property_get("dalvik.vm.stack-trace-file", anr_traces_path, "");
+ if (anr_traces_path[0] && !stat(anr_traces_path, &st) && time(NULL) - st.st_mtime < 15 * 60) {
+ dump_file("VM TRACES AT LAST ANR", anr_traces_path);
+ }
+
+ // dump_file("EVENT LOG TAGS", "/etc/event-log-tags");
+ run_command("EVENT LOG", 20, "logcat", "-b", "events", "-v", "time", "-d", "*:v", NULL);
+ run_command("RADIO LOG", 20, "logcat", "-b", "radio", "-v", "time", "-d", "*:v", NULL);
+
+ run_command("NETWORK INTERFACES", 10, "netcfg", NULL);
+ dump_file("NETWORK ROUTES", "/proc/net/route");
+
#ifdef FWDUMP_bcm4329
- PRINT("Dump wlan FW log");
- EXEC_XBIN6("su", "root","dhdutil","-i","eth0","upload","/data/local/tmp/wlan_crash.dump");
+ run_command("DUMP WIFI FIRMWARE LOG", 60,
+ "dhdutil", "-i", "eth0", "upload", "/data/local/tmp/wlan_crash.dump", NULL);
#endif
- PRINT("------ SYSTEM PROPERTIES ------");
- print_properties();
- PRINT("------ KERNEL LOG ------");
- EXEC("dmesg");
- PRINT("------ KERNEL WAKELOCKS ------");
- DUMP("/proc/wakelocks");
- PRINT("------ KERNEL CPUFREQ ------");
- DUMP("/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state");
- PRINT("");
- PRINT("------ PROCESSES ------");
- EXEC1("ps", "-P");
- PRINT("------ PROCESSES AND THREADS ------");
- EXEC3("ps", "-t", "-p", "-P");
- PRINT("------ LIBRANK ------");
- EXEC_XBIN("librank");
- PRINT("------ BINDER FAILED TRANSACTION LOG ------");
- DUMP("/proc/binder/failed_transaction_log");
- PRINT("");
- PRINT("------ BINDER TRANSACTION LOG ------");
- DUMP("/proc/binder/transaction_log");
- PRINT("");
- PRINT("------ BINDER TRANSACTIONS ------");
- DUMP("/proc/binder/transactions");
- PRINT("");
- PRINT("------ BINDER STATS ------");
- DUMP("/proc/binder/stats");
- PRINT("");
- PRINT("------ BINDER PROCESS STATE: $i ------");
- DUMP_FILES("/proc/binder/proc");
- PRINT("------ FILESYSTEMS ------");
- EXEC("df");
- PRINT("------ PACKAGE SETTINGS ------");
- DUMP("/data/system/packages.xml");
- PRINT("------ PACKAGE UID ERRORS ------");
- DUMP("/data/system/uiderrors.txt");
- dump_kernel_log("/proc/last_kmsg", "LAST KMSG");
+ print_properties();
- PRINT("------ LAST RADIO LOG ------");
- EXEC1("parse_radio_log", "/proc/last_radio_log");
+ run_command("KERNEL LOG", 20, "dmesg", NULL);
- dump_kernel_log("/data/dontpanic/apanic_console",
- "PANIC CONSOLE");
- dump_kernel_log("/data/dontpanic/apanic_threads",
- "PANIC THREADS");
+ dump_file("KERNEL WAKELOCKS", "/proc/wakelocks");
+ dump_file("KERNEL CPUFREQ", "/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state");
- PRINT("------ BACKLIGHTS ------");
- DUMP_PROMPT("LCD brightness=", "/sys/class/leds/lcd-backlight/brightness");
- DUMP_PROMPT("Button brightness=", "/sys/class/leds/button-backlight/brightness");
- DUMP_PROMPT("Keyboard brightness=", "/sys/class/leds/keyboard-backlight/brightness");
- DUMP_PROMPT("ALS mode=", "/sys/class/leds/lcd-backlight/als");
- DUMP_PROMPT("LCD driver registers:\n", "/sys/class/leds/lcd-backlight/registers");
- }
- PRINT("========================================================");
- PRINT("== build.prop");
- PRINT("========================================================");
+ run_command("PROCESSES", 10, "ps", "-P", NULL);
+ run_command("PROCESSES AND THREADS", 10, "ps", "-t", "-p", "-P", NULL);
+ run_command("LIBRANK", 10, "librank", NULL);
- /* the crash server parses key-value pairs between the VERSION INFO and
- * END lines so we can aggregate crash reports based on this data.
- */
- PRINT("------ VERSION INFO ------");
- print_date("currenttime=", &now);
- DUMP_PROMPT("kernel.version=", "/proc/version");
- DUMP_PROMPT("kernel.cmdline=", "/proc/cmdline");
- DUMP("/system/build.prop");
- PROPERTY("gsm.version.ril-impl");
- PROPERTY("gsm.version.baseband");
- PROPERTY("gsm.imei");
- PROPERTY("gsm.sim.operator.numeric");
- PROPERTY("gsm.operator.alpha");
- PRINT("------ END ------");
+ dump_file("BINDER FAILED TRANSACTION LOG", "/proc/binder/failed_transaction_log");
+ dump_file("BINDER TRANSACTION LOG", "/proc/binder/transaction_log");
+ dump_file("BINDER TRANSACTIONS", "/proc/binder/transactions");
+ dump_file("BINDER STATS", "/proc/binder/stats");
+ run_command("BINDER PROCESS STATE", 10, "sh", "-c", "cat /proc/binder/proc/*");
- if (full) {
- PRINT("========================================================");
- PRINT("== dumpsys");
- PRINT("========================================================");
- /* the full dumpsys is starting to take a long time, so we need
- to increase its timeout. we really need to do the timeouts in
- dumpsys itself... */
- EXEC_TIMEOUT("dumpsys", 60);
- }
+ run_command("FILESYSTEMS & FREE SPACE", 10, "df", NULL);
+
+ dump_file("PACKAGE SETTINGS", "/data/system/packages.xml");
+ dump_file("PACKAGE UID ERRORS", "/data/system/uiderrors.txt");
+
+ dump_file("LAST KMSG", "/proc/last_kmsg");
+ run_command("LAST RADIO LOG", 10, "parse_radio_log", "/proc/last_radio_log", NULL);
+ dump_file("LAST PANIC CONSOLE", "/data/dontpanic/apanic_console");
+ dump_file("LAST PANIC THREADS", "/data/dontpanic/apanic_threads");
+
+ printf("----- BACKLIGHTS -----\n");
+ printf("LCD brightness=");
+ dump_file(NULL, "/sys/class/leds/lcd-backlight/brightness");
+ printf("Button brightness=");
+ dump_file(NULL, "/sys/class/leds/button-backlight/brightness");
+ printf("Keyboard brightness=");
+ dump_file(NULL, "/sys/class/leds/keyboard-backlight/brightness");
+ printf("ALS mode=");
+ dump_file(NULL, "/sys/class/leds/lcd-backlight/als");
+ printf("LCD driver registers:\n");
+ dump_file(NULL, "/sys/class/leds/lcd-backlight/registers");
+ printf("\n");
+
+ printf("========================================================\n");
+ printf("== Android Framework Services\n");
+ printf("========================================================\n");
+
+ /* the full dumpsys is starting to take a long time, so we need
+ to increase its timeout. we really need to do the timeouts in
+ dumpsys itself... */
+ run_command("DUMPSYS", 60, "dumpsys", NULL);
}
-/* used to check the file name passed via argv[0] */
-static int check_command_name(const char* name, const char* test) {
- int name_length, test_length;
- if (!strcmp(name, test))
- return 1;
-
- name_length = strlen(name);
- test_length = strlen(test);
-
- if (name_length > test_length + 2) {
- name += (name_length - test_length);
- if (name[-1] != '/')
- return 0;
- if (!strcmp(name, test))
- return 1;
- }
-
- return 0;
+static void usage() {
+ fprintf(stderr, "usage: dumpstate [-d] [-o file] [-s] [-z]\n"
+ " -d: append date to filename (requires -o)\n"
+ " -o: write to file (instead of stdout)\n"
+ " -s: write output to control socket (for init)\n"
+ " -z: gzip output (requires -o)\n");
}
int main(int argc, char *argv[]) {
- int dumpcrash = check_command_name(argv[0], "dumpcrash");
- int add_date = 0;
- char* outfile = 0;
- int vibrate = 0;
- int compress = 0;
- int socket = 0;
- int c, fd, vibrate_fd, fds[2];
- char path[PATH_MAX];
- pid_t pid;
- gid_t groups[] = { AID_LOG, AID_SDCARD_RW };
+ int do_add_date = 0;
+ int do_compress = 0;
+ char* use_outfile = 0;
+ int use_socket = 0;
LOGI("begin\n");
/* set as high priority, and protect from OOM killer */
setpriority(PRIO_PROCESS, 0, -20);
- protect_from_oom_killer();
+ FILE *oom_adj = fopen("/proc/self/oom_adj", "w");
+ if (oom_adj) {
+ fputs("-17", oom_adj);
+ fclose(oom_adj);
+ }
- get_time(&now);
+ /* very first thing, collect VM traces from Dalvik (needs root) */
+ dump_traces_path = dump_vm_traces();
- do {
- c = getopt(argc, argv, "do:svz");
- if (c == EOF)
- break;
+ int c;
+ while ((c = getopt(argc, argv, "dho:svz")) != -1) {
switch (c) {
- case 'd':
- add_date = 1;
- break;
- case 'o':
- outfile = optarg;
- break;
- case 'v':
- vibrate = 1;
- break;
- case 'z':
- compress = 1;
- break;
- case 's':
- socket = 1;
- break;
- case '?':
- fprintf(stderr, "%s: invalid option -%c\n",
- argv[0], optopt);
+ case 'd': do_add_date = 1; break;
+ case 'o': use_outfile = optarg; break;
+ case 's': use_socket = 1; break;
+ case 'v': break; // compatibility no-op
+ case 'z': do_compress = 6; break;
+ case '?': printf("\n");
+ case 'h':
+ usage();
exit(1);
}
- } while (1);
+ }
- /* open vibrator before switching user */
- if (vibrate) {
- vibrate_fd = open("/sys/class/timed_output/vibrator/enable", O_WRONLY);
- if (vibrate_fd > 0)
- fcntl(vibrate_fd, F_SETFD, FD_CLOEXEC);
- } else
- vibrate_fd = -1;
+ /* open the vibrator before dropping root */
+ FILE *vibrator = fopen("/sys/class/timed_output/vibrator/enable", "w");
+ if (vibrator) fcntl(fileno(vibrator), F_SETFD, FD_CLOEXEC);
+
+ /* read /proc/cmdline before dropping root */
+ FILE *cmdline = fopen("/proc/cmdline", "r");
+ if (cmdline != NULL) {
+ fgets(cmdline_buf, sizeof(cmdline_buf), cmdline);
+ fclose(cmdline);
+ }
/* switch to non-root user and group */
+ gid_t groups[] = { AID_LOG, AID_SDCARD_RW };
setgroups(sizeof(groups)/sizeof(groups[0]), groups);
setuid(AID_SHELL);
- /* make it safe to use both printf and STDOUT_FILENO */
- setvbuf(stdout, 0, _IONBF, 0);
+ char path[PATH_MAX], tmp_path[PATH_MAX];
+ pid_t gzip_pid = -1;
- if (socket) {
- struct sockaddr addr;
- socklen_t alen;
-
- int s = android_get_control_socket("dumpstate");
- if (s < 0) {
- fprintf(stderr, "could not open dumpstate socket\n");
- exit(1);
+ if (use_socket) {
+ redirect_to_socket(stdout, "dumpstate");
+ } else if (use_outfile) {
+ strlcpy(path, use_outfile, sizeof(path));
+ if (do_add_date) {
+ char date[80];
+ time_t now = time(NULL);
+ strftime(date, sizeof(date), "-%Y-%m-%d-%H-%M-%S", localtime(&now));
+ strlcat(path, date, sizeof(path));
}
- if (listen(s, 4) < 0) {
- fprintf(stderr, "could not listen on dumpstate socket\n");
- exit(1);
- }
-
- alen = sizeof(addr);
- fd = accept(s, &addr, &alen);
- if (fd < 0) {
- fprintf(stderr, "could not accept dumpstate socket\n");
- exit(1);
- }
-
- /* redirect stdout to the socket */
- dup2(fd, STDOUT_FILENO);
- close(fd);
- } else if (outfile) {
- if (strlen(outfile) > sizeof(path) - 100)
- exit(1);
-
- strcpy(path, outfile);
- if (add_date) {
- char date[260];
- strftime(date, sizeof(date),
- "-%Y-%m-%d-%H-%M-%S",
- &now);
- strcat(path, date);
- }
- if (compress)
- strcat(path, ".gz");
- else
- strcat(path, ".txt");
-
- /* ensure that all directories in the path exist */
- create_directories(path);
- fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
- if (fd < 0)
- return fd;
-
- if (compress) {
- pipe(fds);
-
- /* redirect our stdout to the pipe */
- dup2(fds[1], STDOUT_FILENO);
- close(fds[1]);
-
- if ((pid = fork()) < 0)
- {
- fprintf(stderr, "fork error\n");
- exit(1);
- }
-
- if (pid) {
- /* parent case */
-
- /* close our copy of the input to gzip */
- close(fds[0]);
- /* close our copy of the output file */
- close(fd);
- } else {
- /* child case */
-
- /* redirect our input pipe to stdin */
- dup2(fds[0], STDIN_FILENO);
- close(fds[0]);
-
- /* redirect stdout to the output file */
- dup2(fd, STDOUT_FILENO);
- close(fd);
-
- /* run gzip to postprocess our output */
- execv("/system/bin/gzip", gzip_args);
- fprintf(stderr, "execv returned\n");
- }
- } else {
- /* redirect stdout to the output file */
- dup2(fd, STDOUT_FILENO);
- close(fd);
- }
- }
- /* else everything will print to stdout */
-
- if (vibrate) {
- vibrate_pattern(vibrate_fd, start_pattern);
- }
- dumpstate(!dumpcrash);
- if (vibrate) {
- vibrate_pattern(vibrate_fd, end_pattern);
- close(vibrate_fd);
+ strlcat(path, ".txt", sizeof(path));
+ if (do_compress) strlcat(path, ".gz", sizeof(path));
+ strlcpy(tmp_path, path, sizeof(tmp_path));
+ strlcat(tmp_path, ".tmp", sizeof(tmp_path));
+ gzip_pid = redirect_to_file(stdout, tmp_path, do_compress);
}
- /* so gzip will terminate */
- close(STDOUT_FILENO);
+ /* bzzzzzz */
+ if (vibrator) {
+ fputs("150", vibrator);
+ fflush(vibrator);
+ }
+
+ dumpstate();
+
+ /* bzzz bzzz bzzz */
+ if (vibrator) {
+ int i;
+ for (i = 0; i < 3; i++) {
+ fputs("75\n", vibrator);
+ fflush(vibrator);
+ usleep((75 + 50) * 1000);
+ }
+ fclose(vibrator);
+ }
+
+ /* wait for gzip to finish, otherwise it might get killed when we exit */
+ if (gzip_pid > 0) {
+ fclose(stdout);
+ waitpid(gzip_pid, NULL, 0);
+ }
+
+ /* rename the (now complete) .tmp file to its final location */
+ if (use_outfile && rename(tmp_path, path)) {
+ fprintf(stderr, "rename(%s, %s): %s\n", tmp_path, path, strerror(errno));
+ }
LOGI("done\n");
return 0;
}
-
-static void dump_kernel_log(const char *path, const char *title)
-
-{
- printf("------ KERNEL %s LOG ------\n", title);
- if (access(path, R_OK) < 0)
- printf("%s: %s\n", path, strerror(errno));
- else {
- struct stat sbuf;
-
- if (stat(path, &sbuf) < 0)
- printf("%s: stat failed (%s)\n", path, strerror(errno));
- else
- printf("Harvested %s", ctime(&sbuf.st_mtime));
-
- DUMP(path);
- }
-}
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index ed1f005..6d48a85 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -18,157 +18,24 @@
#define _DUMPSTATE_H_
#include <time.h>
-
-// Commands time out after 60 seconds
-#define TIMEOUT 60
-
-#define PRINT(s) printf("%s\n", s)
-
-#define DUMP(file) dump_file(file)
-
-#define DUMP_FILES(path) dump_files(path)
-
-#define DUMP_PROMPT(prompt, file) \
-{ \
- printf(prompt); \
- dump_file(file); \
-}
-
-#define EXEC(cmd) \
-{ \
- static struct Command c = { \
- "/system/bin/" cmd, \
- { cmd, 0 } \
- }; \
- run_command(&c, TIMEOUT); \
-}
-
-#define EXEC_TIMEOUT(cmd, tmout)\
-{ \
- static struct Command c = { \
- "/system/bin/" cmd, \
- { cmd, 0 } \
- }; \
- run_command(&c, tmout); \
-}
-
-#define EXEC_XBIN(cmd) \
-{ \
- static struct Command c = { \
- "/system/xbin/" cmd, \
- { cmd, 0 } \
- }; \
- run_command(&c, TIMEOUT); \
-}
-
-#define EXEC1(cmd, a1) \
-{ \
- static struct Command c = { \
- "/system/bin/" cmd, \
- { cmd, a1, 0 } \
- }; \
- run_command(&c, TIMEOUT); \
-}
-
-#define EXEC2(cmd, a1, a2) \
-{ \
- static struct Command c = { \
- "/system/bin/" cmd, \
- { cmd, a1, a2, 0 } \
- }; \
- run_command(&c, TIMEOUT); \
-}
-
-#define EXEC3(cmd, a1, a2, a3) \
-{ \
- static struct Command c = { \
- "/system/bin/" cmd, \
- { cmd, a1, a2, a3, 0 } \
- }; \
- run_command(&c, TIMEOUT); \
-}
-
-#define EXEC4(cmd, a1, a2, a3, a4) \
-{ \
- static struct Command c = { \
- "/system/bin/" cmd, \
- { cmd, a1, a2, a3, a4, 0 } \
- }; \
- run_command(&c, TIMEOUT); \
-}
-
-#define EXEC6(cmd, a1, a2, a3, a4, a5, a6) \
-{ \
- static struct Command c = { \
- "/system/bin/" cmd, \
- { cmd, a1, a2, a3, a4, a5, a6, 0 } \
- }; \
- run_command(&c, TIMEOUT); \
-}
-
-#define EXEC7(cmd, a1, a2, a3, a4, a5, a6, a7) \
-{ \
- static struct Command c = { \
- "/system/bin/" cmd, \
- { cmd, a1, a2, a3, a4, a5, a6, a7, 0 } \
- }; \
- run_command(&c, TIMEOUT); \
-}
-
-#define EXEC8(cmd, a1, a2, a3, a4, a5, a6, a7, a8) \
-{ \
- static struct Command c = { \
- "/system/bin/" cmd, \
- { cmd, a1, a2, a3, a4, a5, a6, a7, a8, 0 } \
- }; \
- run_command(&c, TIMEOUT); \
-}
-
-#define EXEC_XBIN6(cmd, a1, a2, a3, a4, a5, a6) \
-{ \
- static struct Command c = { \
- "/system/xbin/" cmd, \
- { cmd, a1, a2, a3, a4, a5, a6, 0 } \
- }; \
- run_command(&c, TIMEOUT); \
-}
-
-#define PROPERTY(name) print_property(name)
-
-struct Command {
- const char* path;
- char* const args[];
-};
-typedef struct Command Command;
+#include <unistd.h>
/* prints the contents of a file */
-int dump_file(const char* path);
+int dump_file(const char *title, const char* path);
-/* prints the contents of all files in a directory */
-void dump_files(const char* path);
-
-/* forks a command and waits for it to finish */
-int run_command(struct Command* cmd, int timeout);
-
-/* reads the current time into tm */
-void get_time(struct tm *tm);
-
-/* prints the date in tm */
-void print_date(const char* prompt, struct tm *tm);
-
-/* prints the name and value of a system property */
-int print_property(const char* name);
+/* forks a command and waits for it to finish -- terminate args with NULL */
+int run_command(const char *title, int timeout_seconds, const char *command, ...);
/* prints all the system properties */
void print_properties();
-/* creates directories as needed for the given path */
-void create_directories(char *path);
+/* redirect output to a service control socket */
+void redirect_to_socket(FILE *redirect, const char *service);
-/* runs the vibrator using the given pattern */
-void vibrate_pattern(int fd, int* pattern);
+/* redirect output to a file, optionally gzipping; returns gzip pid */
+pid_t redirect_to_file(FILE *redirect, char *path, int gzip_level);
-/* prevents the OOM killer from killing us */
-void protect_from_oom_killer();
+/* dump Dalvik stack traces, return the trace file location (NULL if none) */
+const char *dump_vm_traces();
#endif /* _DUMPSTATE_H_ */
diff --git a/cmds/dumpstate/utils.c b/cmds/dumpstate/utils.c
index 60d845f..fda618c 100644
--- a/cmds/dumpstate/utils.c
+++ b/cmds/dumpstate/utils.c
@@ -14,217 +14,337 @@
* limitations under the License.
*/
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <time.h>
-#include <unistd.h>
+#include <sys/inotify.h>
#include <sys/stat.h>
-#include <dirent.h>
-#include <limits.h>
-#include <fcntl.h>
-#include <signal.h>
#include <sys/time.h>
#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
#include <cutils/properties.h>
-#include <sys/system_properties.h>
+#include <cutils/sockets.h>
#include "dumpstate.h"
/* prints the contents of a file */
-int dump_file(const char* path) {
- char buffer[32768];
- int fd, amount_read;
- int ret = 0;
+int dump_file(const char *title, const char* path) {
+ char buffer[32768];
+ int fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ int err = errno;
+ if (title) printf("----- %s (%s) -----\n", title, path);
+ printf("*** %s: %s\n", path, strerror(err));
+ if (title) printf("\n");
+ return -1;
+ }
- fd = open(path, O_RDONLY);
- if (fd < 0)
- return fd;
+ if (title) printf("----- %s (%s", title, path);
- do {
- ret = read(fd, buffer, sizeof(buffer));
- if (ret > 0)
- ret = write(STDOUT_FILENO, buffer, ret);
- } while (ret > 0);
+ if (title) {
+ struct stat st;
+ if (memcmp(path, "/proc/", 6) && memcmp(path, "/sys/", 5) && !fstat(fd, &st)) {
+ char stamp[80];
+ time_t mtime = st.st_mtime;
+ strftime(stamp, sizeof(stamp), "%Y-%m-%d %H:%M:%S", localtime(&mtime));
+ printf(": %s", stamp);
+ }
+ printf(") -----\n");
+ }
- buffer[0] = '\n';
- write(STDOUT_FILENO, buffer, 1);
+ int newline = 0;
+ for (;;) {
+ int ret = read(fd, buffer, sizeof(buffer));
+ if (ret > 0) {
+ newline = (buffer[ret - 1] == '\n');
+ ret = fwrite(buffer, ret, 1, stdout);
+ }
+ if (ret <= 0) break;
+ }
close(fd);
- return ret;
-}
-
-/* prints the contents of all files in a directory */
-void dump_files(const char* path) {
- DIR* dir;
- struct dirent* entry;
- char buffer[PATH_MAX];
-
- dir = opendir(path);
- if (!dir) {
- fprintf(stderr, "could not open directory %s\n", path);
- return;
- }
-
- while ((entry = readdir(dir))) {
- if (entry->d_type == DT_REG) {
- snprintf(buffer, sizeof(buffer), "%s/%s", path, entry->d_name);
- dump_file(path);
- printf("\n");
- }
- }
-
- closedir(dir);
-}
-
-/* prints the name and value of a system property */
-int print_property(const char* name) {
- char value[PROP_VALUE_MAX];
-
- __system_property_get(name, value);
- printf("%s=%s\n", name, value);
+ if (!newline) printf("\n");
+ if (title) printf("\n");
return 0;
}
-static pid_t alarm_pid = 0;
-static int timed_out = 0;
-static void sig_alarm(int sig)
-{
- if (alarm_pid) {
- kill(alarm_pid, SIGKILL);
- timed_out = 1;
- alarm_pid = 0;
- }
-}
-
/* forks a command and waits for it to finish */
-int run_command(struct Command* cmd, int timeout) {
- struct sigaction sa;
- pid_t pid;
- int status;
+int run_command(const char *title, int timeout_seconds, const char *command, ...) {
+ fflush(stdout);
+ clock_t start = clock();
+ pid_t pid = fork();
- pid = fork();
/* handle error case */
- if (pid < 0)
+ if (pid < 0) {
+ printf("*** fork: %s\n", strerror(errno));
return pid;
+ }
/* handle child case */
if (pid == 0) {
- int ret = execv(cmd->path, cmd->args);
- if (ret)
- fprintf(stderr, "execv %s returned %d\n", cmd->path, ret);
- exit(ret);
+ const char *args[1024] = {command};
+ size_t arg;
+
+ va_list ap;
+ va_start(ap, command);
+ if (title) printf("----- %s (%s", title, command);
+ for (arg = 1; arg < sizeof(args) / sizeof(args[0]); ++arg) {
+ args[arg] = va_arg(ap, const char *);
+ if (args[arg] == NULL) break;
+ if (title) printf(" %s", args[arg]);
+ }
+ if (title) printf(") -----\n");
+ fflush(stdout);
+
+ execvp(command, (char**) args);
+ printf("*** exec(%s): %s\n", command, strerror(errno));
+ _exit(-1);
}
/* handle parent case */
- timed_out = 0;
- if (timeout) {
- memset(&sa, 0, sizeof(sa));
- sa.sa_flags = SA_RESETHAND;
- sa.sa_handler = sig_alarm;
- sigaction(SIGALRM, &sa, NULL);
+ for (;;) {
+ int status;
+ pid_t p = waitpid(pid, &status, WNOHANG);
+ float elapsed = (float) (clock() - start) / CLOCKS_PER_SEC;
+ if (p == pid) {
+ if (WIFSIGNALED(status)) {
+ printf("*** %s: Killed by signal %d\n", command, WTERMSIG(status));
+ } else if (WEXITSTATUS(status) > 0) {
+ printf("*** %s: Exit code %d\n", command, WEXITSTATUS(status));
+ }
+ if (title) printf("[%s: %.1fs elapsed]\n\n", command, elapsed);
+ return status;
+ }
- /* set an alarm so we don't hang forever */
- alarm_pid = pid;
- alarm(timeout);
+ if (timeout_seconds && elapsed > timeout_seconds) {
+ printf("*** %s: Timed out after %.1fs (killing pid %d)\n", command, elapsed, pid);
+ kill(pid, SIGTERM);
+ return -1;
+ }
+
+ usleep(100000); // poll every 0.1 sec
}
-
- waitpid(pid, &status, 0);
-
- if (timed_out)
- printf("ERROR: command %s timed out\n", cmd->path);
-
- return status;
}
-/* reads the current time into tm */
-void get_time(struct tm *tm) {
- time_t t;
+size_t num_props = 0;
+static char* props[2000];
- tzset();
- time(&t);
- localtime_r(&t, tm);
+static void print_prop(const char *key, const char *name, void *user) {
+ (void) user;
+ if (num_props < sizeof(props) / sizeof(props[0])) {
+ char buf[PROPERTY_KEY_MAX + PROPERTY_VALUE_MAX + 10];
+ snprintf(buf, sizeof(buf), "[%s]: [%s]\n", key, name);
+ props[num_props++] = strdup(buf);
+ }
}
-/* prints the date in tm */
-void print_date(const char* prompt, struct tm *tm) {
- char strbuf[260];
-
- strftime(strbuf, sizeof(strbuf),
- "%a %b %e %H:%M:%S %Z %Y",
- tm);
- printf("%s%s\n", prompt, strbuf);
-}
-
-
-static void print_prop(const char *key, const char *name,
- void *user __attribute__((unused)))
-{
- printf("[%s]: [%s]\n", key, name);
+static int compare_prop(const void *a, const void *b) {
+ return strcmp(*(char * const *) a, *(char * const *) b);
}
/* prints all the system properties */
void print_properties() {
+ size_t i;
+ num_props = 0;
property_list(print_prop, NULL);
+ qsort(&props, num_props, sizeof(props[0]), compare_prop);
+
+ printf("----- SYSTEM PROPERTIES -----\n");
+ for (i = 0; i < num_props; ++i) {
+ fputs(props[i], stdout);
+ free(props[i]);
+ }
+ printf("\n");
}
-/* creates directories as needed for the given path */
-void create_directories(char *path)
-{
+/* redirect output to a service control socket */
+void redirect_to_socket(FILE *redirect, const char *service) {
+ int s = android_get_control_socket(service);
+ if (s < 0) {
+ fprintf(stderr, "android_get_control_socket(%s): %s\n", service, strerror(errno));
+ exit(1);
+ }
+ if (listen(s, 4) < 0) {
+ fprintf(stderr, "listen(control socket): %s\n", strerror(errno));
+ exit(1);
+ }
+
+ struct sockaddr addr;
+ socklen_t alen = sizeof(addr);
+ int fd = accept(s, &addr, &alen);
+ if (fd < 0) {
+ fprintf(stderr, "accept(control socket): %s\n", strerror(errno));
+ exit(1);
+ }
+
+ fflush(redirect);
+ dup2(fd, fileno(redirect));
+ close(fd);
+}
+
+/* redirect output to a file, optionally gzipping; returns gzip pid (or -1) */
+pid_t redirect_to_file(FILE *redirect, char *path, int gzip_level) {
char *chp = path;
/* skip initial slash */
if (chp[0] == '/')
chp++;
+ /* create leading directories, if necessary */
while (chp && chp[0]) {
chp = strchr(chp, '/');
if (chp) {
*chp = 0;
- mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
- *chp = '/';
- chp++;
+ mkdir(path, 0775); /* drwxrwxr-x */
+ *chp++ = '/';
}
}
-}
-/* runs the vibrator using the given pattern */
-void vibrate_pattern(int fd, int* pattern)
-{
- struct timespec tm;
- char buffer[10];
-
- while (*pattern) {
- /* read vibrate on time */
- int on_time = *pattern++;
- snprintf(buffer, sizeof(buffer), "%d", on_time);
- write(fd, buffer, strlen(buffer));
-
- /* read vibrate off time */
- int delay = *pattern++;
- if (delay) {
- delay += on_time;
-
- tm.tv_sec = delay / 1000;
- tm.tv_nsec = (delay % 1000) * 1000000;
- nanosleep(&tm, NULL);
- } else
- break;
+ int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if (fd < 0) {
+ fprintf(stderr, "%s: %s\n", path, strerror(errno));
+ exit(1);
}
-}
-/* prevents the OOM killer from killing us */
-void protect_from_oom_killer()
-{
- int fd;
+ pid_t gzip_pid = -1;
+ if (gzip_level > 0) {
+ int fds[2];
+ if (pipe(fds)) {
+ fprintf(stderr, "pipe: %s\n", strerror(errno));
+ exit(1);
+ }
- fd = open("/proc/self/oom_adj", O_WRONLY);
- if (fd >= 0) {
- // -17 should make us immune to OOM
- const char* text = "-17";
- write(fd, text, strlen(text));
+ fflush(redirect);
+ fflush(stdout);
+
+ gzip_pid = fork();
+ if (gzip_pid < 0) {
+ fprintf(stderr, "fork: %s\n", strerror(errno));
+ exit(1);
+ }
+
+ if (gzip_pid == 0) {
+ dup2(fds[0], STDIN_FILENO);
+ dup2(fd, STDOUT_FILENO);
+
+ close(fd);
+ close(fds[0]);
+ close(fds[1]);
+
+ char level[10];
+ snprintf(level, sizeof(level), "-%d", gzip_level);
+ execlp("gzip", "gzip", level, NULL);
+ fprintf(stderr, "exec(gzip): %s\n", strerror(errno));
+ _exit(-1);
+ }
+
close(fd);
+ close(fds[0]);
+ fd = fds[1];
}
+
+ dup2(fd, fileno(redirect));
+ close(fd);
+ return gzip_pid;
+}
+
+/* dump Dalvik stack traces, return the trace file location (NULL if none) */
+const char *dump_vm_traces() {
+ char traces_path[PROPERTY_VALUE_MAX] = "";
+ property_get("dalvik.vm.stack-trace-file", traces_path, "");
+ if (!traces_path[0]) return NULL;
+
+ /* move the old traces.txt (if any) out of the way temporarily */
+ char anr_traces_path[PATH_MAX];
+ strlcpy(anr_traces_path, traces_path, sizeof(anr_traces_path));
+ strlcat(anr_traces_path, ".anr", sizeof(anr_traces_path));
+ rename(traces_path, anr_traces_path);
+
+ /* create a new, empty traces.txt file to receive stack dumps */
+ int fd = open(traces_path, O_CREAT | O_WRONLY | O_TRUNC, 0666); /* -rw-rw-rw- */
+ if (fd < 0) {
+ fprintf(stderr, "%s: %s\n", traces_path, strerror(errno));
+ return NULL;
+ }
+ close(fd);
+
+ /* walk /proc and kill -QUIT all Dalvik processes */
+ DIR *proc = opendir("/proc");
+ if (proc == NULL) {
+ fprintf(stderr, "/proc: %s\n", strerror(errno));
+ return NULL;
+ }
+
+ /* use inotify to find when processes are done dumping */
+ int ifd = inotify_init();
+ if (ifd < 0) {
+ fprintf(stderr, "inotify_init: %s\n", strerror(errno));
+ return NULL;
+ }
+
+ int wfd = inotify_add_watch(ifd, traces_path, IN_CLOSE_WRITE);
+ if (wfd < 0) {
+ fprintf(stderr, "inotify_add_watch(%s): %s\n", traces_path, strerror(errno));
+ return NULL;
+ }
+
+ struct dirent *d;
+ while ((d = readdir(proc))) {
+ int pid = atoi(d->d_name);
+ if (pid <= 0) continue;
+
+ /* identify Dalvik: /proc/(pid)/exe = /system/bin/app_process */
+ char path[PATH_MAX], data[PATH_MAX];
+ snprintf(path, sizeof(path), "/proc/%d/exe", pid);
+ size_t len = readlink(path, data, sizeof(data) - 1);
+ if (len <= 0 || memcmp(data, "/system/bin/app_process", 23)) continue;
+
+ /* skip zygote -- it won't dump its stack anyway */
+ snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);
+ int fd = open(path, O_RDONLY);
+ len = read(fd, data, sizeof(data) - 1);
+ close(fd);
+ if (len <= 0 || !memcmp(data, "zygote", 6)) continue;
+
+ if (kill(pid, SIGQUIT)) {
+ fprintf(stderr, "kill(%d, SIGQUIT): %s\n", pid, strerror(errno));
+ continue;
+ }
+
+ /* wait for the writable-close notification from inotify */
+ struct pollfd pfd = { ifd, POLLIN, 0 };
+ int ret = poll(&pfd, 1, 200); /* 200 msec timeout */
+ if (ret < 0) {
+ fprintf(stderr, "poll: %s\n", strerror(errno));
+ } else if (ret == 0) {
+ fprintf(stderr, "warning: timed out dumping pid %d\n", pid);
+ } else {
+ struct inotify_event ie;
+ read(ifd, &ie, sizeof(ie));
+ }
+ }
+
+ close(ifd);
+
+ static char dump_traces_path[PATH_MAX];
+ strlcpy(dump_traces_path, traces_path, sizeof(dump_traces_path));
+ strlcat(dump_traces_path, ".bugreport", sizeof(dump_traces_path));
+ if (rename(traces_path, dump_traces_path)) {
+ fprintf(stderr, "rename(%s, %s): %s\n", traces_path, dump_traces_path, strerror(errno));
+ return NULL;
+ }
+
+ /* replace the saved [ANR] traces.txt file */
+ rename(anr_traces_path, traces_path);
+ return dump_traces_path;
}
diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c
index dcae0c7..79bda74 100644
--- a/cmds/installd/commands.c
+++ b/cmds/installd/commands.c
@@ -218,14 +218,20 @@
static int is_valid_apk_path(const char *path)
{
int len = strlen(APK_DIR_PREFIX);
+int nosubdircheck = 0;
if (strncmp(path, APK_DIR_PREFIX, len)) {
len = strlen(PROTECTED_DIR_PREFIX);
if (strncmp(path, PROTECTED_DIR_PREFIX, len)) {
- LOGE("invalid apk path '%s' (bad prefix)\n", path);
- return 0;
+ len = strlen(SDCARD_DIR_PREFIX);
+ if (strncmp(path, SDCARD_DIR_PREFIX, len)) {
+ LOGE("invalid apk path '%s' (bad prefix)\n", path);
+ return 0;
+ } else {
+ nosubdircheck = 1;
+ }
}
}
- if (strchr(path + len, '/')) {
+ if ((nosubdircheck != 1) && strchr(path + len, '/')) {
LOGE("invalid apk path '%s' (subdir?)\n", path);
return 0;
}
diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h
index 1679d14..35a173e 100644
--- a/cmds/installd/installd.h
+++ b/cmds/installd/installd.h
@@ -68,6 +68,7 @@
/* other handy constants */
#define PROTECTED_DIR_PREFIX "/data/app-private/"
+#define SDCARD_DIR_PREFIX "/asec/"
#define DALVIK_CACHE_PREFIX "/data/dalvik-cache/"
#define DALVIK_CACHE_POSTFIX "/classes.dex"
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 79eb310b..4953f5d 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -594,6 +594,8 @@
}
} else if (opt.equals("-t")) {
installFlags |= PackageManager.INSTALL_ALLOW_TEST;
+ } else if (opt.equals("-s")) {
+ installFlags |= PackageManager.INSTALL_ON_SDCARD;
} else {
System.err.println("Error: Unknown option: " + opt);
showUsage();
@@ -822,7 +824,7 @@
System.err.println(" pm list instrumentation [-f] [TARGET-PACKAGE]");
System.err.println(" pm list features");
System.err.println(" pm path PACKAGE");
- System.err.println(" pm install [-l] [-r] [-t] [-i INSTALLER_PACKAGE_NAME] PATH");
+ System.err.println(" pm install [-l] [-r] [-t] [-i INSTALLER_PACKAGE_NAME] [-s] PATH");
System.err.println(" pm uninstall [-k] PACKAGE");
System.err.println(" pm enable PACKAGE_OR_COMPONENT");
System.err.println(" pm disable PACKAGE_OR_COMPONENT");
@@ -854,6 +856,7 @@
System.err.println(" -r: reinstall an exisiting app, keeping its data.");
System.err.println(" -t: allow test .apks to be installed.");
System.err.println(" -i: specify the installer package name.");
+ System.err.println(" -s: install package on sdcard.");
System.err.println("");
System.err.println("The uninstall command removes a package from the system. Options:");
System.err.println(" -k: keep the data and cache directories around.");
diff --git a/common/java/com/android/common/DNParser.java b/common/java/com/android/common/DNParser.java
new file mode 100644
index 0000000..32d57c0
--- /dev/null
+++ b/common/java/com/android/common/DNParser.java
@@ -0,0 +1,447 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.common;
+
+import android.util.Log;
+
+import java.io.IOException;
+
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * A simple distinguished name(DN) parser.
+ *
+ * <p>This class is based on org.apache.harmony.security.x509.DNParser. It's customized to remove
+ * external references which are unnecessary for our requirements.
+ *
+ * <p>This class is only meant for extracting a string value from a DN. e.g. it doesn't support
+ * values in the hex-string style.
+ *
+ * <p>This class is used by {@link DomainNameValidator} only. However, in order to make this
+ * class visible from unit tests, it's made public.
+ */
+public final class DNParser {
+ private static final String TAG = "DNParser";
+
+ /** DN to be parsed. */
+ private final String dn;
+
+ // length of distinguished name string
+ private final int length;
+
+ private int pos, beg, end;
+
+ // tmp vars to store positions of the currently parsed item
+ private int cur;
+
+ // distinguished name chars
+ private char[] chars;
+
+ /**
+ * Exception message thrown when we failed to parse DN, which shouldn't happen because we
+ * only handle DNs that {@link X500Principal#getName} returns, which shouldn't be malformed.
+ */
+ private static final String ERROR_PARSE_ERROR = "Failed to parse DN";
+
+ /**
+ * Constructor.
+ *
+ * @param principal - {@link X500Principal} to be parsed
+ */
+ public DNParser(X500Principal principal) {
+ this.dn = principal.getName(X500Principal.RFC2253);
+ this.length = dn.length();
+ }
+
+ // gets next attribute type: (ALPHA 1*keychar) / oid
+ private String nextAT() throws IOException {
+
+ // skip preceding space chars, they can present after
+ // comma or semicolon (compatibility with RFC 1779)
+ for (; pos < length && chars[pos] == ' '; pos++) {
+ }
+ if (pos == length) {
+ return null; // reached the end of DN
+ }
+
+ // mark the beginning of attribute type
+ beg = pos;
+
+ // attribute type chars
+ pos++;
+ for (; pos < length && chars[pos] != '=' && chars[pos] != ' '; pos++) {
+ // we don't follow exact BNF syntax here:
+ // accept any char except space and '='
+ }
+ if (pos >= length) {
+ // unexpected end of DN
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ // mark the end of attribute type
+ end = pos;
+
+ // skip trailing space chars between attribute type and '='
+ // (compatibility with RFC 1779)
+ if (chars[pos] == ' ') {
+ for (; pos < length && chars[pos] != '=' && chars[pos] == ' '; pos++) {
+ }
+
+ if (chars[pos] != '=' || pos == length) {
+ // unexpected end of DN
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+ }
+
+ pos++; //skip '=' char
+
+ // skip space chars between '=' and attribute value
+ // (compatibility with RFC 1779)
+ for (; pos < length && chars[pos] == ' '; pos++) {
+ }
+
+ // in case of oid attribute type skip its prefix: "oid." or "OID."
+ // (compatibility with RFC 1779)
+ if ((end - beg > 4) && (chars[beg + 3] == '.')
+ && (chars[beg] == 'O' || chars[beg] == 'o')
+ && (chars[beg + 1] == 'I' || chars[beg + 1] == 'i')
+ && (chars[beg + 2] == 'D' || chars[beg + 2] == 'd')) {
+ beg += 4;
+ }
+
+ return new String(chars, beg, end - beg);
+ }
+
+ // gets quoted attribute value: QUOTATION *( quotechar / pair ) QUOTATION
+ private String quotedAV() throws IOException {
+
+ pos++;
+ beg = pos;
+ end = beg;
+ while (true) {
+
+ if (pos == length) {
+ // unexpected end of DN
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ if (chars[pos] == '"') {
+ // enclosing quotation was found
+ pos++;
+ break;
+ } else if (chars[pos] == '\\') {
+ chars[end] = getEscaped();
+ } else {
+ // shift char: required for string with escaped chars
+ chars[end] = chars[pos];
+ }
+ pos++;
+ end++;
+ }
+
+ // skip trailing space chars before comma or semicolon.
+ // (compatibility with RFC 1779)
+ for (; pos < length && chars[pos] == ' '; pos++) {
+ }
+
+ return new String(chars, beg, end - beg);
+ }
+
+ // gets hex string attribute value: "#" hexstring
+ private String hexAV() throws IOException {
+
+ if (pos + 4 >= length) {
+ // encoded byte array must be not less then 4 c
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ beg = pos; // store '#' position
+ pos++;
+ while (true) {
+
+ // check for end of attribute value
+ // looks for space and component separators
+ if (pos == length || chars[pos] == '+' || chars[pos] == ','
+ || chars[pos] == ';') {
+ end = pos;
+ break;
+ }
+
+ if (chars[pos] == ' ') {
+ end = pos;
+ pos++;
+ // skip trailing space chars before comma or semicolon.
+ // (compatibility with RFC 1779)
+ for (; pos < length && chars[pos] == ' '; pos++) {
+ }
+ break;
+ } else if (chars[pos] >= 'A' && chars[pos] <= 'F') {
+ chars[pos] += 32; //to low case
+ }
+
+ pos++;
+ }
+
+ // verify length of hex string
+ // encoded byte array must be not less then 4 and must be even number
+ int hexLen = end - beg; // skip first '#' char
+ if (hexLen < 5 || (hexLen & 1) == 0) {
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ // get byte encoding from string representation
+ byte[] encoded = new byte[hexLen / 2];
+ for (int i = 0, p = beg + 1; i < encoded.length; p += 2, i++) {
+ encoded[i] = (byte) getByte(p);
+ }
+
+ return new String(chars, beg, hexLen);
+ }
+
+ // gets string attribute value: *( stringchar / pair )
+ private String escapedAV() throws IOException {
+
+ beg = pos;
+ end = pos;
+ while (true) {
+
+ if (pos >= length) {
+ // the end of DN has been found
+ return new String(chars, beg, end - beg);
+ }
+
+ switch (chars[pos]) {
+ case '+':
+ case ',':
+ case ';':
+ // separator char has beed found
+ return new String(chars, beg, end - beg);
+ case '\\':
+ // escaped char
+ chars[end++] = getEscaped();
+ pos++;
+ break;
+ case ' ':
+ // need to figure out whether space defines
+ // the end of attribute value or not
+ cur = end;
+
+ pos++;
+ chars[end++] = ' ';
+
+ for (; pos < length && chars[pos] == ' '; pos++) {
+ chars[end++] = ' ';
+ }
+ if (pos == length || chars[pos] == ',' || chars[pos] == '+'
+ || chars[pos] == ';') {
+ // separator char or the end of DN has beed found
+ return new String(chars, beg, cur - beg);
+ }
+ break;
+ default:
+ chars[end++] = chars[pos];
+ pos++;
+ }
+ }
+ }
+
+ // returns escaped char
+ private char getEscaped() throws IOException {
+
+ pos++;
+ if (pos == length) {
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ switch (chars[pos]) {
+ case '"':
+ case '\\':
+ case ',':
+ case '=':
+ case '+':
+ case '<':
+ case '>':
+ case '#':
+ case ';':
+ case ' ':
+ case '*':
+ case '%':
+ case '_':
+ //FIXME: escaping is allowed only for leading or trailing space char
+ return chars[pos];
+ default:
+ // RFC doesn't explicitly say that escaped hex pair is
+ // interpreted as UTF-8 char. It only contains an example of such DN.
+ return getUTF8();
+ }
+ }
+
+ // decodes UTF-8 char
+ // see http://www.unicode.org for UTF-8 bit distribution table
+ private char getUTF8() throws IOException {
+
+ int res = getByte(pos);
+ pos++; //FIXME tmp
+
+ if (res < 128) { // one byte: 0-7F
+ return (char) res;
+ } else if (res >= 192 && res <= 247) {
+
+ int count;
+ if (res <= 223) { // two bytes: C0-DF
+ count = 1;
+ res = res & 0x1F;
+ } else if (res <= 239) { // three bytes: E0-EF
+ count = 2;
+ res = res & 0x0F;
+ } else { // four bytes: F0-F7
+ count = 3;
+ res = res & 0x07;
+ }
+
+ int b;
+ for (int i = 0; i < count; i++) {
+ pos++;
+ if (pos == length || chars[pos] != '\\') {
+ return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
+ }
+ pos++;
+
+ b = getByte(pos);
+ pos++; //FIXME tmp
+ if ((b & 0xC0) != 0x80) {
+ return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
+ }
+
+ res = (res << 6) + (b & 0x3F);
+ }
+ return (char) res;
+ } else {
+ return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
+ }
+ }
+
+ // Returns byte representation of a char pair
+ // The char pair is composed of DN char in
+ // specified 'position' and the next char
+ // According to BNF syntax:
+ // hexchar = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
+ // / "a" / "b" / "c" / "d" / "e" / "f"
+ private int getByte(int position) throws IOException {
+
+ if ((position + 1) >= length) {
+ // to avoid ArrayIndexOutOfBoundsException
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ int b1, b2;
+
+ b1 = chars[position];
+ if (b1 >= '0' && b1 <= '9') {
+ b1 = b1 - '0';
+ } else if (b1 >= 'a' && b1 <= 'f') {
+ b1 = b1 - 87; // 87 = 'a' - 10
+ } else if (b1 >= 'A' && b1 <= 'F') {
+ b1 = b1 - 55; // 55 = 'A' - 10
+ } else {
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ b2 = chars[position + 1];
+ if (b2 >= '0' && b2 <= '9') {
+ b2 = b2 - '0';
+ } else if (b2 >= 'a' && b2 <= 'f') {
+ b2 = b2 - 87; // 87 = 'a' - 10
+ } else if (b2 >= 'A' && b2 <= 'F') {
+ b2 = b2 - 55; // 55 = 'A' - 10
+ } else {
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ return (b1 << 4) + b2;
+ }
+
+ /**
+ * Parses the DN and returns the attribute value for an attribute type.
+ *
+ * @param attributeType attribute type to look for (e.g. "ca")
+ * @return value of the attribute that first found, or null if none found
+ */
+ public String find(String attributeType) {
+ try {
+ // Initialize internal state.
+ pos = 0;
+ beg = 0;
+ end = 0;
+ cur = 0;
+ chars = dn.toCharArray();
+
+ String attType = nextAT();
+ if (attType == null) {
+ return null;
+ }
+ while (true) {
+ String attValue = "";
+
+ if (pos == length) {
+ return null;
+ }
+
+ switch (chars[pos]) {
+ case '"':
+ attValue = quotedAV();
+ break;
+ case '#':
+ attValue = hexAV();
+ break;
+ case '+':
+ case ',':
+ case ';': // compatibility with RFC 1779: semicolon can separate RDNs
+ //empty attribute value
+ break;
+ default:
+ attValue = escapedAV();
+ }
+
+ if (attributeType.equalsIgnoreCase(attType)) {
+ return attValue;
+ }
+
+ if (pos >= length) {
+ return null;
+ }
+
+ if (chars[pos] == ',' || chars[pos] == ';') {
+ } else if (chars[pos] != '+') {
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ pos++;
+ attType = nextAT();
+ if (attType == null) {
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+ }
+ } catch (IOException e) {
+ // Parse error shouldn't happen, because we only handle DNs that
+ // X500Principal.getName() returns, which shouldn't be malformed.
+ Log.e(TAG, "Failed to parse DN: " + dn);
+ return null;
+ }
+ }
+}
diff --git a/core/java/android/net/http/DomainNameChecker.java b/common/java/com/android/common/DomainNameValidator.java
similarity index 85%
rename from core/java/android/net/http/DomainNameChecker.java
rename to common/java/com/android/common/DomainNameValidator.java
index 3e01d2c..ad44a7d 100644
--- a/core/java/android/net/http/DomainNameChecker.java
+++ b/common/java/com/android/common/DomainNameValidator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2010 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.
@@ -13,29 +13,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.common;
-package android.net.http;
-
-import org.bouncycastle.asn1.x509.X509Name;
+import android.util.Config;
+import android.util.Log;
import java.net.InetAddress;
import java.net.UnknownHostException;
-import java.security.cert.X509Certificate;
import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
-import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
-import java.util.Vector;
-/**
- * Implements basic domain-name validation as specified by RFC2818.
- *
- * {@hide}
- */
-public class DomainNameChecker {
+import javax.security.auth.x500.X500Principal;
+
+public class DomainNameValidator {
+ private final static String TAG = "DomainNameValidator";
+
+ private static final boolean DEBUG = false;
+ private static final boolean LOG_ENABLED = DEBUG ? Config.LOGD : Config.LOGV;
+
private static Pattern QUICK_IP_PATTERN;
static {
try {
@@ -84,8 +84,8 @@
errorMessage = "unknown host exception";
}
- if (HttpLog.LOGV) {
- HttpLog.v("DomainNameChecker.isIpAddress(): " + errorMessage);
+ if (LOG_ENABLED) {
+ Log.v(TAG, "DomainNameValidator.isIpAddress(): " + errorMessage);
}
rval = false;
@@ -102,8 +102,8 @@
* @return True iff if there is a domain match as specified by RFC2818
*/
private static boolean matchIpAddress(X509Certificate certificate, String thisDomain) {
- if (HttpLog.LOGV) {
- HttpLog.v("DomainNameChecker.matchIpAddress(): this domain: " + thisDomain);
+ if (LOG_ENABLED) {
+ Log.v(TAG, "DomainNameValidator.matchIpAddress(): this domain: " + thisDomain);
}
try {
@@ -118,8 +118,8 @@
if (altNameType.intValue() == ALT_IPA_NAME) {
String altName = (String)(altNameEntry.get(1));
if (altName != null) {
- if (HttpLog.LOGV) {
- HttpLog.v("alternative IP: " + altName);
+ if (LOG_ENABLED) {
+ Log.v(TAG, "alternative IP: " + altName);
}
if (thisDomain.equalsIgnoreCase(altName)) {
return true;
@@ -171,26 +171,26 @@
// spec (a valid DNS name must start with a letter); there is no
// good way around this, and in order to be compatible we proceed
// to check the common name (ie, ignore alternative names)
- if (HttpLog.LOGV) {
+ if (LOG_ENABLED) {
String errorMessage = e.getMessage();
if (errorMessage == null) {
errorMessage = "failed to parse certificate";
}
- if (HttpLog.LOGV) {
- HttpLog.v("DomainNameChecker.matchDns(): " + errorMessage);
- }
+ Log.v(TAG, "DomainNameValidator.matchDns(): " + errorMessage);
}
}
if (!hasDns) {
- X509Name xName = new X509Name(certificate.getSubjectDN().getName());
- Vector val = xName.getValues();
- Vector oid = xName.getOIDs();
- for (int i = 0; i < oid.size(); i++) {
- if (oid.elementAt(i).equals(X509Name.CN)) {
- return matchDns(thisDomain, (String)(val.elementAt(i)));
- }
+ final String cn = new DNParser(certificate.getSubjectX500Principal())
+ .find("cn");
+ if (LOG_ENABLED) {
+ Log.v(TAG, "Validating subject: DN:"
+ + certificate.getSubjectX500Principal().getName(X500Principal.CANONICAL)
+ + " CN:" + cn);
+ }
+ if (cn != null) {
+ return matchDns(thisDomain, cn);
}
}
@@ -202,9 +202,10 @@
* @param thatDomain The domain name from the certificate
* @return True iff thisDomain matches thatDomain as specified by RFC2818
*/
- private static boolean matchDns(String thisDomain, String thatDomain) {
- if (HttpLog.LOGV) {
- HttpLog.v("DomainNameChecker.matchDns():" +
+ // not private for testing
+ public static boolean matchDns(String thisDomain, String thatDomain) {
+ if (LOG_ENABLED) {
+ Log.v(TAG, "DomainNameValidator.matchDns():" +
" this domain: " + thisDomain +
" that domain: " + thatDomain);
}
diff --git a/common/java/com/android/common/Patterns.java b/common/java/com/android/common/Patterns.java
index 2eab3e1..24a18c0 100644
--- a/common/java/com/android/common/Patterns.java
+++ b/common/java/com/android/common/Patterns.java
@@ -122,7 +122,7 @@
public static final Pattern EMAIL_ADDRESS
= Pattern.compile(
- "[a-zA-Z0-9\\+\\.\\_\\%\\-]{1,256}" +
+ "[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" +
"\\@" +
"[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
"(" +
diff --git a/common/tests/src/com/android/common/DNParserTest.java b/common/tests/src/com/android/common/DNParserTest.java
new file mode 100644
index 0000000..34b140a
--- /dev/null
+++ b/common/tests/src/com/android/common/DNParserTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.common;
+
+import javax.security.auth.x500.X500Principal;
+
+import junit.framework.TestCase;
+
+public class DNParserTest extends TestCase {
+ public void testFind() {
+ checkFind("", "cn", null);
+ checkFind("ou=xxx", "cn", null);
+ checkFind("ou=xxx,cn=xxx", "cn", "xxx");
+ checkFind("ou=xxx+cn=yyy,cn=zzz+cn=abc", "cn", "yyy");
+ checkFind("2.5.4.3=a,ou=xxx", "cn", "a"); // OID
+ checkFind("cn=a,cn=b", "cn", "a");
+ checkFind("ou=Cc,ou=Bb,ou=Aa", "ou", "Cc");
+ checkFind("cn=imap.gmail.com", "cn", "imap.gmail.com");
+
+ // Quoted string (see http://www.ietf.org/rfc/rfc2253.txt)
+ checkFind("o=\"\\\" a ,=<>#;\"", "o", "\" a ,=<>#;");
+ checkFind("o=abc\\,def", "o", "abc,def");
+
+ // UTF-8 (example in rfc 2253)
+ checkFind("cn=Lu\\C4\\8Di\\C4\\87", "cn", "\u004c\u0075\u010d\u0069\u0107");
+
+ // whitespaces
+ checkFind("ou=a, o= a b ,cn=x", "o", "a b");
+ checkFind("o=\" a b \" ,cn=x", "o", " a b ");
+ }
+
+ private void checkFind(String dn, String attrType, String expected) {
+ String actual = new DNParser(new X500Principal(dn)).find(attrType);
+ assertEquals("dn:" + dn + " attr:" + attrType, expected, actual);
+ }
+}
diff --git a/common/tests/src/com/android/common/DomainNameValidatorTest.java b/common/tests/src/com/android/common/DomainNameValidatorTest.java
new file mode 100644
index 0000000..4fdd4cdc
--- /dev/null
+++ b/common/tests/src/com/android/common/DomainNameValidatorTest.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.common;
+
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.security.auth.x500.X500Principal;
+
+import junit.framework.TestCase;
+
+public class DomainNameValidatorTest extends TestCase {
+ private static final int ALT_UNKNOWN = 0;
+ private static final int ALT_DNS_NAME = 2;
+ private static final int ALT_IPA_NAME = 7;
+
+ /**
+ * Tests {@link DomainNameValidator#match}
+ */
+ public void testMatch() {
+ // TODO Use actual X509Certificate objects, instead of StubX509Certificate.
+ // Comment in DomainNameValidator suggests X509Certificate fails to parse a certificate
+ // if subject alternative names contain a domain name that begins with '*'.
+ // This test won't cover this kind of errors.
+
+ checkMatch("11", new StubX509Certificate("cn=imap.g.com"), "imap.g.com", true);
+ checkMatch("12", new StubX509Certificate("cn=imap2.g.com"), "imap.g.com", false);
+ checkMatch("13", new StubX509Certificate("cn=sub.imap.g.com"), "imap.g.com", false);
+
+ // If a subjectAltName extension of type dNSName is present, that MUST
+ // be used as the identity
+ checkMatch("21", new StubX509Certificate("")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "a.y.com")
+ , "imap.g.com", false);
+ checkMatch("22", new StubX509Certificate("cn=imap.g.com") // This cn should be ignored
+ .addSubjectAlternativeName(ALT_DNS_NAME, "a.y.com")
+ , "imap.g.com", false);
+ checkMatch("23", new StubX509Certificate("")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com")
+ , "imap.g.com", true);
+
+ // With wildcards
+ checkMatch("24", new StubX509Certificate("")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "*.g.com")
+ , "imap.g.com", true);
+
+
+ // host name is ip address
+ checkMatch("31", new StubX509Certificate("")
+ .addSubjectAlternativeName(ALT_IPA_NAME, "1.2.3.4")
+ , "1.2.3.4", true);
+ checkMatch("32", new StubX509Certificate("")
+ .addSubjectAlternativeName(ALT_IPA_NAME, "1.2.3.4")
+ , "1.2.3.5", false);
+ checkMatch("32", new StubX509Certificate("")
+ .addSubjectAlternativeName(ALT_IPA_NAME, "1.2.3.4")
+ .addSubjectAlternativeName(ALT_IPA_NAME, "192.168.100.1")
+ , "192.168.100.1", true);
+
+ // Has unknown subject alternative names
+ checkMatch("41", new StubX509Certificate("")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 2")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "*.google.com")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com")
+ .addSubjectAlternativeName(ALT_IPA_NAME, "2.33.44.55")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3")
+ , "imap.g.com", true);
+
+ checkMatch("42", new StubX509Certificate("")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 2")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "*.google.com")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com")
+ .addSubjectAlternativeName(ALT_IPA_NAME, "2.33.44.55")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3")
+ , "2.33.44.55", true);
+
+ checkMatch("43", new StubX509Certificate("")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 2")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "*.google.com")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com")
+ .addSubjectAlternativeName(ALT_IPA_NAME, "2.33.44.55")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3")
+ , "g.com", false);
+
+ checkMatch("44", new StubX509Certificate("")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 2")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "*.google.com")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com")
+ .addSubjectAlternativeName(ALT_IPA_NAME, "2.33.44.55")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3")
+ , "2.33.44.1", false);
+ }
+
+ private void checkMatch(String message, X509Certificate certificate, String thisDomain,
+ boolean expected) {
+ Boolean actual = DomainNameValidator.match(certificate, thisDomain);
+ assertEquals(message, (Object) expected, (Object) actual);
+ }
+
+ /**
+ * Tests {@link DomainNameValidator#matchDns}
+ */
+ public void testMatchDns() {
+ checkMatchDns("11", "a.b.c.d", "a.b.c.d", true);
+ checkMatchDns("12", "a.b.c.d", "*.b.c.d", true);
+ checkMatchDns("13", "b.c.d", "*.b.c.d", true);
+ checkMatchDns("14", "b.c.d", "b*.c.d", true);
+
+ checkMatchDns("15", "a.b.c.d", "*.*.c.d", false);
+ checkMatchDns("16", "a.b.c.d", "*.c.d", false);
+
+ checkMatchDns("21", "imap.google.com", "imap.google.com", true);
+ checkMatchDns("22", "imap2.google.com", "imap.google.com", false);
+ checkMatchDns("23", "imap.google.com", "*.google.com", true);
+ checkMatchDns("24", "imap2.google.com", "*.google.com", true);
+ checkMatchDns("25", "imap.google.com", "*.googl.com", false);
+ checkMatchDns("26", "imap2.google2.com", "*.google3.com", false);
+ checkMatchDns("27", "imap.google.com", "ima*.google.com", true);
+ checkMatchDns("28", "imap.google.com", "imap*.google.com", true);
+ checkMatchDns("29", "imap.google.com", "*.imap.google.com", true);
+
+ checkMatchDns("41", "imap.google.com", "a*.google.com", false);
+ checkMatchDns("42", "imap.google.com", "ix*.google.com", false);
+
+ checkMatchDns("51", "imap.google.com", "iMap.Google.Com", true);
+ }
+
+ private void checkMatchDns(String message, String thisDomain, String thatDomain,
+ boolean expected) {
+ boolean actual = DomainNameValidator.matchDns(thisDomain, thatDomain);
+ assertEquals(message, expected, actual);
+ }
+
+ /**
+ * Minimal {@link X509Certificate} implementation for {@link DomainNameValidator}.
+ */
+ private static class StubX509Certificate extends X509Certificate {
+ private final X500Principal subjectX500Principal;
+ private Collection<List<?>> subjectAlternativeNames;
+
+ public StubX509Certificate(String subjectDn) {
+ subjectX500Principal = new X500Principal(subjectDn);
+ subjectAlternativeNames = null;
+ }
+
+ public StubX509Certificate addSubjectAlternativeName(int type, String name) {
+ if (subjectAlternativeNames == null) {
+ subjectAlternativeNames = new ArrayList<List<?>>();
+ }
+ LinkedList<Object> entry = new LinkedList<Object>();
+ entry.add(type);
+ entry.add(name);
+ subjectAlternativeNames.add(entry);
+ return this;
+ }
+
+ @Override
+ public Collection<List<?>> getSubjectAlternativeNames() throws CertificateParsingException {
+ return subjectAlternativeNames;
+ }
+
+ @Override
+ public X500Principal getSubjectX500Principal() {
+ return subjectX500Principal;
+ }
+
+ @Override
+ public void checkValidity() throws CertificateExpiredException,
+ CertificateNotYetValidException {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public void checkValidity(Date date) throws CertificateExpiredException,
+ CertificateNotYetValidException {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public int getBasicConstraints() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public Principal getIssuerDN() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public boolean[] getIssuerUniqueID() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public boolean[] getKeyUsage() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public Date getNotAfter() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public Date getNotBefore() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public BigInteger getSerialNumber() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public String getSigAlgName() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public String getSigAlgOID() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public byte[] getSigAlgParams() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public byte[] getSignature() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public Principal getSubjectDN() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public boolean[] getSubjectUniqueID() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public byte[] getTBSCertificate() throws CertificateEncodingException {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public int getVersion() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public byte[] getEncoded() throws CertificateEncodingException {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public PublicKey getPublicKey() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public String toString() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
+ InvalidKeyException, NoSuchProviderException, SignatureException {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public void verify(PublicKey key, String sigProvider) throws CertificateException,
+ NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
+ SignatureException {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ public Set<String> getCriticalExtensionOIDs() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ public byte[] getExtensionValue(String oid) {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ public Set<String> getNonCriticalExtensionOIDs() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ public boolean hasUnsupportedCriticalExtension() {
+ throw new RuntimeException("Method not implemented");
+ }
+ }
+}
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 9765496..3bbfce8 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -230,6 +230,47 @@
}
/**
+ * Tests that the given account has the specified features. If this account does not exist
+ * then this call returns false.
+ * <p>
+ * This call returns immediately but runs asynchronously and the result is accessed via the
+ * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
+ * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
+ * method asynchronously then they will generally pass in a callback object that will get
+ * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
+ * they will generally pass null for the callback and instead call
+ * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
+ * which will then block until the request completes.
+ * <p>
+ * Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS}.
+ *
+ * @param account The {@link Account} to test
+ * @param features the features for which to test
+ * @param callback A callback to invoke when the request completes. If null then
+ * no callback is invoked.
+ * @param handler The {@link Handler} to use to invoke the callback. If null then the
+ * main thread's {@link Handler} is used.
+ * @return an {@link AccountManagerFuture} that represents the future result of the call.
+ * The future result is a {@link Boolean} that is true if the account exists and has the
+ * specified features.
+ */
+ public AccountManagerFuture<Boolean> testHasFeatures(final Account account,
+ final String[] features,
+ AccountManagerCallback<Boolean> callback, Handler handler) {
+ return new Future2Task<Boolean>(handler, callback) {
+ public void doWork() throws RemoteException {
+ mService.testHasFeatures(mResponse, account, features);
+ }
+ public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
+ if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
+ throw new AuthenticatorException("no result in response");
+ }
+ return bundle.getBoolean(KEY_BOOLEAN_RESULT);
+ }
+ }.start();
+ }
+
+ /**
* Add an account to the AccountManager's set of known accounts.
* <p>
* Requires that the caller has permission
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index d5a9b02..f5166c2 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -449,6 +449,64 @@
return db.insert(TABLE_EXTRAS, EXTRAS_KEY, values);
}
+ public void testHasFeatures(IAccountManagerResponse response,
+ Account account, String[] features) {
+ checkReadAccountsPermission();
+ long identityToken = clearCallingIdentity();
+ try {
+ new TestFeaturesSession(response, account, features).bind();
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ private class TestFeaturesSession extends Session {
+ private final String[] mFeatures;
+ private final Account mAccount;
+
+ public TestFeaturesSession(IAccountManagerResponse response,
+ Account account, String[] features) {
+ super(response, account.type, false /* expectActivityLaunch */);
+ mFeatures = features;
+ mAccount = account;
+ }
+
+ public void run() throws RemoteException {
+ try {
+ mAuthenticator.hasFeatures(this, mAccount, mFeatures);
+ } catch (RemoteException e) {
+ onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "remote exception");
+ }
+ }
+
+ public void onResult(Bundle result) {
+ IAccountManagerResponse response = getResponseAndClose();
+ if (response != null) {
+ try {
+ if (result == null) {
+ onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle");
+ return;
+ }
+ final Bundle newResult = new Bundle();
+ newResult.putBoolean(AccountManager.KEY_BOOLEAN_RESULT,
+ result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false));
+ response.onResult(newResult);
+ } catch (RemoteException e) {
+ // if the caller is dead then there is no one to care about remote exceptions
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "failure while notifying response", e);
+ }
+ }
+ }
+ }
+
+ protected String toDebugString(long now) {
+ return super.toDebugString(now) + ", testHasFeatures"
+ + ", " + mAccount
+ + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null);
+ }
+ }
+
public void removeAccount(IAccountManagerResponse response, Account account) {
checkManageAccountsPermission();
long identityToken = clearCallingIdentity();
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 0e318c0..cbd26ee 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -31,6 +31,8 @@
String getUserData(in Account account, String key);
AuthenticatorDescription[] getAuthenticatorTypes();
Account[] getAccounts(String accountType);
+ void testHasFeatures(in IAccountManagerResponse response, in Account account,
+ in String[] features);
void getAccountsByFeatures(in IAccountManagerResponse response, String accountType, in String[] features);
boolean addAccount(in Account account, String password, in Bundle extras);
void removeAccount(in IAccountManagerResponse response, in Account account);
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 5961ef5..981145b 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -1332,20 +1332,11 @@
public final static String EXTRA_DATA_KEY = "intent_extra_data_key";
/**
- * String extra data key for {@link Intent#ACTION_GLOBAL_SEARCH} intents. Contains the initial
- * query to show in the global search activity.
- *
- * @hide Pending API council approval
+ * Boolean extra data key for {@link #INTENT_ACTION_GLOBAL_SEARCH} intents. If {@code true},
+ * the initial query should be selected when the global search activity is started, so
+ * that the user can easily replace it with another query.
*/
- public final static String INITIAL_QUERY = "initial_query";
-
- /**
- * Boolean extra data key for {@link Intent#ACTION_GLOBAL_SEARCH} intents. If {@code true},
- * the initial query should be selected.
- *
- * @hide Pending API council approval
- */
- public final static String SELECT_INITIAL_QUERY = "select_initial_query";
+ public final static String EXTRA_SELECT_QUERY = "select_query";
/**
* Defines the constants used in the communication between {@link android.app.SearchDialog} and
@@ -1643,10 +1634,12 @@
= "android.search.action.CHANGE_SEARCH_SOURCE";
/**
- * Intent action for finding the global search activity.
+ * Intent action for starting the global search activity.
* The global search provider should handle this intent.
- *
- * @hide Pending API council approval.
+ *
+ * Supported extra data keys: {@link #QUERY},
+ * {@link #EXTRA_SELECT_QUERY},
+ * {@link #APP_DATA}.
*/
public final static String INTENT_ACTION_GLOBAL_SEARCH
= "android.search.action.GLOBAL_SEARCH";
@@ -1816,17 +1809,17 @@
Log.w(TAG, "No global search activity found.");
return;
}
- Intent intent = new Intent(Intent.ACTION_GLOBAL_SEARCH);
+ Intent intent = new Intent(INTENT_ACTION_GLOBAL_SEARCH);
intent.setComponent(globalSearchActivity);
// TODO: Always pass name of calling package as an extra?
if (appSearchData != null) {
intent.putExtra(APP_DATA, appSearchData);
}
if (!TextUtils.isEmpty(initialQuery)) {
- intent.putExtra(INITIAL_QUERY, initialQuery);
+ intent.putExtra(QUERY, initialQuery);
}
if (selectInitialQuery) {
- intent.putExtra(SELECT_INITIAL_QUERY, selectInitialQuery);
+ intent.putExtra(EXTRA_SELECT_QUERY, selectInitialQuery);
}
try {
if (DBG) Log.d(TAG, "Starting global search: " + intent.toUri(0));
@@ -1847,7 +1840,7 @@
* we have settled on the right mechanism for finding the global search activity.
*/
private ComponentName getGlobalSearchActivity() {
- Intent intent = new Intent(Intent.ACTION_GLOBAL_SEARCH);
+ Intent intent = new Intent(INTENT_ACTION_GLOBAL_SEARCH);
PackageManager pm = mContext.getPackageManager();
List<ResolveInfo> activities =
pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
@@ -2030,8 +2023,23 @@
}
/**
- * Gets information about a searchable activity. This method is static so that it can
- * be used from non-Activity contexts.
+ * Gets information about a searchable activity.
+ *
+ * @param componentName The activity to get searchable information for.
+ * @return Searchable information, or <code>null</code> if the activity does not
+ * exist, or is not searchable.
+ */
+ public SearchableInfo getSearchableInfo(ComponentName componentName) {
+ try {
+ return mService.getSearchableInfo(componentName, false);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "getSearchableInfo() failed: " + ex);
+ return null;
+ }
+ }
+
+ /**
+ * Gets information about a searchable activity.
*
* @param componentName The activity to get searchable information for.
* @param globalSearch If <code>false</code>, return information about the given activity.
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index bf37b62..bbd359b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1102,16 +1102,6 @@
public static final String ACTION_SEARCH_LONG_PRESS = "android.intent.action.SEARCH_LONG_PRESS";
/**
- * Activity Action: Start the global search activity.
- * <p>Input: Nothing.
- * <p>Output: Nothing.
- *
- * @hide Pending API council approval
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ACTION_GLOBAL_SEARCH = "android.intent.action.GLOBAL_SEARCH";
-
- /**
* Activity Action: The user pressed the "Report" button in the crash/ANR dialog.
* This intent is delivered to the package which installed the application, usually
* the Market.
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 3dea286..3e8a55f 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -218,6 +218,22 @@
public static final int FLAG_NEVER_ENCRYPT = 1<<17;
/**
+ * Value for {@link #flags}: Set to true if the application has been
+ * installed using the forward lock option.
+ *
+ * {@hide}
+ */
+ public static final int FLAG_FORWARD_LOCK = 1<<18;
+
+ /**
+ * Value for {@link #flags}: Set to true if the application is
+ * currently installed on the sdcard.
+ *
+ * {@hide}
+ */
+ public static final int FLAG_ON_SDCARD = 1<<19;
+
+ /**
* Flags associated with the application. Any combination of
* {@link #FLAG_SYSTEM}, {@link #FLAG_DEBUGGABLE}, {@link #FLAG_HAS_CODE},
* {@link #FLAG_PERSISTENT}, {@link #FLAG_FACTORY_TEST}, and
@@ -226,7 +242,7 @@
* {@link #FLAG_TEST_ONLY}, {@link #FLAG_SUPPORTS_SMALL_SCREENS},
* {@link #FLAG_SUPPORTS_NORMAL_SCREENS},
* {@link #FLAG_SUPPORTS_LARGE_SCREENS}, {@link #FLAG_RESIZEABLE_FOR_SCREENS},
- * {@link #FLAG_SUPPORTS_SCREEN_DENSITIES}.
+ * {@link #FLAG_SUPPORTS_SCREEN_DENSITIES}
*/
public int flags = 0;
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index fc6538f..54db5e0 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -296,4 +296,13 @@
* in the special development "no pre-dexopt" mode.
*/
boolean performDexOpt(String packageName);
+
+ /**
+ * Update status of external media on the package manager to scan and
+ * install packages installed on the external media. Like say the
+ * MountService uses this to call into the package manager to update
+ * status of sdcard.
+ */
+ void updateExternalMediaStatus(boolean mounted);
+
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 53a966d..bc59c94 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -252,6 +252,13 @@
public static final int INSTALL_ALLOW_TEST = 0x00000004;
/**
+ * Flag parameter for {@link #installPackage} to indicate that this
+ * package has to be installed on the sdcard.
+ * @hide
+ */
+ public static final int INSTALL_ON_SDCARD = 0x00000008;
+
+ /**
* Flag parameter for
* {@link #setComponentEnabledSetting(android.content.ComponentName, int, int)} to indicate
* that you don't want to kill the app containing the component. Be careful when you set this
@@ -411,6 +418,15 @@
*/
public static final int INSTALL_FAILED_MISSING_FEATURE = -17;
+ // ------ Errors related to sdcard
+ /**
+ * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+ * a secure container mount point couldn't be accessed on external media.
+ * @hide
+ */
+ public static final int INSTALL_FAILED_CONTAINER_ERROR = -18;
+
/**
* Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index ad99f54..8a5df32 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -327,16 +327,18 @@
return null;
}
- public final static int PARSE_IS_SYSTEM = 0x0001;
- public final static int PARSE_CHATTY = 0x0002;
- public final static int PARSE_MUST_BE_APK = 0x0004;
- public final static int PARSE_IGNORE_PROCESSES = 0x0008;
+ public final static int PARSE_IS_SYSTEM = 1<<0;
+ public final static int PARSE_CHATTY = 1<<1;
+ public final static int PARSE_MUST_BE_APK = 1<<2;
+ public final static int PARSE_IGNORE_PROCESSES = 1<<3;
+ public final static int PARSE_FORWARD_LOCK = 1<<4;
+ public final static int PARSE_ON_SDCARD = 1<<5;
public int getParseError() {
return mParseError;
}
- public Package parsePackage(File sourceFile, String destFileName,
+ public Package parsePackage(File sourceFile, String destCodePath,
DisplayMetrics metrics, int flags) {
mParseError = PackageManager.INSTALL_SUCCEEDED;
@@ -413,8 +415,11 @@
parser.close();
assmgr.close();
- pkg.applicationInfo.sourceDir = destFileName;
- pkg.applicationInfo.publicSourceDir = destFileName;
+ // Set code and resource paths
+ pkg.mPath = destCodePath;
+ pkg.mScanPath = mArchiveSourcePath;
+ //pkg.applicationInfo.sourceDir = destCodePath;
+ //pkg.applicationInfo.publicSourceDir = destRes;
pkg.mSignatures = null;
return pkg;
@@ -1369,6 +1374,14 @@
}
}
+ if ((flags & PARSE_FORWARD_LOCK) != 0) {
+ ai.flags |= ApplicationInfo.FLAG_FORWARD_LOCK;
+ }
+
+ if ((flags & PARSE_ON_SDCARD) != 0) {
+ ai.flags |= ApplicationInfo.FLAG_ON_SDCARD;
+ }
+
if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestApplication_debuggable,
false)) {
@@ -2530,10 +2543,6 @@
// preferred up order.
public int mPreferredOrder = 0;
- // For use by package manager service to keep track of which apps
- // have been installed with forward locking.
- public boolean mForwardLocked;
-
// For use by the package manager to keep track of the path to the
// file an app came from.
public String mScanPath;
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index f310586..b59030d 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -243,9 +243,12 @@
* (@link setMaxCacheSize(int)}). its default is 0 - i.e., no caching by default because
* most of the apps don't use "?" syntax in their sql, caching is not useful for them.
*/
- private Map<String, SQLiteCompiledSql> mCompiledQueries = Maps.newHashMap();
- private int mMaxSqlCacheSize = 0; // no caching by default
- private static final int MAX_SQL_CACHE_SIZE = 1000;
+ /* package */ Map<String, SQLiteCompiledSql> mCompiledQueries = Maps.newHashMap();
+ /**
+ * @hide
+ */
+ public static final int MAX_SQL_CACHE_SIZE = 250;
+ private int mMaxSqlCacheSize = MAX_SQL_CACHE_SIZE; // max cache size per Database instance
/** maintain stats about number of cache hits and misses */
private int mNumCacheHits;
@@ -828,6 +831,15 @@
}
private void closeClosable() {
+ /* deallocate all compiled sql statement objects from mCompiledQueries cache.
+ * this should be done before de-referencing all {@link SQLiteClosable} objects
+ * from this database object because calling
+ * {@link SQLiteClosable#onAllReferencesReleasedFromContainer()} could cause the database
+ * to be closed. sqlite doesn't let a database close if there are
+ * any unfinalized statements - such as the compiled-sql objects in mCompiledQueries.
+ */
+ deallocCachedSqlStatements();
+
Iterator<Map.Entry<SQLiteClosable, Object>> iter = mPrograms.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<SQLiteClosable, Object> entry = iter.next();
@@ -836,13 +848,6 @@
program.onAllReferencesReleasedFromContainer();
}
}
-
- // finalize all compiled sql statement objects in compiledQueries cache
- synchronized (mCompiledQueries) {
- for (SQLiteCompiledSql compiledStatement : mCompiledQueries.values()) {
- compiledStatement.releaseSqlStatement();
- }
- }
}
/**
@@ -1781,111 +1786,7 @@
return mPath;
}
- /**
- * set the max size of the compiled sql cache for this database after purging the cache.
- * (size of the cache = number of compiled-sql-statements stored in the cache)
- *
- * synchronized because we don't want t threads to change cache size at the same time.
- * @param cacheSize the size of the cache. can be (0 to MAX_SQL_CACHE_SIZE)
- */
- public void setMaxSqlCacheSize(int cacheSize) {
- synchronized(mCompiledQueries) {
- resetCompiledSqlCache();
- mMaxSqlCacheSize = (cacheSize > MAX_SQL_CACHE_SIZE) ? MAX_SQL_CACHE_SIZE
- : (cacheSize < 0) ? 0 : cacheSize;
- }
- }
- /**
- * remove everything from the compiled sql cache
- */
- public void resetCompiledSqlCache() {
- synchronized(mCompiledQueries) {
- mCompiledQueries.clear();
- }
- }
-
- /**
- * adds the given sql and its compiled-statement-id-returned-by-sqlite to the
- * cache of compiledQueries attached to 'this'.
- *
- * if there is already a {@link SQLiteCompiledSql} in compiledQueries for the given sql,
- * the new {@link SQLiteCompiledSql} object is NOT inserted into the cache (i.e.,the current
- * mapping is NOT replaced with the new mapping).
- *
- * @return true if the given obj is added to cache. false otherwise.
- */
- /* package */ boolean addToCompiledQueries(String sql, SQLiteCompiledSql compiledStatement) {
- if (mMaxSqlCacheSize == 0) {
- // for this database, there is no cache of compiled sql.
- if (SQLiteDebug.DEBUG_SQL_CACHE) {
- Log.v(TAG, "|NOT adding_sql_to_cache|" + getPath() + "|" + sql);
- }
- return false;
- }
-
- SQLiteCompiledSql compiledSql = null;
- synchronized(mCompiledQueries) {
- // don't insert the new mapping if a mapping already exists
- compiledSql = mCompiledQueries.get(sql);
- if (compiledSql != null) {
- return false;
- }
- // add this <sql, compiledStatement> to the cache
- if (mCompiledQueries.size() == mMaxSqlCacheSize) {
- /* reached max cachesize. before adding new entry, remove an entry from the
- * cache. we don't want to wipe out the entire cache because of this:
- * GCing {@link SQLiteCompiledSql} requires call to sqlite3_finalize
- * JNI method. If entire cache is wiped out, it could be cause a big GC activity
- * just because a (rogue) process is using the cache incorrectly.
- */
- Set<String> keySet = mCompiledQueries.keySet();
- for (String s : keySet) {
- mCompiledQueries.remove(s);
- break;
- }
- }
- compiledSql = new SQLiteCompiledSql(this, sql);
- mCompiledQueries.put(sql, compiledSql);
- }
- if (SQLiteDebug.DEBUG_SQL_CACHE) {
- Log.v(TAG, "|adding_sql_to_cache|" + getPath() + "|" + mCompiledQueries.size() + "|" +
- sql);
- }
- return true;
- }
-
- /**
- * from the compiledQueries cache, returns the compiled-statement-id for the given sql.
- * returns null, if not found in the cache.
- */
- /* package */ SQLiteCompiledSql getCompiledStatementForSql(String sql) {
- SQLiteCompiledSql compiledStatement = null;
- boolean cacheHit;
- synchronized(mCompiledQueries) {
- if (mMaxSqlCacheSize == 0) {
- // for this database, there is no cache of compiled sql.
- if (SQLiteDebug.DEBUG_SQL_CACHE) {
- Log.v(TAG, "|cache NOT found|" + getPath());
- }
- return null;
- }
- cacheHit = (compiledStatement = mCompiledQueries.get(sql)) != null;
- }
- if (cacheHit) {
- mNumCacheHits++;
- } else {
- mNumCacheMisses++;
- }
-
- if (SQLiteDebug.DEBUG_SQL_CACHE) {
- Log.v(TAG, "|cache_stats|" +
- getPath() + "|" + mCompiledQueries.size() +
- "|" + mNumCacheHits + "|" + mNumCacheMisses +
- "|" + cacheHit + "|" + mTimeOpened + "|" + mTimeClosed + "|" + sql);
- }
- return compiledStatement;
- }
/* package */ void logTimeStat(String sql, long beginNanos) {
// Sample fast queries in proportion to the time taken.
@@ -1934,6 +1835,167 @@
}
}
+ /*
+ * ============================================================================
+ *
+ * The following methods deal with compiled-sql cache
+ * ============================================================================
+ */
+ /**
+ * adds the given sql and its compiled-statement-id-returned-by-sqlite to the
+ * cache of compiledQueries attached to 'this'.
+ *
+ * if there is already a {@link SQLiteCompiledSql} in compiledQueries for the given sql,
+ * the new {@link SQLiteCompiledSql} object is NOT inserted into the cache (i.e.,the current
+ * mapping is NOT replaced with the new mapping).
+ */
+ /* package */ void addToCompiledQueries(String sql, SQLiteCompiledSql compiledStatement) {
+ if (mMaxSqlCacheSize == 0) {
+ // for this database, there is no cache of compiled sql.
+ if (SQLiteDebug.DEBUG_SQL_CACHE) {
+ Log.v(TAG, "|NOT adding_sql_to_cache|" + getPath() + "|" + sql);
+ }
+ return;
+ }
+
+ SQLiteCompiledSql compiledSql = null;
+ synchronized(mCompiledQueries) {
+ // don't insert the new mapping if a mapping already exists
+ compiledSql = mCompiledQueries.get(sql);
+ if (compiledSql != null) {
+ return;
+ }
+ // add this <sql, compiledStatement> to the cache
+ if (mCompiledQueries.size() == mMaxSqlCacheSize) {
+ /* reached max cachesize. before adding new entry, remove an entry from the
+ * cache. we don't want to wipe out the entire cache because of this:
+ * GCing {@link SQLiteCompiledSql} requires call to sqlite3_finalize
+ * JNI method. If entire cache is wiped out, it could cause a big GC activity
+ * just because a (rogue) process is using the cache incorrectly.
+ */
+ Log.wtf(TAG, "Too many sql statements in database cache. Make sure your sql " +
+ "statements are using prepared-sql-statement syntax with '?' for" +
+ "bindargs, instead of using actual values");
+ Set<String> keySet = mCompiledQueries.keySet();
+ for (String s : keySet) {
+ mCompiledQueries.remove(s);
+ break;
+ }
+ }
+ mCompiledQueries.put(sql, compiledStatement);
+ }
+ if (SQLiteDebug.DEBUG_SQL_CACHE) {
+ Log.v(TAG, "|adding_sql_to_cache|" + getPath() + "|" + mCompiledQueries.size() + "|" +
+ sql);
+ }
+ return;
+ }
+
+
+ private void deallocCachedSqlStatements() {
+ synchronized (mCompiledQueries) {
+ for (SQLiteCompiledSql compiledSql : mCompiledQueries.values()) {
+ compiledSql.releaseSqlStatement();
+ }
+ mCompiledQueries.clear();
+ }
+ }
+
+ /**
+ * from the compiledQueries cache, returns the compiled-statement-id for the given sql.
+ * returns null, if not found in the cache.
+ */
+ /* package */ SQLiteCompiledSql getCompiledStatementForSql(String sql) {
+ SQLiteCompiledSql compiledStatement = null;
+ boolean cacheHit;
+ synchronized(mCompiledQueries) {
+ if (mMaxSqlCacheSize == 0) {
+ // for this database, there is no cache of compiled sql.
+ if (SQLiteDebug.DEBUG_SQL_CACHE) {
+ Log.v(TAG, "|cache NOT found|" + getPath());
+ }
+ return null;
+ }
+ cacheHit = (compiledStatement = mCompiledQueries.get(sql)) != null;
+ }
+ if (cacheHit) {
+ mNumCacheHits++;
+ } else {
+ mNumCacheMisses++;
+ }
+
+ if (SQLiteDebug.DEBUG_SQL_CACHE) {
+ Log.v(TAG, "|cache_stats|" +
+ getPath() + "|" + mCompiledQueries.size() +
+ "|" + mNumCacheHits + "|" + mNumCacheMisses +
+ "|" + cacheHit + "|" + mTimeOpened + "|" + mTimeClosed + "|" + sql);
+ }
+ return compiledStatement;
+ }
+
+ /**
+ * returns true if the given sql is cached in compiled-sql cache.
+ * @hide
+ */
+ public boolean isInCompiledSqlCache(String sql) {
+ synchronized(mCompiledQueries) {
+ return mCompiledQueries.containsKey(sql);
+ }
+ }
+
+ /**
+ * purges the given sql from the compiled-sql cache.
+ * @hide
+ */
+ public void purgeFromCompiledSqlCache(String sql) {
+ synchronized(mCompiledQueries) {
+ mCompiledQueries.remove(sql);
+ }
+ }
+
+ /**
+ * remove everything from the compiled sql cache
+ * @hide
+ */
+ public void resetCompiledSqlCache() {
+ synchronized(mCompiledQueries) {
+ mCompiledQueries.clear();
+ }
+ }
+
+ /**
+ * return the current maxCacheSqlCacheSize
+ * @hide
+ */
+ public synchronized int getMaxSqlCacheSize() {
+ return mMaxSqlCacheSize;
+ }
+
+ /**
+ * set the max size of the compiled sql cache for this database after purging the cache.
+ * (size of the cache = number of compiled-sql-statements stored in the cache).
+ *
+ * max cache size can ONLY be increased from its current size (default = 0).
+ * if this method is called with smaller size than the current value of mMaxSqlCacheSize,
+ * then IllegalStateException is thrown
+ *
+ * synchronized because we don't want t threads to change cache size at the same time.
+ * @param cacheSize the size of the cache. can be (0 to MAX_SQL_CACHE_SIZE)
+ * @throws IllegalStateException if input cacheSize > MAX_SQL_CACHE_SIZE or < 0 or
+ * < the value set with previous setMaxSqlCacheSize() call.
+ *
+ * @hide
+ */
+ public synchronized void setMaxSqlCacheSize(int cacheSize) {
+ if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) {
+ throw new IllegalStateException("expected value between 0 and " + MAX_SQL_CACHE_SIZE);
+ } else if (cacheSize < mMaxSqlCacheSize) {
+ throw new IllegalStateException("cannot set cacheSize to a value less than the value " +
+ "set with previous setMaxSqlCacheSize() call.");
+ }
+ mMaxSqlCacheSize = cacheSize;
+ }
+
/**
* Native call to open the database.
*
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index edc15cb..00b0a86 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -37,15 +37,13 @@
protected int nHandle = 0;
/**
- * the compiledSql object for the given sql statement.
+ * the SQLiteCompiledSql object for the given sql statement.
*/
- private SQLiteCompiledSql compiledSql;
- private boolean myCompiledSqlIsInCache;
+ private SQLiteCompiledSql mCompiledSql;
/**
- * compiledSql statement id is populated with the corresponding object from the above
- * member compiledSql.
- * this member is used by the native_bind_* methods
+ * SQLiteCompiledSql statement id is populated with the corresponding object from the above
+ * member. This member is used by the native_bind_* methods
*/
protected int nStatement = 0;
@@ -60,47 +58,50 @@
db.addSQLiteClosable(this);
this.nHandle = db.mNativeHandle;
- compiledSql = db.getCompiledStatementForSql(sql);
- if (compiledSql == null) {
+ mCompiledSql = db.getCompiledStatementForSql(sql);
+ if (mCompiledSql == null) {
// create a new compiled-sql obj
- compiledSql = new SQLiteCompiledSql(db, sql);
+ mCompiledSql = new SQLiteCompiledSql(db, sql);
// add it to the cache of compiled-sqls
- myCompiledSqlIsInCache = db.addToCompiledQueries(sql, compiledSql);
- } else {
- myCompiledSqlIsInCache = true;
+ db.addToCompiledQueries(sql, mCompiledSql);
}
- nStatement = compiledSql.nStatement;
+ nStatement = mCompiledSql.nStatement;
}
@Override
protected void onAllReferencesReleased() {
- // release the compiled sql statement used by me if it is NOT in cache
- if (!myCompiledSqlIsInCache && compiledSql != null) {
- compiledSql.releaseSqlStatement();
- compiledSql = null; // so that GC doesn't call finalize() on it
- }
+ releaseCompiledSqlIfInCache();
mDatabase.releaseReference();
mDatabase.removeSQLiteClosable(this);
}
@Override
protected void onAllReferencesReleasedFromContainer() {
- // release the compiled sql statement used by me if it is NOT in cache
- if (!myCompiledSqlIsInCache && compiledSql != null) {
- compiledSql.releaseSqlStatement();
- compiledSql = null; // so that GC doesn't call finalize() on it
- }
+ releaseCompiledSqlIfInCache();
mDatabase.releaseReference();
}
+ private void releaseCompiledSqlIfInCache() {
+ if (mCompiledSql == null) {
+ return;
+ }
+ synchronized(mDatabase.mCompiledQueries) {
+ if (!mDatabase.mCompiledQueries.containsValue(mCompiledSql)) {
+ mCompiledSql.releaseSqlStatement();
+ mCompiledSql = null; // so that GC doesn't call finalize() on it
+ nStatement = 0;
+ }
+ }
+ }
+
/**
* Returns a unique identifier for this program.
*
* @return a unique identifier for this program
*/
public final int getUniqueId() {
- return compiledSql.nStatement;
+ return nStatement;
}
/* package */ String getSqlString() {
diff --git a/core/java/android/gesture/GestureUtilities.java b/core/java/android/gesture/GestureUtilities.java
index f1dcd89..d6d4899 100755
--- a/core/java/android/gesture/GestureUtilities.java
+++ b/core/java/android/gesture/GestureUtilities.java
@@ -27,7 +27,6 @@
import static android.gesture.GestureConstants.*;
final class GestureUtilities {
- private static final int TEMPORAL_SAMPLING_RATE = 16;
private GestureUtilities() {
}
@@ -51,29 +50,29 @@
final float targetPatchSize = sampleMatrixDimension - 1; // edge inclusive
float[] sample = new float[sampleMatrixDimension * sampleMatrixDimension];
Arrays.fill(sample, 0);
-
+
RectF rect = gesture.getBoundingBox();
float sx = targetPatchSize / rect.width();
float sy = targetPatchSize / rect.height();
float scale = sx < sy ? sx : sy;
-
+
float preDx = -rect.centerX();
float preDy = -rect.centerY();
float postDx = targetPatchSize / 2;
float postDy = targetPatchSize / 2;
-
+
final ArrayList<GestureStroke> strokes = gesture.getStrokes();
final int count = strokes.size();
-
+
int size;
float xpos;
float ypos;
-
+
for (int index = 0; index < count; index++) {
final GestureStroke stroke = strokes.get(index);
float[] strokepoints = stroke.points;
size = strokepoints.length;
-
+
final float[] pts = new float[size];
for (int i = 0; i < size; i += 2) {
@@ -118,7 +117,7 @@
xpos++;
}
}
-
+
// evaluating vertically
if (segmentEndY > segmentStartY) {
ypos = (float) Math.ceil(segmentStartY);
@@ -143,11 +142,11 @@
segmentEndY = segmentStartY;
}
}
-
-
+
+
return sample;
}
-
+
private static void plot(float x, float y, float[] sample, int sampleSize) {
x = x < 0 ? 0 : x;
y = y < 0 ? 0 : y;
@@ -286,8 +285,8 @@
* @param points
* @return the covariance matrix
*/
- private static double[][] computeCoVariance(float[] points) {
- double[][] array = new double[2][2];
+ private static float[][] computeCoVariance(float[] points) {
+ float[][] array = new float[2][2];
array[0][0] = 0;
array[0][1] = 0;
array[1][0] = 0;
@@ -321,17 +320,17 @@
return sum;
}
- static double computeStraightness(float[] points) {
+ static float computeStraightness(float[] points) {
float totalLen = computeTotalLength(points);
float dx = points[2] - points[0];
float dy = points[3] - points[1];
- return Math.sqrt(dx * dx + dy * dy) / totalLen;
+ return (float) Math.sqrt(dx * dx + dy * dy) / totalLen;
}
- static double computeStraightness(float[] points, float totalLen) {
+ static float computeStraightness(float[] points, float totalLen) {
float dx = points[2] - points[0];
float dy = points[3] - points[1];
- return Math.sqrt(dx * dx + dy * dy) / totalLen;
+ return (float) Math.sqrt(dx * dx + dy * dy) / totalLen;
}
/**
@@ -341,8 +340,8 @@
* @param vector2
* @return the distance
*/
- static double squaredEuclideanDistance(float[] vector1, float[] vector2) {
- double squaredDistance = 0;
+ static float squaredEuclideanDistance(float[] vector1, float[] vector2) {
+ float squaredDistance = 0;
int size = vector1.length;
for (int i = 0; i < size; i++) {
float difference = vector1[i] - vector2[i];
@@ -358,13 +357,13 @@
* @param vector2
* @return the distance between 0 and Math.PI
*/
- static double cosineDistance(float[] vector1, float[] vector2) {
+ static float cosineDistance(float[] vector1, float[] vector2) {
float sum = 0;
int len = vector1.length;
for (int i = 0; i < len; i++) {
sum += vector1[i] * vector2[i];
}
- return Math.acos(sum);
+ return (float) Math.acos(sum);
}
/**
@@ -375,46 +374,58 @@
* @param numOrientations the maximum number of orientation allowed
* @return the distance between the two instances (between 0 and Math.PI)
*/
- static double minimumCosineDistance(float[] vector1, float[] vector2, int numOrientations) {
+ static float minimumCosineDistance(float[] vector1, float[] vector2, int numOrientations) {
final int len = vector1.length;
- double a = 0;
- double b = 0;
+ float a = 0;
+ float b = 0;
for (int i = 0; i < len; i += 2) {
a += vector1[i] * vector2[i] + vector1[i + 1] * vector2[i + 1];
b += vector1[i] * vector2[i + 1] - vector1[i + 1] * vector2[i];
}
if (a != 0) {
- final double tan = b/a;
+ final float tan = b/a;
final double angle = Math.atan(tan);
if (numOrientations > 2 && Math.abs(angle) >= Math.PI / numOrientations) {
- return Math.acos(a);
+ return (float) Math.acos(a);
} else {
final double cosine = Math.cos(angle);
final double sine = cosine * tan;
- return Math.acos(a * cosine + b * sine);
+ return (float) Math.acos(a * cosine + b * sine);
}
} else {
- return Math.PI / 2;
+ return (float) Math.PI / 2;
}
}
- static OrientedBoundingBox computeOrientedBoundingBox(ArrayList<GesturePoint> pts) {
- GestureStroke stroke = new GestureStroke(pts);
- float[] points = temporalSampling(stroke, TEMPORAL_SAMPLING_RATE);
- return computeOrientedBoundingBox(points);
- }
-
- static OrientedBoundingBox computeOrientedBoundingBox(float[] points) {
+ static OrientedBoundingBox computeOrientedBoundingBox(ArrayList<GesturePoint> originalPoints) {
+ final int count = originalPoints.size();
+ float[] points = new float[count * 2];
+ for (int i = 0; i < count; i++) {
+ GesturePoint point = originalPoints.get(i);
+ int index = i * 2;
+ points[index] = point.x;
+ points[index + 1] = point.y;
+ }
float[] meanVector = computeCentroid(points);
return computeOrientedBoundingBox(points, meanVector);
}
- static OrientedBoundingBox computeOrientedBoundingBox(float[] points, float[] centroid) {
+ static OrientedBoundingBox computeOrientedBoundingBox(float[] originalPoints) {
+ int size = originalPoints.length;
+ float[] points = new float[size];
+ for (int i = 0; i < size; i++) {
+ points[i] = originalPoints[i];
+ }
+ float[] meanVector = computeCentroid(points);
+ return computeOrientedBoundingBox(points, meanVector);
+ }
+
+ private static OrientedBoundingBox computeOrientedBoundingBox(float[] points, float[] centroid) {
translate(points, -centroid[0], -centroid[1]);
- double[][] array = computeCoVariance(points);
- double[] targetVector = computeOrientation(array);
+ float[][] array = computeCoVariance(points);
+ float[] targetVector = computeOrientation(array);
float angle;
if (targetVector[0] == 0 && targetVector[1] == 0) {
@@ -448,25 +459,25 @@
return new OrientedBoundingBox((float) (angle * 180 / Math.PI), centroid[0], centroid[1], maxx - minx, maxy - miny);
}
- private static double[] computeOrientation(double[][] covarianceMatrix) {
- double[] targetVector = new double[2];
+ private static float[] computeOrientation(float[][] covarianceMatrix) {
+ float[] targetVector = new float[2];
if (covarianceMatrix[0][1] == 0 || covarianceMatrix[1][0] == 0) {
targetVector[0] = 1;
targetVector[1] = 0;
}
- double a = -covarianceMatrix[0][0] - covarianceMatrix[1][1];
- double b = covarianceMatrix[0][0] * covarianceMatrix[1][1] - covarianceMatrix[0][1]
+ float a = -covarianceMatrix[0][0] - covarianceMatrix[1][1];
+ float b = covarianceMatrix[0][0] * covarianceMatrix[1][1] - covarianceMatrix[0][1]
* covarianceMatrix[1][0];
- double value = a / 2;
- double rightside = Math.sqrt(Math.pow(value, 2) - b);
- double lambda1 = -value + rightside;
- double lambda2 = -value - rightside;
+ float value = a / 2;
+ float rightside = (float) Math.sqrt(Math.pow(value, 2) - b);
+ float lambda1 = -value + rightside;
+ float lambda2 = -value - rightside;
if (lambda1 == lambda2) {
targetVector[0] = 0;
targetVector[1] = 0;
} else {
- double lambda = lambda1 > lambda2 ? lambda1 : lambda2;
+ float lambda = lambda1 > lambda2 ? lambda1 : lambda2;
targetVector[0] = 1;
targetVector[1] = (lambda - covarianceMatrix[0][0]) / covarianceMatrix[0][1];
}
@@ -474,13 +485,13 @@
}
- static float[] rotate(float[] points, double angle) {
- double cos = Math.cos(angle);
- double sin = Math.sin(angle);
+ static float[] rotate(float[] points, float angle) {
+ float cos = (float) Math.cos(angle);
+ float sin = (float) Math.sin(angle);
int size = points.length;
for (int i = 0; i < size; i += 2) {
- float x = (float) (points[i] * cos - points[i + 1] * sin);
- float y = (float) (points[i] * sin + points[i + 1] * cos);
+ float x = points[i] * cos - points[i + 1] * sin;
+ float y = points[i] * sin + points[i + 1] * cos;
points[i] = x;
points[i + 1] = y;
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index b315932..5d5bd9c 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -841,7 +841,14 @@
*/
public boolean onEvaluateFullscreenMode() {
Configuration config = getResources().getConfiguration();
- return config.orientation == Configuration.ORIENTATION_LANDSCAPE;
+ if (config.orientation != Configuration.ORIENTATION_LANDSCAPE) {
+ return false;
+ }
+ if (mInputEditorInfo != null
+ && (mInputEditorInfo.imeOptions & EditorInfo.IME_FLAG_NO_FULLSCREEN) != 0) {
+ return false;
+ }
+ return true;
}
/**
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index a127df0..30799ec 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -129,37 +129,33 @@
public static final int TYPE_WIFI = 1;
/**
* An MMS-specific Mobile data connection. This connection may be the
- * same as {@link #TYPEMOBILE} but it may be different. This is used
+ * same as {@link #TYPE_MOBILE} but it may be different. This is used
* by applications needing to talk to the carrier's Multimedia Messaging
* Service servers. It may coexist with default data connections.
- * {@hide}
*/
public static final int TYPE_MOBILE_MMS = 2;
/**
* A SUPL-specific Mobile data connection. This connection may be the
- * same as {@link #TYPEMOBILE} but it may be different. This is used
+ * same as {@link #TYPE_MOBILE} but it may be different. This is used
* by applications needing to talk to the carrier's Secure User Plane
* Location servers for help locating the device. It may coexist with
* default data connections.
- * {@hide}
*/
public static final int TYPE_MOBILE_SUPL = 3;
/**
* A DUN-specific Mobile data connection. This connection may be the
- * same as {@link #TYPEMOBILE} but it may be different. This is used
+ * same as {@link #TYPE_MOBILE} but it may be different. This is used
* by applicaitons performing a Dial Up Networking bridge so that
* the carrier is aware of DUN traffic. It may coexist with default data
* connections.
- * {@hide}
*/
public static final int TYPE_MOBILE_DUN = 4;
/**
* A High Priority Mobile data connection. This connection is typically
- * the same as {@link #TYPEMOBILE} but the routing setup is different.
+ * the same as {@link #TYPE_MOBILE} but the routing setup is different.
* Only requesting processes will have access to the Mobile DNS servers
* and only IP's explicitly requested via {@link #requestRouteToHost}
- * will route over this interface.
- *{@hide}
+ * will route over this interface if a default route exists.
*/
public static final int TYPE_MOBILE_HIPRI = 5;
/** {@hide} */
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index a97b9e5..e40f1b8 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -16,11 +16,12 @@
package android.net;
-import android.net.http.DomainNameChecker;
import android.os.SystemProperties;
import android.util.Config;
import android.util.Log;
+import com.android.common.DomainNameValidator;
+
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
@@ -200,7 +201,7 @@
X509Certificate lastChainCert = (X509Certificate) certs[0];
- if (!DomainNameChecker.match(lastChainCert, destHost)) {
+ if (!DomainNameValidator.match(lastChainCert, destHost)) {
if (Config.LOGD) {
Log.d(LOG_TAG,"validateSocket(): domain name check failed");
}
diff --git a/core/java/android/net/http/CertificateChainValidator.java b/core/java/android/net/http/CertificateChainValidator.java
index ed6b4c2..da6af9d 100644
--- a/core/java/android/net/http/CertificateChainValidator.java
+++ b/core/java/android/net/http/CertificateChainValidator.java
@@ -16,6 +16,8 @@
package android.net.http;
+import com.android.common.DomainNameValidator;
+
import org.apache.harmony.xnet.provider.jsse.SSLParameters;
import java.io.IOException;
@@ -112,7 +114,7 @@
closeSocketThrowException(
sslSocket, "certificate for this site is null");
} else {
- if (!DomainNameChecker.match(currCertificate, domain)) {
+ if (!DomainNameValidator.match(currCertificate, domain)) {
String errorMessage = "certificate not for this host: " + domain;
if (HttpLog.LOGV) {
diff --git a/core/java/android/os/IMountService.aidl b/core/java/android/os/IMountService.aidl
index f052689..e73569a 100644
--- a/core/java/android/os/IMountService.aidl
+++ b/core/java/android/os/IMountService.aidl
@@ -42,17 +42,17 @@
/**
* Mount external storage at given mount point.
*/
- void mountMedia(String mountPoint);
+ void mountVolume(String mountPoint);
/**
* Safely unmount external storage at given mount point.
*/
- void unmountMedia(String mountPoint);
+ void unmountVolume(String mountPoint);
/**
* Format external storage given a mount point.
*/
- void formatMedia(String mountPoint);
+ void formatVolume(String mountPoint);
/**
* Returns true if media notification sounds are enabled.
@@ -104,8 +104,7 @@
String[] getSecureContainerList();
/**
- * Shuts down the MountService and gracefully unmounts
- * all external media.
+ * Shuts down the MountService and gracefully unmounts all external media.
*/
void shutdown();
}
diff --git a/core/java/android/os/Power.java b/core/java/android/os/Power.java
index 3679e47..bc76180 100644
--- a/core/java/android/os/Power.java
+++ b/core/java/android/os/Power.java
@@ -17,6 +17,8 @@
package android.os;
import java.io.IOException;
+import android.os.ServiceManager;
+import android.os.IMountService;
/**
* Class that provides access to some of the power management functions.
@@ -97,5 +99,19 @@
* @throws IOException if reboot fails for some reason (eg, lack of
* permission)
*/
- public static native void reboot(String reason) throws IOException;
+ public static void reboot(String reason) throws IOException
+ {
+ IMountService mSvc = IMountService.Stub.asInterface(
+ ServiceManager.getService("mount"));
+
+ if (mSvc != null) {
+ try {
+ mSvc.shutdown();
+ } catch (Exception e) {
+ }
+ }
+ rebootNative(reason);
+ }
+
+ private static native void rebootNative(String reason) throws IOException ;
}
diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java
index e7c19cf..c2928cb 100644
--- a/core/java/android/pim/vcard/VCardParser_V21.java
+++ b/core/java/android/pim/vcard/VCardParser_V21.java
@@ -110,11 +110,11 @@
private long mTimeHandleBase64;
public VCardParser_V21() {
- this(VCardConfig.PARSE_TYPE_UNKNOWN);
+ this(null);
}
public VCardParser_V21(VCardSourceDetector detector) {
- this(detector.getEstimatedType());
+ this(detector != null ? detector.getEstimatedType() : VCardConfig.PARSE_TYPE_UNKNOWN);
}
public VCardParser_V21(int parseType) {
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index 7f947e9..509aac5 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -583,14 +583,14 @@
* {@link #ACCOUNT_TYPE} identifies a specific account.
* <P>Type: TEXT</P>
*/
- public static final String ACCOUNT_NAME = "account_name";
+ public static final String ACCOUNT_NAME = "_sync_account";
/**
* The type of account to which this row belongs, which when paired with
* {@link #ACCOUNT_NAME} identifies a specific account.
* <P>Type: TEXT</P>
*/
- public static final String ACCOUNT_TYPE = "account_type";
+ public static final String ACCOUNT_TYPE = "_sync_account_type";
public static EntityIterator newEntityIterator(Cursor cursor, ContentResolver resolver) {
return new EntityIteratorImpl(cursor, resolver);
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index 790fe5c..a93cee7 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -510,4 +510,493 @@
* This download doesn't show in the UI or in the notifications.
*/
public static final int VISIBILITY_HIDDEN = 2;
+
+ /**
+ * Implementation details
+ */
+ public static final class Impl implements BaseColumns {
+ private Impl() {}
+
+ /**
+ * The permission to access the download manager
+ */
+ public static final String PERMISSION_ACCESS = "android.permission.ACCESS_DOWNLOAD_MANAGER";
+
+ /**
+ * The permission to access the download manager's advanced functions
+ */
+ public static final String PERMISSION_ACCESS_ADVANCED =
+ "android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED";
+
+ /**
+ * The permission to directly access the download manager's cache directory
+ */
+ public static final String PERMISSION_CACHE = "android.permission.ACCESS_CACHE_FILESYSTEM";
+
+ /**
+ * The permission to send broadcasts on download completion
+ */
+ public static final String PERMISSION_SEND_INTENTS =
+ "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS";
+
+ /**
+ * The content:// URI for the data table in the provider
+ */
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://downloads/download");
+
+ /**
+ * Broadcast Action: this is sent by the download manager to the app
+ * that had initiated a download when that download completes. The
+ * download's content: uri is specified in the intent's data.
+ */
+ public static final String ACTION_DOWNLOAD_COMPLETED =
+ "android.intent.action.DOWNLOAD_COMPLETED";
+
+ /**
+ * Broadcast Action: this is sent by the download manager to the app
+ * that had initiated a download when the user selects the notification
+ * associated with that download. The download's content: uri is specified
+ * in the intent's data if the click is associated with a single download,
+ * or Downloads.CONTENT_URI if the notification is associated with
+ * multiple downloads.
+ * Note: this is not currently sent for downloads that have completed
+ * successfully.
+ */
+ public static final String ACTION_NOTIFICATION_CLICKED =
+ "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
+
+ /**
+ * The name of the column containing the URI of the data being downloaded.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init/Read</P>
+ */
+ public static final String COLUMN_URI = "uri";
+
+ /**
+ * The name of the column containing application-specific data.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init/Read/Write</P>
+ */
+ public static final String COLUMN_APP_DATA = "entity";
+
+ /**
+ * The name of the column containing the flags that indicates whether
+ * the initiating application is capable of verifying the integrity of
+ * the downloaded file. When this flag is set, the download manager
+ * performs downloads and reports success even in some situations where
+ * it can't guarantee that the download has completed (e.g. when doing
+ * a byte-range request without an ETag, or when it can't determine
+ * whether a download fully completed).
+ * <P>Type: BOOLEAN</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_NO_INTEGRITY = "no_integrity";
+
+ /**
+ * The name of the column containing the filename that the initiating
+ * application recommends. When possible, the download manager will attempt
+ * to use this filename, or a variation, as the actual name for the file.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_FILE_NAME_HINT = "hint";
+
+ /**
+ * The name of the column containing the filename where the downloaded data
+ * was actually stored.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Read</P>
+ */
+ public static final String _DATA = "_data";
+
+ /**
+ * The name of the column containing the MIME type of the downloaded data.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init/Read</P>
+ */
+ public static final String COLUMN_MIME_TYPE = "mimetype";
+
+ /**
+ * The name of the column containing the flag that controls the destination
+ * of the download. See the DESTINATION_* constants for a list of legal values.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_DESTINATION = "destination";
+
+ /**
+ * The name of the column containing the flags that controls whether the
+ * download is displayed by the UI. See the VISIBILITY_* constants for
+ * a list of legal values.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Init/Read/Write</P>
+ */
+ public static final String COLUMN_VISIBILITY = "visibility";
+
+ /**
+ * The name of the column containing the current control state of the download.
+ * Applications can write to this to control (pause/resume) the download.
+ * the CONTROL_* constants for a list of legal values.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Read</P>
+ */
+ public static final String COLUMN_CONTROL = "control";
+
+ /**
+ * The name of the column containing the current status of the download.
+ * Applications can read this to follow the progress of each download. See
+ * the STATUS_* constants for a list of legal values.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Read</P>
+ */
+ public static final String COLUMN_STATUS = "status";
+
+ /**
+ * The name of the column containing the date at which some interesting
+ * status changed in the download. Stored as a System.currentTimeMillis()
+ * value.
+ * <P>Type: BIGINT</P>
+ * <P>Owner can Read</P>
+ */
+ public static final String COLUMN_LAST_MODIFICATION = "lastmod";
+
+ /**
+ * The name of the column containing the package name of the application
+ * that initiating the download. The download manager will send
+ * notifications to a component in this package when the download completes.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init/Read</P>
+ */
+ public static final String COLUMN_NOTIFICATION_PACKAGE = "notificationpackage";
+
+ /**
+ * The name of the column containing the component name of the class that
+ * will receive notifications associated with the download. The
+ * package/class combination is passed to
+ * Intent.setClassName(String,String).
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init/Read</P>
+ */
+ public static final String COLUMN_NOTIFICATION_CLASS = "notificationclass";
+
+ /**
+ * If extras are specified when requesting a download they will be provided in the intent that
+ * is sent to the specified class and package when a download has finished.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_NOTIFICATION_EXTRAS = "notificationextras";
+
+ /**
+ * The name of the column contain the values of the cookie to be used for
+ * the download. This is used directly as the value for the Cookie: HTTP
+ * header that gets sent with the request.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_COOKIE_DATA = "cookiedata";
+
+ /**
+ * The name of the column containing the user agent that the initiating
+ * application wants the download manager to use for this download.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_USER_AGENT = "useragent";
+
+ /**
+ * The name of the column containing the referer (sic) that the initiating
+ * application wants the download manager to use for this download.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_REFERER = "referer";
+
+ /**
+ * The name of the column containing the total size of the file being
+ * downloaded.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Read</P>
+ */
+ public static final String COLUMN_TOTAL_BYTES = "total_bytes";
+
+ /**
+ * The name of the column containing the size of the part of the file that
+ * has been downloaded so far.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Read</P>
+ */
+ public static final String COLUMN_CURRENT_BYTES = "current_bytes";
+
+ /**
+ * The name of the column where the initiating application can provide the
+ * UID of another application that is allowed to access this download. If
+ * multiple applications share the same UID, all those applications will be
+ * allowed to access this download. This column can be updated after the
+ * download is initiated. This requires the permission
+ * android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_OTHER_UID = "otheruid";
+
+ /**
+ * The name of the column where the initiating application can provided the
+ * title of this download. The title will be displayed ito the user in the
+ * list of downloads.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init/Read/Write</P>
+ */
+ public static final String COLUMN_TITLE = "title";
+
+ /**
+ * The name of the column where the initiating application can provide the
+ * description of this download. The description will be displayed to the
+ * user in the list of downloads.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init/Read/Write</P>
+ */
+ public static final String COLUMN_DESCRIPTION = "description";
+
+ /*
+ * Lists the destinations that an application can specify for a download.
+ */
+
+ /**
+ * This download will be saved to the external storage. This is the
+ * default behavior, and should be used for any file that the user
+ * can freely access, copy, delete. Even with that destination,
+ * unencrypted DRM files are saved in secure internal storage.
+ * Downloads to the external destination only write files for which
+ * there is a registered handler. The resulting files are accessible
+ * by filename to all applications.
+ */
+ public static final int DESTINATION_EXTERNAL = 0;
+
+ /**
+ * This download will be saved to the download manager's private
+ * partition. This is the behavior used by applications that want to
+ * download private files that are used and deleted soon after they
+ * get downloaded. All file types are allowed, and only the initiating
+ * application can access the file (indirectly through a content
+ * provider). This requires the
+ * android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED permission.
+ */
+ public static final int DESTINATION_CACHE_PARTITION = 1;
+
+ /**
+ * This download will be saved to the download manager's private
+ * partition and will be purged as necessary to make space. This is
+ * for private files (similar to CACHE_PARTITION) that aren't deleted
+ * immediately after they are used, and are kept around by the download
+ * manager as long as space is available.
+ */
+ public static final int DESTINATION_CACHE_PARTITION_PURGEABLE = 2;
+
+ /**
+ * This download will be saved to the download manager's private
+ * partition, as with DESTINATION_CACHE_PARTITION, but the download
+ * will not proceed if the user is on a roaming data connection.
+ */
+ public static final int DESTINATION_CACHE_PARTITION_NOROAMING = 3;
+
+ /**
+ * This download is allowed to run.
+ */
+ public static final int CONTROL_RUN = 0;
+
+ /**
+ * This download must pause at the first opportunity.
+ */
+ public static final int CONTROL_PAUSED = 1;
+
+ /*
+ * Lists the states that the download manager can set on a download
+ * to notify applications of the download progress.
+ * The codes follow the HTTP families:<br>
+ * 1xx: informational<br>
+ * 2xx: success<br>
+ * 3xx: redirects (not used by the download manager)<br>
+ * 4xx: client errors<br>
+ * 5xx: server errors
+ */
+
+ /**
+ * Returns whether the status is informational (i.e. 1xx).
+ */
+ public static boolean isStatusInformational(int status) {
+ return (status >= 100 && status < 200);
+ }
+
+ /**
+ * Returns whether the download is suspended. (i.e. whether the download
+ * won't complete without some action from outside the download
+ * manager).
+ */
+ public static boolean isStatusSuspended(int status) {
+ return (status == STATUS_PENDING_PAUSED || status == STATUS_RUNNING_PAUSED);
+ }
+
+ /**
+ * Returns whether the status is a success (i.e. 2xx).
+ */
+ public static boolean isStatusSuccess(int status) {
+ return (status >= 200 && status < 300);
+ }
+
+ /**
+ * Returns whether the status is an error (i.e. 4xx or 5xx).
+ */
+ public static boolean isStatusError(int status) {
+ return (status >= 400 && status < 600);
+ }
+
+ /**
+ * Returns whether the status is a client error (i.e. 4xx).
+ */
+ public static boolean isStatusClientError(int status) {
+ return (status >= 400 && status < 500);
+ }
+
+ /**
+ * Returns whether the status is a server error (i.e. 5xx).
+ */
+ public static boolean isStatusServerError(int status) {
+ return (status >= 500 && status < 600);
+ }
+
+ /**
+ * Returns whether the download has completed (either with success or
+ * error).
+ */
+ public static boolean isStatusCompleted(int status) {
+ return (status >= 200 && status < 300) || (status >= 400 && status < 600);
+ }
+
+ /**
+ * This download hasn't stated yet
+ */
+ public static final int STATUS_PENDING = 190;
+
+ /**
+ * This download hasn't stated yet and is paused
+ */
+ public static final int STATUS_PENDING_PAUSED = 191;
+
+ /**
+ * This download has started
+ */
+ public static final int STATUS_RUNNING = 192;
+
+ /**
+ * This download has started and is paused
+ */
+ public static final int STATUS_RUNNING_PAUSED = 193;
+
+ /**
+ * This download has successfully completed.
+ * Warning: there might be other status values that indicate success
+ * in the future.
+ * Use isSucccess() to capture the entire category.
+ */
+ public static final int STATUS_SUCCESS = 200;
+
+ /**
+ * This request couldn't be parsed. This is also used when processing
+ * requests with unknown/unsupported URI schemes.
+ */
+ public static final int STATUS_BAD_REQUEST = 400;
+
+ /**
+ * This download can't be performed because the content type cannot be
+ * handled.
+ */
+ public static final int STATUS_NOT_ACCEPTABLE = 406;
+
+ /**
+ * This download cannot be performed because the length cannot be
+ * determined accurately. This is the code for the HTTP error "Length
+ * Required", which is typically used when making requests that require
+ * a content length but don't have one, and it is also used in the
+ * client when a response is received whose length cannot be determined
+ * accurately (therefore making it impossible to know when a download
+ * completes).
+ */
+ public static final int STATUS_LENGTH_REQUIRED = 411;
+
+ /**
+ * This download was interrupted and cannot be resumed.
+ * This is the code for the HTTP error "Precondition Failed", and it is
+ * also used in situations where the client doesn't have an ETag at all.
+ */
+ public static final int STATUS_PRECONDITION_FAILED = 412;
+
+ /**
+ * This download was canceled
+ */
+ public static final int STATUS_CANCELED = 490;
+
+ /**
+ * This download has completed with an error.
+ * Warning: there will be other status values that indicate errors in
+ * the future. Use isStatusError() to capture the entire category.
+ */
+ public static final int STATUS_UNKNOWN_ERROR = 491;
+
+ /**
+ * This download couldn't be completed because of a storage issue.
+ * Typically, that's because the filesystem is missing or full.
+ */
+ public static final int STATUS_FILE_ERROR = 492;
+
+ /**
+ * This download couldn't be completed because of an HTTP
+ * redirect response that the download manager couldn't
+ * handle.
+ */
+ public static final int STATUS_UNHANDLED_REDIRECT = 493;
+
+ /**
+ * This download couldn't be completed because of an
+ * unspecified unhandled HTTP code.
+ */
+ public static final int STATUS_UNHANDLED_HTTP_CODE = 494;
+
+ /**
+ * This download couldn't be completed because of an
+ * error receiving or processing data at the HTTP level.
+ */
+ public static final int STATUS_HTTP_DATA_ERROR = 495;
+
+ /**
+ * This download couldn't be completed because of an
+ * HttpException while setting up the request.
+ */
+ public static final int STATUS_HTTP_EXCEPTION = 496;
+
+ /**
+ * This download couldn't be completed because there were
+ * too many redirects.
+ */
+ public static final int STATUS_TOO_MANY_REDIRECTS = 497;
+
+ /**
+ * This download is visible but only shows in the notifications
+ * while it's in progress.
+ */
+ public static final int VISIBILITY_VISIBLE = 0;
+
+ /**
+ * This download is visible and shows in the notifications while
+ * in progress and after completion.
+ */
+ public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1;
+
+ /**
+ * This download doesn't show in the UI or in the notifications.
+ */
+ public static final int VISIBILITY_HIDDEN = 2;
+
+ }
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7db9fdc..c0aee4c 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2094,13 +2094,6 @@
public static final String LOGGING_ID = "logging_id";
/**
- * The Logging ID (a unique 64-bit value) as a hex string.
- * Used as a pseudonymous identifier for logging.
- * @hide
- */
- public static final String LOGGING_ID2 = "logging_id2";
-
- /**
* User preference for which network(s) should be used. Only the
* connectivity service should touch this.
*/
@@ -2318,32 +2311,6 @@
public static final String BACKGROUND_DATA = "background_data";
/**
- * The time in msec, when the LAST_KMSG file was send to the checkin server.
- * We will only send the LAST_KMSG file if it was modified after this time.
- *
- * @hide
- */
- public static final String CHECKIN_SEND_LAST_KMSG_TIME = "checkin_kmsg_time";
-
- /**
- * The time in msec, when the apanic_console file was send to the checkin server.
- * We will only send the apanic_console file if it was modified after this time.
- *
- * @hide
- */
- public static final String CHECKIN_SEND_APANIC_CONSOLE_TIME =
- "checkin_apanic_console_time";
-
- /**
- * The time in msec, when the apanic_thread file was send to the checkin server.
- * We will only send the apanic_thread file if it was modified after this time.
- *
- * @hide
- */
- public static final String CHECKIN_SEND_APANIC_THREAD_TIME =
- "checkin_apanic_thread_time";
-
- /**
* The CDMA roaming mode 0 = Home Networks, CDMA default
* 1 = Roaming on Affiliated networks
* 2 = Roaming on any networks
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 4860cbd..747ae30 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -1638,6 +1638,13 @@
*/
public static final String LAST_TRY = "last_try";
}
+
+ public static final class WordsTable {
+ public static final String ID = "_id";
+ public static final String SOURCE_ROW_ID = "source_id";
+ public static final String TABLE_ID = "table_to_use";
+ public static final String INDEXED_TEXT = "index_text";
+ }
}
public static final class Carriers implements BaseColumns {
diff --git a/core/java/android/text/LoginFilter.java b/core/java/android/text/LoginFilter.java
index 9045c09..e2d1596 100644
--- a/core/java/android/text/LoginFilter.java
+++ b/core/java/android/text/LoginFilter.java
@@ -158,11 +158,11 @@
/**
* This filter rejects characters in the user name that are not compatible with Google login.
- * It is slightly less restrictive than the above filter in that it allows [a-zA-Z0-9._-].
+ * It is slightly less restrictive than the above filter in that it allows [a-zA-Z0-9._-+].
*
*/
public static class UsernameFilterGeneric extends LoginFilter {
- private static final String mAllowed = "@_-."; // Additional characters
+ private static final String mAllowed = "@_-+."; // Additional characters
public UsernameFilterGeneric() {
super(false);
diff --git a/core/java/android/view/TransformGestureDetector.java b/core/java/android/view/TransformGestureDetector.java
new file mode 100644
index 0000000..196716a
--- /dev/null
+++ b/core/java/android/view/TransformGestureDetector.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2010 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.view;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.GestureDetector.SimpleOnGestureListener;
+
+/**
+ * Detects transformation gestures involving more than one pointer ("multitouch")
+ * using the supplied {@link MotionEvent}s. The {@link OnGestureListener} callback
+ * will notify users when a particular gesture event has occurred. This class
+ * should only be used with {@link MotionEvent}s reported via touch.
+ *
+ * To use this class:
+ * <ul>
+ * <li>Create an instance of the {@code TransformGestureDetector} for your
+ * {@link View}
+ * <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
+ * {@link #onTouchEvent(MotionEvent)}. The methods defined in your
+ * callback will be executed when the events occur.
+ * </ul>
+ * @hide Pending API approval
+ */
+public class TransformGestureDetector {
+ /**
+ * The listener for receiving notifications when gestures occur.
+ * If you want to listen for all the different gestures then implement
+ * this interface. If you only want to listen for a subset it might
+ * be easier to extend {@link SimpleOnGestureListener}.
+ *
+ * An application will receive events in the following order:
+ * One onTransformBegin()
+ * Zero or more onTransform()
+ * One onTransformEnd() or onTransformFling()
+ */
+ public interface OnTransformGestureListener {
+ /**
+ * Responds to transformation events for a gesture in progress.
+ * Reported by pointer motion.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ * @return true if the event was handled, false otherwise.
+ */
+ public boolean onTransform(TransformGestureDetector detector);
+
+ /**
+ * Responds to the beginning of a transformation gesture. Reported by
+ * new pointers going down.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ * @return true if the event was handled, false otherwise.
+ */
+ public boolean onTransformBegin(TransformGestureDetector detector);
+
+ /**
+ * Responds to the end of a transformation gesture. Reported by existing
+ * pointers going up. If the end of a gesture would result in a fling,
+ * onTransformFling is called instead.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ * @return true if the event was handled, false otherwise.
+ */
+ public boolean onTransformEnd(TransformGestureDetector detector);
+
+ /**
+ * Responds to the end of a transformation gesture that begins a fling.
+ * Reported by existing pointers going up. If the end of a gesture
+ * would not result in a fling, onTransformEnd is called instead.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ * @return true if the event was handled, false otherwise.
+ */
+ public boolean onTransformFling(TransformGestureDetector detector);
+ }
+
+ private static final boolean DEBUG = false;
+
+ private static final int INITIAL_EVENT_IGNORES = 2;
+
+ private Context mContext;
+ private float mTouchSizeScale;
+ private OnTransformGestureListener mListener;
+ private int mVelocityTimeUnits;
+ private MotionEvent mInitialEvent;
+
+ private MotionEvent mPrevEvent;
+ private MotionEvent mCurrEvent;
+ private VelocityTracker mVelocityTracker;
+
+ private float mCenterX;
+ private float mCenterY;
+ private float mTransX;
+ private float mTransY;
+ private float mPrevFingerDiffX;
+ private float mPrevFingerDiffY;
+ private float mCurrFingerDiffX;
+ private float mCurrFingerDiffY;
+ private float mRotateDegrees;
+ private float mCurrLen;
+ private float mPrevLen;
+ private float mScaleFactor;
+
+ // Units in pixels. Current value is pulled out of thin air for debugging only.
+ private float mPointerJumpLimit = 30;
+
+ private int mEventIgnoreCount;
+
+ public TransformGestureDetector(Context context, OnTransformGestureListener listener,
+ int velocityTimeUnits) {
+ mContext = context;
+ mListener = listener;
+ mTouchSizeScale = context.getResources().getDisplayMetrics().widthPixels/3;
+ mVelocityTimeUnits = velocityTimeUnits;
+ mEventIgnoreCount = INITIAL_EVENT_IGNORES;
+ }
+
+ public TransformGestureDetector(Context context, OnTransformGestureListener listener) {
+ this(context, listener, 1000);
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ final int action = event.getAction();
+ boolean handled = true;
+
+ if (mInitialEvent == null) {
+ // No transform gesture in progress
+ if ((action == MotionEvent.ACTION_POINTER_1_DOWN ||
+ action == MotionEvent.ACTION_POINTER_2_DOWN) &&
+ event.getPointerCount() >= 2) {
+ // We have a new multi-finger gesture
+ mInitialEvent = MotionEvent.obtain(event);
+ mPrevEvent = MotionEvent.obtain(event);
+ mVelocityTracker = VelocityTracker.obtain();
+ handled = mListener.onTransformBegin(this);
+ }
+ } else {
+ // Transform gesture in progress - attempt to handle it
+ switch (action) {
+ case MotionEvent.ACTION_POINTER_1_UP:
+ case MotionEvent.ACTION_POINTER_2_UP:
+ // Gesture ended
+ handled = mListener.onTransformEnd(this);
+
+ reset();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ handled = mListener.onTransformEnd(this);
+
+ reset();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ setContext(event);
+
+ // Our first few events can be crazy from some touchscreens - drop them.
+ if (mEventIgnoreCount == 0) {
+ mVelocityTracker.addMovement(event);
+ handled = mListener.onTransform(this);
+ } else {
+ mEventIgnoreCount--;
+ }
+
+ mPrevEvent.recycle();
+ mPrevEvent = MotionEvent.obtain(event);
+ break;
+ }
+ }
+ return handled;
+ }
+
+ private void setContext(MotionEvent curr) {
+ mCurrEvent = MotionEvent.obtain(curr);
+
+ mRotateDegrees = -1;
+ mCurrLen = -1;
+ mPrevLen = -1;
+ mScaleFactor = -1;
+
+ final MotionEvent prev = mPrevEvent;
+
+ float px0 = prev.getX(0);
+ float py0 = prev.getY(0);
+ float px1 = prev.getX(1);
+ float py1 = prev.getY(1);
+ float cx0 = curr.getX(0);
+ float cy0 = curr.getY(0);
+ float cx1 = curr.getX(1);
+ float cy1 = curr.getY(1);
+
+ // Some touchscreens do weird things with pointer values where points are
+ // too close along one axis. Try to detect this here and smooth things out.
+ // The main indicator is that we get the X or Y value from the other pointer.
+ final float dx0 = cx0 - px0;
+ final float dy0 = cy0 - py0;
+ final float dx1 = cx1 - px1;
+ final float dy1 = cy1 - py1;
+
+ if (cx0 == cx1) {
+ if (Math.abs(dx0) > mPointerJumpLimit) {
+ cx0 = px0;
+ } else if (Math.abs(dx1) > mPointerJumpLimit) {
+ cx1 = px1;
+ }
+ } else if (cy0 == cy1) {
+ if (Math.abs(dy0) > mPointerJumpLimit) {
+ cy0 = py0;
+ } else if (Math.abs(dy1) > mPointerJumpLimit) {
+ cy1 = py1;
+ }
+ }
+
+ final float pvx = px1 - px0;
+ final float pvy = py1 - py0;
+ final float cvx = cx1 - cx0;
+ final float cvy = cy1 - cy0;
+ mPrevFingerDiffX = pvx;
+ mPrevFingerDiffY = pvy;
+ mCurrFingerDiffX = cvx;
+ mCurrFingerDiffY = cvy;
+
+ final float pmidx = px0 + pvx * 0.5f;
+ final float pmidy = py0 + pvy * 0.5f;
+ final float cmidx = cx0 + cvx * 0.5f;
+ final float cmidy = cy0 + cvy * 0.5f;
+
+ mCenterX = cmidx;
+ mCenterY = cmidy;
+ mTransX = cmidx - pmidx;
+ mTransY = cmidy - pmidy;
+ }
+
+ private void reset() {
+ if (mInitialEvent != null) {
+ mInitialEvent.recycle();
+ mInitialEvent = null;
+ }
+ if (mPrevEvent != null) {
+ mPrevEvent.recycle();
+ mPrevEvent = null;
+ }
+ if (mCurrEvent != null) {
+ mCurrEvent.recycle();
+ mCurrEvent = null;
+ }
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ mEventIgnoreCount = INITIAL_EVENT_IGNORES;
+ }
+
+ public float getCenterX() {
+ return mCenterX;
+ }
+
+ public float getCenterY() {
+ return mCenterY;
+ }
+
+ public float getTranslateX() {
+ return mTransX;
+ }
+
+ public float getTranslateY() {
+ return mTransY;
+ }
+
+ public float getCurrentSpan() {
+ if (mCurrLen == -1) {
+ final float cvx = mCurrFingerDiffX;
+ final float cvy = mCurrFingerDiffY;
+ mCurrLen = (float)Math.sqrt(cvx*cvx + cvy*cvy);
+ }
+ return mCurrLen;
+ }
+
+ public float getPreviousSpan() {
+ if (mPrevLen == -1) {
+ final float pvx = mPrevFingerDiffX;
+ final float pvy = mPrevFingerDiffY;
+ mPrevLen = (float)Math.sqrt(pvx*pvx + pvy*pvy);
+ }
+ return mPrevLen;
+ }
+
+ public float getScaleFactor() {
+ if (mScaleFactor == -1) {
+ mScaleFactor = getCurrentSpan() / getPreviousSpan();
+ }
+ return mScaleFactor;
+ }
+
+ public float getRotation() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index 5d89c46..9581080 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -55,12 +55,12 @@
}
}, 2));
- final float mPastX[] = new float[NUM_PAST];
- final float mPastY[] = new float[NUM_PAST];
- final long mPastTime[] = new long[NUM_PAST];
+ final float mPastX[][] = new float[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST];
+ final float mPastY[][] = new float[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST];
+ final long mPastTime[][] = new long[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST];
- float mYVelocity;
- float mXVelocity;
+ float mYVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS];
+ float mXVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS];
private VelocityTracker mNext;
@@ -105,7 +105,9 @@
* Reset the velocity tracker back to its initial state.
*/
public void clear() {
- mPastTime[0] = 0;
+ for (int i = 0; i < MotionEvent.BASE_AVAIL_POINTERS; i++) {
+ mPastTime[i][0] = 0;
+ }
}
/**
@@ -120,18 +122,21 @@
public void addMovement(MotionEvent ev) {
long time = ev.getEventTime();
final int N = ev.getHistorySize();
- for (int i=0; i<N; i++) {
- addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i),
- ev.getHistoricalEventTime(i));
+ final int pointerCount = ev.getPointerCount();
+ for (int p = 0; p < pointerCount; p++) {
+ for (int i=0; i<N; i++) {
+ addPoint(p, ev.getHistoricalX(p, i), ev.getHistoricalY(p, i),
+ ev.getHistoricalEventTime(i));
+ }
+ addPoint(p, ev.getX(p), ev.getY(p), time);
}
- addPoint(ev.getX(), ev.getY(), time);
}
- private void addPoint(float x, float y, long time) {
+ private void addPoint(int pos, float x, float y, long time) {
int drop = -1;
int i;
if (localLOGV) Log.v(TAG, "Adding past y=" + y + " time=" + time);
- final long[] pastTime = mPastTime;
+ final long[] pastTime = mPastTime[pos];
for (i=0; i<NUM_PAST; i++) {
if (pastTime[i] == 0) {
break;
@@ -146,8 +151,8 @@
drop = 0;
}
if (drop == i) drop--;
- final float[] pastX = mPastX;
- final float[] pastY = mPastY;
+ final float[] pastX = mPastX[pos];
+ final float[] pastY = mPastY[pos];
if (drop >= 0) {
if (localLOGV) Log.v(TAG, "Dropping up to #" + drop);
final int start = drop+1;
@@ -190,44 +195,48 @@
* must be positive.
*/
public void computeCurrentVelocity(int units, float maxVelocity) {
- final float[] pastX = mPastX;
- final float[] pastY = mPastY;
- final long[] pastTime = mPastTime;
-
- // Kind-of stupid.
- final float oldestX = pastX[0];
- final float oldestY = pastY[0];
- final long oldestTime = pastTime[0];
- float accumX = 0;
- float accumY = 0;
- int N=0;
- while (N < NUM_PAST) {
- if (pastTime[N] == 0) {
- break;
+ for (int pos = 0; pos < MotionEvent.BASE_AVAIL_POINTERS; pos++) {
+ final float[] pastX = mPastX[pos];
+ final float[] pastY = mPastY[pos];
+ final long[] pastTime = mPastTime[pos];
+
+ // Kind-of stupid.
+ final float oldestX = pastX[0];
+ final float oldestY = pastY[0];
+ final long oldestTime = pastTime[0];
+ float accumX = 0;
+ float accumY = 0;
+ int N=0;
+ while (N < NUM_PAST) {
+ if (pastTime[N] == 0) {
+ break;
+ }
+ N++;
}
- N++;
+ // Skip the last received event, since it is probably pretty noisy.
+ if (N > 3) N--;
+
+ for (int i=1; i < N; i++) {
+ final int dur = (int)(pastTime[i] - oldestTime);
+ if (dur == 0) continue;
+ float dist = pastX[i] - oldestX;
+ float vel = (dist/dur) * units; // pixels/frame.
+ if (accumX == 0) accumX = vel;
+ else accumX = (accumX + vel) * .5f;
+
+ dist = pastY[i] - oldestY;
+ vel = (dist/dur) * units; // pixels/frame.
+ if (accumY == 0) accumY = vel;
+ else accumY = (accumY + vel) * .5f;
+ }
+ mXVelocity[pos] = accumX < 0.0f ? Math.max(accumX, -maxVelocity)
+ : Math.min(accumX, maxVelocity);
+ mYVelocity[pos] = accumY < 0.0f ? Math.max(accumY, -maxVelocity)
+ : Math.min(accumY, maxVelocity);
+
+ if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity="
+ + mXVelocity + " N=" + N);
}
- // Skip the last received event, since it is probably pretty noisy.
- if (N > 3) N--;
-
- for (int i=1; i < N; i++) {
- final int dur = (int)(pastTime[i] - oldestTime);
- if (dur == 0) continue;
- float dist = pastX[i] - oldestX;
- float vel = (dist/dur) * units; // pixels/frame.
- if (accumX == 0) accumX = vel;
- else accumX = (accumX + vel) * .5f;
-
- dist = pastY[i] - oldestY;
- vel = (dist/dur) * units; // pixels/frame.
- if (accumY == 0) accumY = vel;
- else accumY = (accumY + vel) * .5f;
- }
- mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity) : Math.min(accumX, maxVelocity);
- mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity) : Math.min(accumY, maxVelocity);
-
- if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity="
- + mXVelocity + " N=" + N);
}
/**
@@ -237,7 +246,7 @@
* @return The previously computed X velocity.
*/
public float getXVelocity() {
- return mXVelocity;
+ return mXVelocity[0];
}
/**
@@ -247,6 +256,32 @@
* @return The previously computed Y velocity.
*/
public float getYVelocity() {
- return mYVelocity;
+ return mYVelocity[0];
+ }
+
+ /**
+ * Retrieve the last computed X velocity. You must first call
+ * {@link #computeCurrentVelocity(int)} before calling this function.
+ *
+ * @param pos Which pointer's velocity to return.
+ * @return The previously computed X velocity.
+ *
+ * @hide Pending API approval
+ */
+ public float getXVelocity(int pos) {
+ return mXVelocity[pos];
+ }
+
+ /**
+ * Retrieve the last computed Y velocity. You must first call
+ * {@link #computeCurrentVelocity(int)} before calling this function.
+ *
+ * @param pos Which pointer's velocity to return.
+ * @return The previously computed Y velocity.
+ *
+ * @hide Pending API approval
+ */
+ public float getYVelocity(int pos) {
+ return mYVelocity[pos];
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index df4cab0..a3d3521 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -6177,7 +6177,10 @@
* @see #getDrawingCache()
*/
public void setDrawingCacheBackgroundColor(int color) {
- mDrawingCacheBackgroundColor = color;
+ if (color != mDrawingCacheBackgroundColor) {
+ mDrawingCacheBackgroundColor = color;
+ mPrivateFlags &= ~DRAWING_CACHE_VALID;
+ }
}
/**
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index c718bac..3da18d6 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -111,7 +111,15 @@
* flag for you on multi-line text views.
*/
public static final int IME_FLAG_NO_ENTER_ACTION = 0x40000000;
-
+
+ /**
+ * Flag of {@link #imeOptions}: used to request that the IME never go
+ * into fullscreen mode. Applications need to be aware that the flag is not
+ * a guarantee, and not all IMEs will respect it.
+ * @hide
+ */
+ public static final int IME_FLAG_NO_FULLSCREEN = 0x80000000;
+
/**
* Generic unspecified type for {@link #imeOptions}.
*/
diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java
index ecbc7e7..429b335 100644
--- a/core/java/android/webkit/HTML5VideoViewProxy.java
+++ b/core/java/android/webkit/HTML5VideoViewProxy.java
@@ -48,6 +48,8 @@
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
/**
* <p>Proxy for HTML5 video views.
@@ -71,6 +73,9 @@
private static final int ENDED = 201;
private static final int POSTER_FETCHED = 202;
+ // Timer thread -> UI thread
+ private static final int TIMEUPDATE = 300;
+
// The C++ MediaPlayerPrivateAndroid object.
int mNativePointer;
// The handler for WebCore thread messages;
@@ -95,6 +100,22 @@
private static View mProgressView;
// The container for the progress view and video view
private static FrameLayout mLayout;
+ // The timer for timeupate events.
+ // See http://www.whatwg.org/specs/web-apps/current-work/#event-media-timeupdate
+ private static Timer mTimer;
+ private static final class TimeupdateTask extends TimerTask {
+ private HTML5VideoViewProxy mProxy;
+
+ public TimeupdateTask(HTML5VideoViewProxy proxy) {
+ mProxy = proxy;
+ }
+
+ public void run() {
+ mProxy.onTimeupdate();
+ }
+ }
+ // The spec says the timer should fire every 250 ms or less.
+ private static final int TIMEUPDATE_PERIOD = 250; // ms
private static final WebChromeClient.CustomViewCallback mCallback =
new WebChromeClient.CustomViewCallback() {
@@ -104,6 +125,8 @@
// which happens when the video view is detached from its parent
// view. This happens in the WebChromeClient before this method
// is invoked.
+ mTimer.cancel();
+ mTimer = null;
mCurrentProxy.playbackEnded();
mCurrentProxy = null;
mLayout.removeView(mVideoView);
@@ -154,10 +177,23 @@
mProgressView.setVisibility(View.VISIBLE);
}
mLayout.setVisibility(View.VISIBLE);
+ mTimer = new Timer();
mVideoView.start();
client.onShowCustomView(mLayout, mCallback);
}
+ public static boolean isPlaying(HTML5VideoViewProxy proxy) {
+ return (mCurrentProxy == proxy && mVideoView != null && mVideoView.isPlaying());
+ }
+
+ public static int getCurrentPosition() {
+ int currentPosMs = 0;
+ if (mVideoView != null) {
+ currentPosMs = mVideoView.getCurrentPosition();
+ }
+ return currentPosMs;
+ }
+
public static void seek(int time, HTML5VideoViewProxy proxy) {
if (mCurrentProxy == proxy && time >= 0 && mVideoView != null) {
mVideoView.seekTo(time);
@@ -167,10 +203,13 @@
public static void pause(HTML5VideoViewProxy proxy) {
if (mCurrentProxy == proxy && mVideoView != null) {
mVideoView.pause();
+ mTimer.purge();
}
}
public static void onPrepared() {
+ mTimer.schedule(new TimeupdateTask(mCurrentProxy), TIMEUPDATE_PERIOD, TIMEUPDATE_PERIOD);
+
if (mProgressView == null || mLayout == null) {
return;
}
@@ -211,7 +250,11 @@
sendMessage(obtainMessage(ENDED));
}
- // Handler for the messages from WebCore thread to the UI thread.
+ public void onTimeupdate() {
+ sendMessage(obtainMessage(TIMEUPDATE));
+ }
+
+ // Handler for the messages from WebCore or Timer thread to the UI thread.
@Override
public void handleMessage(Message msg) {
// This executes on the UI thread.
@@ -249,6 +292,12 @@
}
break;
}
+ case TIMEUPDATE: {
+ if (VideoPlayer.isPlaying(this)) {
+ sendTimeupdate();
+ }
+ break;
+ }
}
}
@@ -418,6 +467,9 @@
Bitmap poster = (Bitmap) msg.obj;
nativeOnPosterFetched(poster, mNativePointer);
break;
+ case TIMEUPDATE:
+ nativeOnTimeupdate(msg.arg1, mNativePointer);
+ break;
}
}
};
@@ -434,6 +486,12 @@
mWebCoreHandler.sendMessage(msg);
}
+ private void sendTimeupdate() {
+ Message msg = Message.obtain(mWebCoreHandler, TIMEUPDATE);
+ msg.arg1 = VideoPlayer.getCurrentPosition();
+ mWebCoreHandler.sendMessage(msg);
+ }
+
public Context getContext() {
return mWebView.getContext();
}
@@ -514,4 +572,5 @@
private native void nativeOnPrepared(int duration, int width, int height, int nativePointer);
private native void nativeOnEnded(int nativePointer);
private native void nativeOnPosterFetched(Bitmap poster, int nativePointer);
+ private native void nativeOnTimeupdate(int position, int nativePointer);
}
diff --git a/core/java/android/webkit/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java
index 84a8a3c..a9d6ff6 100644
--- a/core/java/android/webkit/MimeTypeMap.java
+++ b/core/java/android/webkit/MimeTypeMap.java
@@ -124,6 +124,11 @@
return null;
}
+ // Static method called by jni.
+ private static String mimeTypeFromExtension(String extension) {
+ return getSingleton().getMimeTypeFromExtension(extension);
+ }
+
/**
* Return true if the given extension has a registered MIME type.
* @param extension A file extension without the leading '.'
@@ -344,6 +349,7 @@
sMimeTypeMap.loadEntry("application/x-pkcs7-crl", "crl");
sMimeTypeMap.loadEntry("application/x-quicktimeplayer", "qtl");
sMimeTypeMap.loadEntry("application/x-shar", "shar");
+ sMimeTypeMap.loadEntry("application/x-shockwave-flash", "swf");
sMimeTypeMap.loadEntry("application/x-stuffit", "sit");
sMimeTypeMap.loadEntry("application/x-sv4cpio", "sv4cpio");
sMimeTypeMap.loadEntry("application/x-sv4crc", "sv4crc");
diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java
index b6891b1..6d0be43 100644
--- a/core/java/android/webkit/WebTextView.java
+++ b/core/java/android/webkit/WebTextView.java
@@ -807,19 +807,21 @@
int maxLength = -1;
int inputType = EditorInfo.TYPE_CLASS_TEXT
| EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
+ int imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
+ | EditorInfo.IME_FLAG_NO_FULLSCREEN;
switch (type) {
case 1: // TEXT_AREA
single = false;
inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE
| EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES
| EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT;
- setImeOptions(EditorInfo.IME_ACTION_NONE);
+ imeOptions |= EditorInfo.IME_ACTION_NONE;
break;
case 2: // PASSWORD
inPassword = true;
break;
case 3: // SEARCH
- setImeOptions(EditorInfo.IME_ACTION_SEARCH);
+ imeOptions |= EditorInfo.IME_ACTION_SEARCH;
break;
case 4: // EMAIL
// TYPE_TEXT_VARIATION_WEB_EDIT_TEXT prevents EMAIL_ADDRESS
@@ -858,14 +860,14 @@
switch (action) {
// Keep in sync with CachedRoot::ImeAction
case 0: // NEXT
- setImeOptions(EditorInfo.IME_ACTION_NEXT);
+ imeOptions |= EditorInfo.IME_ACTION_NEXT;
break;
case 1: // GO
- setImeOptions(EditorInfo.IME_ACTION_GO);
+ imeOptions |= EditorInfo.IME_ACTION_GO;
break;
case -1: // FAILURE
case 2: // DONE
- setImeOptions(EditorInfo.IME_ACTION_DONE);
+ imeOptions |= EditorInfo.IME_ACTION_DONE;
break;
}
}
@@ -874,6 +876,7 @@
setMaxLength(maxLength);
setHorizontallyScrolling(single);
setInputType(inputType);
+ setImeOptions(imeOptions);
setInPassword(inPassword);
AutoCompleteAdapter adapter = null;
setAdapterCustom(adapter);
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index b10d786..09ed931 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -2997,8 +2997,11 @@
private void drawLayers(Canvas canvas) {
if (mRootLayer != 0) {
- float scrollY = Math.max(mScrollY - getTitleHeight(), 0);
+ int scrollY = computeVerticalScrollOffset();
+ int viewHeight = getHeight() - getVisibleTitleHeight();
+
nativeDrawLayers(mRootLayer, mScrollX, scrollY,
+ getWidth(), viewHeight,
mActualScale, canvas);
}
}
@@ -3892,7 +3895,9 @@
public void run() {
// we always force, in case our height changed, in which case we
// still want to send the notification over to webkit
- setNewZoomScale(mActualScale, true);
+ if (mWebViewCore != null) {
+ setNewZoomScale(mActualScale, true);
+ }
}
});
}
@@ -5127,12 +5132,12 @@
mWebViewCore.sendMessageAtFrontOfQueue(EventHub.VALID_NODE_BOUNDS,
motionUpData);
} else {
- doMotionUp(contentX, contentY, false);
+ doMotionUp(contentX, contentY);
}
}
- private void doMotionUp(int contentX, int contentY, boolean useNavCache) {
- if (nativeMotionUp(contentX, contentY, useNavCache ? mNavSlop : 0)) {
+ private void doMotionUp(int contentX, int contentY) {
+ if (nativeMotionUp(contentX, contentY, mNavSlop)) {
if (mLogEvent) {
Checkin.updateStats(mContext.getContentResolver(),
Checkin.Stats.Tag.BROWSER_SNAP_CENTER, 1, 0.0);
@@ -5761,7 +5766,7 @@
break;
case DO_MOTION_UP:
- doMotionUp(msg.arg1, msg.arg2, (Boolean) msg.obj);
+ doMotionUp(msg.arg1, msg.arg2);
break;
case SHOW_FULLSCREEN:
@@ -6352,7 +6357,8 @@
private native boolean nativeLayersHaveAnimations(int layer);
private native void nativeUpdateLayers(int layer, int updates);
private native void nativeDrawLayers(int layer,
- float scrollX, float scrollY,
+ int scrollX, int scrollY,
+ int width, int height,
float scale, Canvas canvas);
private native void nativeDrawMatches(Canvas canvas);
private native void nativeDrawSelectionPointer(Canvas content,
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 463f14e..949b318 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -1316,13 +1316,14 @@
case VALID_NODE_BOUNDS: {
MotionUpData motionUpData = (MotionUpData) msg.obj;
- boolean result = nativeValidNodeAndBounds(
+ if (!nativeValidNodeAndBounds(
motionUpData.mFrame, motionUpData.mNode,
- motionUpData.mBounds);
+ motionUpData.mBounds)) {
+ nativeUpdateFrameCache();
+ }
Message message = mWebView.mPrivateHandler
.obtainMessage(WebView.DO_MOTION_UP,
- motionUpData.mX, motionUpData.mY,
- new Boolean(result));
+ motionUpData.mX, motionUpData.mY);
mWebView.mPrivateHandler.sendMessageAtFrontOfQueue(
message);
break;
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index e241c77..b795080 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -1567,7 +1567,12 @@
if (!hasWindowFocus) {
setChildrenDrawingCacheEnabled(false);
- removeCallbacks(mFlingRunnable);
+ if (mFlingRunnable != null) {
+ removeCallbacks(mFlingRunnable);
+ // let the fling runnable report it's new state which
+ // should be idle
+ mFlingRunnable.endFling();
+ }
// Always hide the type filter
dismissPopup();
@@ -3245,7 +3250,14 @@
* @param color The background color
*/
public void setCacheColorHint(int color) {
- mCacheColorHint = color;
+ if (color != mCacheColorHint) {
+ mCacheColorHint = color;
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ getChildAt(i).setDrawingCacheBackgroundColor(color);
+ }
+ mRecycler.setCacheColorHint(color);
+ }
}
/**
@@ -3665,5 +3677,38 @@
}
}
}
+
+ /**
+ * Updates the cache color hint of all known views.
+ *
+ * @param color The new cache color hint.
+ */
+ void setCacheColorHint(int color) {
+ if (mViewTypeCount == 1) {
+ final ArrayList<View> scrap = mCurrentScrap;
+ final int scrapCount = scrap.size();
+ for (int i = 0; i < scrapCount; i++) {
+ scrap.get(i).setDrawingCacheBackgroundColor(color);
+ }
+ } else {
+ final int typeCount = mViewTypeCount;
+ for (int i = 0; i < typeCount; i++) {
+ final ArrayList<View> scrap = mScrapViews[i];
+ final int scrapCount = scrap.size();
+ for (int j = 0; j < scrapCount; j++) {
+ scrap.get(i).setDrawingCacheBackgroundColor(color);
+ }
+ }
+ }
+ // Just in case this is called during a layout pass
+ final View[] activeViews = mActiveViews;
+ final int count = activeViews.length;
+ for (int i = 0; i < count; ++i) {
+ final View victim = activeViews[i];
+ if (victim != null) {
+ victim.setDrawingCacheBackgroundColor(color);
+ }
+ }
+ }
}
}
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index fad994b..1aa1df3 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -291,6 +291,8 @@
}
}
+ // TODO: we need to find another way to implement RelativeLayout
+ // This implementation cannot handle every case
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mDirtyHierarchy) {
@@ -438,6 +440,10 @@
final int[] rules = params.getRules();
if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
centerHorizontal(child, params, width);
+ } else if (rules[ALIGN_PARENT_RIGHT] != 0) {
+ final int childWidth = child.getMeasuredWidth();
+ params.mLeft = width - mPaddingRight - childWidth;
+ params.mRight = params.mLeft + childWidth;
}
}
}
@@ -464,6 +470,10 @@
final int[] rules = params.getRules();
if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {
centerVertical(child, params, height);
+ } else if (rules[ALIGN_PARENT_BOTTOM] != 0) {
+ final int childHeight = child.getMeasuredHeight();
+ params.mTop = height - mPaddingBottom - childHeight;
+ params.mBottom = params.mTop + childHeight;
}
}
}
@@ -673,7 +683,7 @@
params.mRight = params.mLeft + child.getMeasuredWidth();
}
}
- return false;
+ return rules[ALIGN_PARENT_RIGHT] != 0;
}
private boolean positionChildVertical(View child, LayoutParams params, int myHeight,
@@ -702,7 +712,7 @@
params.mBottom = params.mTop + child.getMeasuredHeight();
}
}
- return false;
+ return rules[ALIGN_PARENT_BOTTOM] != 0;
}
private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth) {
diff --git a/core/java/com/android/internal/app/ExternalMediaFormatActivity.java b/core/java/com/android/internal/app/ExternalMediaFormatActivity.java
index 000f6c4..2b07ae6 100644
--- a/core/java/com/android/internal/app/ExternalMediaFormatActivity.java
+++ b/core/java/com/android/internal/app/ExternalMediaFormatActivity.java
@@ -102,7 +102,7 @@
.getService("mount"));
if (mountService != null) {
try {
- mountService.formatMedia(Environment.getExternalStorageDirectory().toString());
+ mountService.formatVolume(Environment.getExternalStorageDirectory().toString());
} catch (RemoteException e) {
}
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 58056bd..3c5a753 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -38,8 +38,9 @@
public class LockPatternUtils {
private static final String TAG = "LockPatternUtils";
-
+
private static final String LOCK_PATTERN_FILE = "/system/gesture.key";
+ private static final String LOCK_PASSWORD_FILE = "/system/password.key";
/**
* The maximum number of incorrect attempts before the user is prevented
@@ -70,20 +71,32 @@
public static final int MIN_LOCK_PATTERN_SIZE = 4;
/**
+ * Type of password being stored.
+ * pattern = pattern screen
+ * pin = digit-only password
+ * password = alphanumeric password
+ */
+ public static final int MODE_PATTERN = 0;
+ public static final int MODE_PIN = 1;
+ public static final int MODE_PASSWORD = 2;
+
+ /**
* The minimum number of dots the user must include in a wrong pattern
* attempt for it to be counted against the counts that affect
* {@link #FAILED_ATTEMPTS_BEFORE_TIMEOUT} and {@link #FAILED_ATTEMPTS_BEFORE_RESET}
*/
- public static final int MIN_PATTERN_REGISTER_FAIL = 3;
+ public static final int MIN_PATTERN_REGISTER_FAIL = 3;
private final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently";
private final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline";
- private final static String PATTERN_EVER_CHOSEN = "lockscreen.patterneverchosen";
+ private final static String PATTERN_EVER_CHOSEN_KEY = "lockscreen.patterneverchosen";
+ public final static String PASSWORD_TYPE_KEY = "lockscreen.password_type";
private final ContentResolver mContentResolver;
private static String sLockPatternFilename;
-
+ private static String sLockPasswordFilename;
+
/**
* @param contentResolver Used to look up and save settings.
*/
@@ -91,16 +104,19 @@
mContentResolver = contentResolver;
// Initialize the location of gesture lock file
if (sLockPatternFilename == null) {
- sLockPatternFilename = android.os.Environment.getDataDirectory()
+ sLockPatternFilename = android.os.Environment.getDataDirectory()
.getAbsolutePath() + LOCK_PATTERN_FILE;
+ sLockPasswordFilename = android.os.Environment.getDataDirectory()
+ .getAbsolutePath() + LOCK_PASSWORD_FILE;
}
+
}
/**
* Check to see if a pattern matches the saved pattern. If no pattern exists,
* always returns true.
* @param pattern The pattern to check.
- * @return Whether the pattern matchees the stored one.
+ * @return Whether the pattern matches the stored one.
*/
public boolean checkPattern(List<LockPatternView.Cell> pattern) {
try {
@@ -122,13 +138,40 @@
}
/**
- * Check to see if the user has stored a lock pattern.
- * @return Whether a saved pattern exists.
+ * Check to see if a password matches the saved password. If no password exists,
+ * always returns true.
+ * @param password The password to check.
+ * @return Whether the password matches the stored one.
*/
- public boolean savedPatternExists() {
+ public boolean checkPassword(String password) {
+ try {
+ // Read all the bytes from the file
+ RandomAccessFile raf = new RandomAccessFile(sLockPasswordFilename, "r");
+ final byte[] stored = new byte[(int) raf.length()];
+ int got = raf.read(stored, 0, stored.length);
+ raf.close();
+ if (got <= 0) {
+ return true;
+ }
+ // Compare the hash from the file with the entered password's hash
+ return Arrays.equals(stored, LockPatternUtils.passwordToHash(password));
+ } catch (FileNotFoundException fnfe) {
+ return true;
+ } catch (IOException ioe) {
+ return true;
+ }
+ }
+
+ /**
+ * Checks to see if the given file exists and contains any data. Returns true if it does,
+ * false otherwise.
+ * @param filename
+ * @return true if file exists and is non-empty.
+ */
+ private boolean nonEmptyFileExists(String filename) {
try {
// Check if we can read a byte from the file
- RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "r");
+ RandomAccessFile raf = new RandomAccessFile(filename, "r");
byte first = raf.readByte();
raf.close();
return true;
@@ -140,13 +183,29 @@
}
/**
+ * Check to see if the user has stored a lock pattern.
+ * @return Whether a saved pattern exists.
+ */
+ public boolean savedPatternExists() {
+ return nonEmptyFileExists(sLockPatternFilename);
+ }
+
+ /**
+ * Check to see if the user has stored a lock pattern.
+ * @return Whether a saved pattern exists.
+ */
+ public boolean savedPasswordExists() {
+ return nonEmptyFileExists(sLockPasswordFilename);
+ }
+
+ /**
* Return true if the user has ever chosen a pattern. This is true even if the pattern is
* currently cleared.
*
* @return True if the user has ever chosen a pattern.
*/
public boolean isPatternEverChosen() {
- return getBoolean(PATTERN_EVER_CHOSEN);
+ return getBoolean(PATTERN_EVER_CHOSEN_KEY);
}
/**
@@ -166,7 +225,8 @@
raf.write(hash, 0, hash.length);
}
raf.close();
- setBoolean(PATTERN_EVER_CHOSEN, true);
+ setBoolean(PATTERN_EVER_CHOSEN_KEY, true);
+ setLong(PASSWORD_TYPE_KEY, MODE_PATTERN);
} catch (FileNotFoundException fnfe) {
// Cant do much, unless we want to fail over to using the settings provider
Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename);
@@ -177,6 +237,38 @@
}
/**
+ * Save a lock password.
+ * @param password The password to save
+ */
+ public void saveLockPassword(String password) {
+ // Compute the hash
+ boolean numericHint = password != null ? TextUtils.isDigitsOnly(password) : false;
+ final byte[] hash = LockPatternUtils.passwordToHash(password);
+ try {
+ // Write the hash to file
+ RandomAccessFile raf = new RandomAccessFile(sLockPasswordFilename, "rw");
+ // Truncate the file if pattern is null, to clear the lock
+ if (password == null) {
+ raf.setLength(0);
+ } else {
+ raf.write(hash, 0, hash.length);
+ }
+ raf.close();
+ setLong(PASSWORD_TYPE_KEY, numericHint ? MODE_PIN : MODE_PASSWORD);
+ } catch (FileNotFoundException fnfe) {
+ // Cant do much, unless we want to fail over to using the settings provider
+ Log.e(TAG, "Unable to save lock pattern to " + sLockPasswordFilename);
+ } catch (IOException ioe) {
+ // Cant do much
+ Log.e(TAG, "Unable to save lock pattern to " + sLockPasswordFilename);
+ }
+ }
+
+ public int getPasswordMode() {
+ return (int) getLong(PASSWORD_TYPE_KEY, MODE_PATTERN);
+ }
+
+ /**
* Deserialize a pattern.
* @param string The pattern serialized with {@link #patternToString}
* @return The pattern.
@@ -210,7 +302,7 @@
}
return new String(res);
}
-
+
/*
* Generate an SHA-1 hash for the pattern. Not the most secure, but it is
* at least a second level of protection. First level is that the file
@@ -218,11 +310,11 @@
* @param pattern the gesture pattern.
* @return the hash of the pattern in a byte array.
*/
- static byte[] patternToHash(List<LockPatternView.Cell> pattern) {
+ private static byte[] patternToHash(List<LockPatternView.Cell> pattern) {
if (pattern == null) {
return null;
}
-
+
final int patternSize = pattern.size();
byte[] res = new byte[patternSize];
for (int i = 0; i < patternSize; i++) {
@@ -238,11 +330,55 @@
}
}
+ /*
+ * Generate a hash for the given password. To avoid brute force attacks, we use a salted hash.
+ * Not the most secure, but it is at least a second level of protection. First level is that
+ * the file is in a location only readable by the system process.
+ * @param password the gesture pattern.
+ * @return the hash of the pattern in a byte array.
+ */
+ public static byte[] passwordToHash(String password) {
+ if (password == null) {
+ return null;
+ }
+ String algo = null;
+ byte[] hashed = null;
+ try {
+ long salt = 0x2374868151054924L; // TODO: make this unique to device
+ byte[] saltedPassword = (password + Long.toString(salt)).getBytes();
+ byte[] sha1 = MessageDigest.getInstance(algo = "SHA-1").digest(saltedPassword);
+ byte[] md5 = MessageDigest.getInstance(algo = "MD5").digest(saltedPassword);
+ hashed = (toHex(sha1) + toHex(md5)).getBytes();
+ } catch (NoSuchAlgorithmException e) {
+ Log.w(TAG, "Failed to encode string because of missing algorithm: " + algo);
+ }
+ return hashed;
+ }
+
+ private static String toHex(byte[] ary) {
+ final String hex = "0123456789ABCDEF";
+ String ret = "";
+ for (int i = 0; i < ary.length; i++) {
+ ret += hex.charAt((ary[i] >> 4) & 0xf);
+ ret += hex.charAt(ary[i] & 0xf);
+ }
+ return ret;
+ }
+
+ /**
+ * @return Whether the lock password is enabled.
+ */
+ public boolean isLockPasswordEnabled() {
+ long mode = getLong(PASSWORD_TYPE_KEY, 0);
+ return savedPasswordExists() && (mode == MODE_PASSWORD || mode == MODE_PIN);
+ }
+
/**
* @return Whether the lock pattern is enabled.
*/
public boolean isLockPatternEnabled() {
- return getBoolean(Settings.System.LOCK_PATTERN_ENABLED);
+ return getBoolean(Settings.System.LOCK_PATTERN_ENABLED)
+ && getLong(PASSWORD_TYPE_KEY, MODE_PATTERN) == MODE_PATTERN;
}
/**
@@ -361,5 +497,10 @@
android.provider.Settings.System.putLong(mContentResolver, systemSettingKey, value);
}
-
+ public boolean isSecure() {
+ long mode = getPasswordMode();
+ boolean secure = mode == MODE_PATTERN && isLockPatternEnabled() && savedPatternExists()
+ || (mode == MODE_PIN || mode == MODE_PASSWORD) && savedPasswordExists();
+ return secure;
+ }
}
diff --git a/core/jni/android_os_Power.cpp b/core/jni/android_os_Power.cpp
index df5edba..a46c2dd 100644
--- a/core/jni/android_os_Power.cpp
+++ b/core/jni/android_os_Power.cpp
@@ -105,7 +105,7 @@
{ "setLastUserActivityTimeout", "(J)I", (void*)setLastUserActivityTimeout },
{ "setScreenState", "(Z)I", (void*)setScreenState },
{ "shutdown", "()V", (void*)android_os_Power_shutdown },
- { "reboot", "(Ljava/lang/String;)V", (void*)android_os_Power_reboot },
+ { "rebootNative", "(Ljava/lang/String;)V", (void*)android_os_Power_reboot },
};
int register_android_os_Power(JNIEnv *env)
diff --git a/core/res/res/layout/keyguard_screen_password_landscape.xml b/core/res/res/layout/keyguard_screen_password_landscape.xml
new file mode 100644
index 0000000..5bc034b
--- /dev/null
+++ b/core/res/res/layout/keyguard_screen_password_landscape.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:background="@android:color/background_dark">
+
+ <!-- displays dots as user enters pin -->
+ <LinearLayout android:id="@+id/pinDisplayGroup"
+ android:orientation="horizontal"
+ android:layout_centerInParent="true"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:addStatesFromChildren="true"
+ android:gravity="center_vertical"
+ android:baselineAligned="false"
+ android:paddingRight="0dip"
+ android:layout_marginRight="30dip"
+ android:layout_marginLeft="30dip"
+ android:layout_marginTop="6dip"
+ android:background="@android:drawable/edit_text">
+
+ <EditText android:id="@+id/pinDisplay"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_height="fill_parent"
+ android:maxLines="1"
+ android:background="@null"
+ android:textSize="32sp"
+ android:inputType="textPassword"
+ />
+
+ <ImageButton android:id="@+id/backspace"
+ android:src="@android:drawable/ic_input_delete"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:layout_marginTop="2dip"
+ android:layout_marginRight="2dip"
+ android:layout_marginBottom="2dip"
+ android:gravity="center"
+ />
+
+ </LinearLayout>
+
+ <!-- header text ('Enter Pin Code') -->
+ <TextView android:id="@+id/headerText"
+ android:layout_above="@id/pinDisplayGroup"
+ android:layout_centerHorizontal="true"
+ android:layout_marginBottom="30dip"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="24sp"
+ />
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_alignParentBottom="true"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="8dip"
+ android:layout_marginLeft="8dip"
+ android:layout_marginRight="8dip">
+
+ <Button android:id="@+id/ok"
+ android:text="@android:string/ok"
+ android:layout_alignParentBottom="true"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1.0"
+ android:layout_marginBottom="8dip"
+ android:layout_marginRight="8dip"
+ android:textSize="18sp"
+ />
+
+ <Button android:id="@+id/emergencyCall"
+ android:text="@android:string/lockscreen_emergency_call"
+ android:layout_alignParentBottom="true"
+ android:layout_centerHorizontal="true"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1.0"
+ android:layout_marginBottom="8dip"
+ android:layout_marginLeft="8dip"
+ android:textSize="18sp"
+ android:drawableLeft="@drawable/ic_emergency"
+ android:drawablePadding="8dip"
+ />
+ </LinearLayout>
+
+ <!-- Not currently visible on this screen -->
+ <TextView
+ android:id="@+id/carrier"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_marginTop="6dip"
+ android:layout_alignParentRight="true"
+ android:layout_marginRight="8dip"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:visibility="gone"
+ />
+
+</RelativeLayout>
diff --git a/core/res/res/layout/keyguard_screen_password_portrait.xml b/core/res/res/layout/keyguard_screen_password_portrait.xml
new file mode 100644
index 0000000..a7814af
--- /dev/null
+++ b/core/res/res/layout/keyguard_screen_password_portrait.xml
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical"
+ android:background="#70000000"
+ android:gravity="center_horizontal">
+
+ <LinearLayout android:id="@+id/topDisplayGroup"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <RelativeLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <com.android.internal.widget.DigitalClock android:id="@+id/time"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="6dip"
+ android:layout_marginLeft="6dip"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true">
+
+ <TextView android:id="@+id/timeDisplay"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="bottom"
+ android:singleLine="true"
+ android:ellipsize="none"
+ android:textSize="56sp"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:shadowColor="#C0000000"
+ android:shadowDx="0"
+ android:shadowDy="0"
+ android:shadowRadius="3.0"
+ android:layout_marginBottom="6dip"
+ />
+
+ <TextView android:id="@+id/am_pm"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:gravity="bottom"
+ android:singleLine="true"
+ android:ellipsize="none"
+ android:textSize="18sp"
+ android:layout_marginLeft="4dip"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:shadowColor="#C0000000"
+ android:shadowDx="0"
+ android:shadowDy="0"
+ android:shadowRadius="3.0"
+ />
+
+ </com.android.internal.widget.DigitalClock>
+
+ <TextView android:id="@+id/carrier"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_marginTop="6dip"
+ android:layout_alignParentRight="true"
+ android:layout_marginRight="8dip"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ />
+
+ </RelativeLayout>
+
+ <!-- password entry -->
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginRight="6dip"
+ android:layout_marginLeft="6dip"
+ android:gravity="center_vertical"
+ android:hint="@android:string/keyguard_password_enter_password_code"
+ android:background="@android:drawable/edit_text">
+
+ <!-- displays dots as user enters pin -->
+ <TextView android:id="@+id/pinDisplay"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:maxLines="1"
+ android:textAppearance="?android:attr/textAppearanceLargeInverse"
+ android:textStyle="bold"
+ android:inputType="textPassword"
+ />
+
+ <ImageButton android:id="@+id/backspace"
+ android:src="@android:drawable/ic_input_delete"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="-3dip"
+ android:layout_marginBottom="-3dip"
+ />
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <include
+ android:id="@+id/keyPad"
+ layout="@android:layout/twelve_key_entry"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/topDisplayGroup"
+ android:layout_marginTop="10dip"
+ />
+
+ <!-- spacer below keypad -->
+ <View
+ android:id="@+id/spacerBottom"
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:layout_marginTop="6dip"
+ android:layout_above="@id/emergencyCall"
+ android:background="@android:drawable/divider_horizontal_dark"
+ />
+
+ <!-- The emergency button should take the rest of the space and be centered vertically -->
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:gravity="center"
+ android:orientation="vertical">
+
+ <!-- emergency call button -->
+ <Button
+ android:id="@+id/emergencyCall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableLeft="@android:drawable/ic_emergency"
+ android:drawablePadding="8dip"
+ android:text="@android:string/lockscreen_emergency_call"
+ />
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/core/res/res/layout/keyguard_screen_rotary_unlock.xml b/core/res/res/layout/keyguard_screen_rotary_unlock.xml
deleted file mode 100644
index 8a7553c..0000000
--- a/core/res/res/layout/keyguard_screen_rotary_unlock.xml
+++ /dev/null
@@ -1,138 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2009, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License")
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- This is the general lock screen which shows information about the
- state of the device, as well as instructions on how to get past it
- depending on the state of the device. It is the same for landscape
- and portrait.-->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:id="@+id/root"
- >
-
-<RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="#70000000"
- >
-
- <TextView
- android:id="@+id/carrier"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="20dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?android:attr/textColorSecondary"
- />
-
- <TextView
- android:id="@+id/time"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/carrier"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="25dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textSize="55sp"
- />
-
- <TextView
- android:id="@+id/date"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/time"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="-12dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- />
-
- <View
- android:id="@+id/divider"
- android:layout_width="match_parent"
- android:layout_height="1dip"
- android:layout_marginTop="10dip"
- android:layout_below="@id/date"
- android:background="@android:drawable/divider_horizontal_dark"
- />
-
- <TextView
- android:id="@+id/status1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/divider"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="6dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?android:attr/textColorSecondary"
- android:drawablePadding="4dip"
- />
-
- <TextView
- android:id="@+id/status2"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/status1"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="6dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?android:attr/textColorSecondary"
- android:drawablePadding="4dip"
- />
-
- <TextView
- android:id="@+id/screenLocked"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/status2"
- android:layout_centerHorizontal="true"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?android:attr/textColorSecondary"
- android:gravity="center"
- android:layout_marginTop="12dip"
- />
-
- <!-- By having the rotary selector hang from the top, we get a layout more
- robust for different screen sizes. On hvga, the widget should be flush with the bottom.-->
- <com.android.internal.widget.RotarySelector
- android:id="@+id/rotary"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_centerHorizontal="true"
- android:layout_alignParentTop="true"
- android:layout_marginTop="286dip"
- />
-
- <!-- emergency call button shown when sim is missing or PUKd -->
- <Button
- android:id="@+id/emergencyCallButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/screenLocked"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="24dip"
- android:drawableLeft="@drawable/ic_emergency"
- android:drawablePadding="8dip"
- />
-
-</RelativeLayout>
-
-</FrameLayout>
diff --git a/core/res/res/layout/keyguard_screen_rotary_unlock_land.xml b/core/res/res/layout/keyguard_screen_rotary_unlock_land.xml
deleted file mode 100644
index 898aea6..0000000
--- a/core/res/res/layout/keyguard_screen_rotary_unlock_land.xml
+++ /dev/null
@@ -1,145 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2009, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License")
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- This is the general lock screen which shows information about the
- state of the device, as well as instructions on how to get past it
- depending on the state of the device.-->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:rotaryunlock="http://schemas.android.com/apk/res/com.android.rotaryunlock"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:id="@+id/root"
- >
-<LinearLayout
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="#70000000"
- >
-
- <!-- left side -->
- <RelativeLayout
- android:orientation="vertical"
- android:layout_width="0dip"
- android:layout_height="match_parent"
- android:layout_weight="1.0"
- android:gravity="center_horizontal"
- >
-
- <TextView
- android:id="@+id/carrier"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="20dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?android:attr/textColorSecondary"
- />
-
- <TextView
- android:id="@+id/time"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/carrier"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="25dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textSize="55sp"
- />
-
- <TextView
- android:id="@+id/date"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/time"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="-12dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- />
-
- <View
- android:id="@+id/divider"
- android:layout_width="match_parent"
- android:layout_height="1dip"
- android:layout_marginTop="10dip"
- android:layout_below="@id/date"
- android:background="@android:drawable/divider_horizontal_dark"
- />
-
- <TextView
- android:id="@+id/status1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/divider"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="6dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?android:attr/textColorSecondary"
- android:drawablePadding="4dip"
- />
-
- <TextView
- android:id="@+id/status2"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/status1"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="6dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?android:attr/textColorSecondary"
- android:drawablePadding="4dip"
- />
-
- <TextView
- android:id="@+id/screenLocked"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/status2"
- android:layout_centerHorizontal="true"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?android:attr/textColorSecondary"
- android:gravity="center"
- android:layout_marginTop="12dip"
- />
- <!-- emergency call button shown when sim is missing or PUKd -->
- <Button
- android:id="@+id/emergencyCallButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/screenLocked"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="24dip"
- android:drawableLeft="@drawable/ic_emergency"
- android:drawablePadding="8dip"
- />
- </RelativeLayout>
-
-
- <!-- right side -->
- <com.android.internal.widget.RotarySelector
- android:id="@+id/rotary"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:orientation="vertical"
- />
-
-
-</LinearLayout>
-</FrameLayout>
\ No newline at end of file
diff --git a/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml b/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml
index dad994f..1f7f8f7 100644
--- a/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml
+++ b/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml
@@ -68,170 +68,14 @@
</LinearLayout>
- <!-- Keypad section -->
- <LinearLayout android:id="@+id/keyPad"
- android:layout_width="match_parent"
+ <include
+ android:id="@+id/keyPad"
+ layout="@android:layout/twelve_key_entry"
+ android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/topDisplayGroup"
android:layout_marginTop="10dip"
- android:orientation="vertical">
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="64dip"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:orientation="horizontal">
-
- <Button android:id="@+id/one"
- android:layout_width="0sp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textStyle="bold"
- />
-
- <Button android:id="@+id/two"
- android:layout_width="0sp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textStyle="bold"
- />
-
- <Button android:id="@+id/three"
- android:layout_width="0sp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textStyle="bold"
- />
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="64dip"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:orientation="horizontal">
-
- <Button android:id="@+id/four"
- android:layout_width="0sp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textStyle="bold"
- />
-
- <Button android:id="@+id/five"
- android:layout_width="0sp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textStyle="bold"
- />
-
- <Button android:id="@+id/six"
- android:layout_width="0sp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textStyle="bold"
- />
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="64dip"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:orientation="horizontal">
-
- <Button android:id="@+id/seven"
- android:layout_width="0sp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textStyle="bold"
- />
-
- <Button android:id="@+id/eight"
- android:layout_width="0sp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textStyle="bold"
- />
-
- <Button android:id="@+id/nine"
- android:layout_width="0sp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textStyle="bold"
- />
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="64dip"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:orientation="horizontal">
-
- <Button android:id="@+id/ok"
- android:layout_width="0sp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textStyle="bold"
- android:text="@android:string/ok"
- />
-
- <Button android:id="@+id/zero"
- android:layout_width="0sp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textStyle="bold"
- />
-
- <Button android:id="@+id/cancel"
- android:layout_width="0sp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textStyle="bold"
- android:text="@android:string/cancel"
- />
- </LinearLayout>
-
- <!-- end keypad -->
- </LinearLayout>
+ />
<!-- spacer below keypad -->
<View
diff --git a/core/res/res/layout/twelve_key_entry.xml b/core/res/res/layout/twelve_key_entry.xml
new file mode 100644
index 0000000..46301cd
--- /dev/null
+++ b/core/res/res/layout/twelve_key_entry.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This is not a standalone element it can be included into apps that need 12-key input -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="64dip"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:orientation="horizontal">
+
+ <Button android:id="@+id/one"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textStyle="bold"
+ />
+
+ <Button android:id="@+id/two"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textStyle="bold"
+ />
+
+ <Button android:id="@+id/three"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textStyle="bold"
+ />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="64dip"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:orientation="horizontal">
+
+ <Button android:id="@+id/four"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textStyle="bold"
+ />
+
+ <Button android:id="@+id/five"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textStyle="bold"
+ />
+
+ <Button android:id="@+id/six"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textStyle="bold"
+ />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="64dip"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:orientation="horizontal">
+
+ <Button android:id="@+id/seven"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textStyle="bold"
+ />
+
+ <Button android:id="@+id/eight"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textStyle="bold"
+ />
+
+ <Button android:id="@+id/nine"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textStyle="bold"
+ />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="64dip"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:orientation="horizontal">
+
+ <Button android:id="@+id/ok"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textStyle="bold"
+ android:text="@android:string/ok"
+ />
+
+ <Button android:id="@+id/zero"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textStyle="bold"
+ />
+
+ <Button android:id="@+id/cancel"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textStyle="bold"
+ android:text="@android:string/cancel"
+ />
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index 66f0e82..53bb586 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -131,6 +131,7 @@
the first component from this list which is found to be installed is set as the
preferred activity. -->
<string-array name="default_web_search_providers">
+ <item>com.android.quicksearchbox/com.android.googlesearch.GoogleSearch</item>
<item>com.google.android.providers.enhancedgooglesearch/.Launcher</item>
<item>com.android.googlesearch/.GoogleSearch</item>
<item>com.android.websearch/.Search.1</item>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 047115c..539db83 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1325,10 +1325,14 @@
<!-- Attbution of a contact status update, when the time of update is known -->
<string name="contact_status_update_attribution_with_date"><xliff:g id="date" example="3 hours ago">%1$s</xliff:g> via <xliff:g id="source" example="Google Talk">%2$s</xliff:g></string>
- <!-- Instructions telling the user to enter their pin to unlock the keyguard.
+ <!-- Instructions telling the user to enter their SIM PIN to unlock the keyguard.
Displayed in one line in a large font. -->
<string name="keyguard_password_enter_pin_code">Enter PIN code</string>
+ <!-- Instructions telling the user to enter their PIN password to unlock the keyguard.
+ Displayed in one line in a large font. -->
+ <string name="keyguard_password_enter_password_code">Enter password to unlock</string>
+
<!-- Instructions telling the user that they entered the wrong pin while trying
to unlock the keyguard. Displayed in one line in a large font. -->
<string name="keyguard_password_wrong_pin_code">Incorrect PIN code!</string>
diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java
index 115dd62..7d100eb 100644
--- a/graphics/java/android/renderscript/Allocation.java
+++ b/graphics/java/android/renderscript/Allocation.java
@@ -39,6 +39,10 @@
mType = t;
}
+ public Type getType() {
+ return mType;
+ }
+
public void uploadToTexture(int baseMipLevel) {
mRS.validate();
mRS.validateSurface();
diff --git a/graphics/java/android/renderscript/Program.java b/graphics/java/android/renderscript/Program.java
index 9d70cb2..1614ec5 100644
--- a/graphics/java/android/renderscript/Program.java
+++ b/graphics/java/android/renderscript/Program.java
@@ -111,12 +111,13 @@
mOutputs[mOutputCount++] = e;
}
- public void addConstant(Type t) throws IllegalStateException {
+ public int addConstant(Type t) throws IllegalStateException {
// Should check for consistant and non-conflicting names...
if(mConstantCount >= MAX_CONSTANT) {
throw new IllegalArgumentException("Max input count exceeded.");
}
- mConstants[mConstantCount++] = t;
+ mConstants[mConstantCount] = t;
+ return mConstantCount++;
}
public void setTextureCount(int count) throws IllegalArgumentException {
diff --git a/include/media/mediametadataretriever.h b/include/media/mediametadataretriever.h
index cfc205c..113c452 100644
--- a/include/media/mediametadataretriever.h
+++ b/include/media/mediametadataretriever.h
@@ -53,6 +53,7 @@
METADATA_KEY_VIDEO_HEIGHT = 19,
METADATA_KEY_VIDEO_WIDTH = 20,
METADATA_KEY_WRITER = 21,
+ METADATA_KEY_MIMETYPE = 22,
// Add more here...
};
diff --git a/include/media/stagefright/DataSource.h b/include/media/stagefright/DataSource.h
index b843cd9..f88666a 100644
--- a/include/media/stagefright/DataSource.h
+++ b/include/media/stagefright/DataSource.h
@@ -31,6 +31,8 @@
class DataSource : public RefBase {
public:
+ static sp<DataSource> CreateFromURI(const char *uri);
+
DataSource() {}
virtual status_t initCheck() const = 0;
diff --git a/include/media/stagefright/MediaExtractor.h b/include/media/stagefright/MediaExtractor.h
index d56d4b3..4bc996e 100644
--- a/include/media/stagefright/MediaExtractor.h
+++ b/include/media/stagefright/MediaExtractor.h
@@ -43,6 +43,10 @@
virtual sp<MetaData> getTrackMetaData(
size_t index, uint32_t flags = 0) = 0;
+ // Return container specific meta-data. The default implementation
+ // returns an empty metadata object.
+ virtual sp<MetaData> getMetaData();
+
protected:
MediaExtractor() {}
virtual ~MediaExtractor() {}
diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h
index c6ac6c2..2bc2734 100644
--- a/include/media/stagefright/MetaData.h
+++ b/include/media/stagefright/MetaData.h
@@ -48,6 +48,18 @@
kKeyBufferID = 'bfID',
kKeyMaxInputSize = 'inpS',
kKeyThumbnailTime = 'thbT', // int64_t (usecs)
+
+ kKeyAlbum = 'albu', // cstring
+ kKeyArtist = 'arti', // cstring
+ kKeyComposer = 'comp', // cstring
+ kKeyGenre = 'genr', // cstring
+ kKeyTitle = 'titl', // cstring
+ kKeyYear = 'year', // cstring
+ kKeyAlbumArt = 'albA', // compressed image data
+ kKeyAlbumArtMIME = 'alAM', // cstring
+ kKeyAuthor = 'auth', // cstring
+ kKeyCDTrackNumber = 'cdtr', // cstring
+ kKeyDate = 'date', // cstring
};
enum {
diff --git a/libs/rs/rsProgram.cpp b/libs/rs/rsProgram.cpp
index b7639be..656a3c3 100644
--- a/libs/rs/rsProgram.cpp
+++ b/libs/rs/rsProgram.cpp
@@ -95,7 +95,9 @@
Program::~Program()
{
- bindAllocation(NULL);
+ for (uint32_t ct=0; ct < MAX_UNIFORMS; ct++) {
+ bindAllocation(NULL, ct);
+ }
delete[] mInputElements;
delete[] mOutputElements;
@@ -106,15 +108,15 @@
}
-void Program::bindAllocation(Allocation *alloc)
+void Program::bindAllocation(Allocation *alloc, uint32_t slot)
{
- if (mConstants.get() == alloc) {
+ if (mConstants[slot].get() == alloc) {
return;
}
- if (mConstants.get()) {
- mConstants.get()->removeProgramToDirty(this);
+ if (mConstants[slot].get()) {
+ mConstants[slot].get()->removeProgramToDirty(this);
}
- mConstants.set(alloc);
+ mConstants[slot].set(alloc);
if (alloc) {
alloc->addProgramToDirty(this);
}
@@ -239,7 +241,7 @@
void rsi_ProgramBindConstants(Context *rsc, RsProgram vp, uint32_t slot, RsAllocation constants)
{
Program *p = static_cast<Program *>(vp);
- p->bindAllocation(static_cast<Allocation *>(constants));
+ p->bindAllocation(static_cast<Allocation *>(constants), slot);
}
void rsi_ProgramBindTexture(Context *rsc, RsProgram vpf, uint32_t slot, RsAllocation a)
diff --git a/libs/rs/rsProgram.h b/libs/rs/rsProgram.h
index 4bb7802..a34e89f 100644
--- a/libs/rs/rsProgram.h
+++ b/libs/rs/rsProgram.h
@@ -39,7 +39,7 @@
const uint32_t * params, uint32_t paramLength);
virtual ~Program();
- void bindAllocation(Allocation *);
+ void bindAllocation(Allocation *, uint32_t slot);
virtual void createShader();
bool isUserProgram() const {return mUserShader.size() > 0;}
@@ -69,7 +69,7 @@
uint32_t mOutputCount;
uint32_t mConstantCount;
- ObjectBaseRef<Allocation> mConstants;
+ ObjectBaseRef<Allocation> mConstants[MAX_UNIFORMS];
mutable bool mDirty;
String8 mShader;
diff --git a/libs/rs/rsProgramVertex.cpp b/libs/rs/rsProgramVertex.cpp
index 8e59bc2..cf6d7fc 100644
--- a/libs/rs/rsProgramVertex.cpp
+++ b/libs/rs/rsProgramVertex.cpp
@@ -69,7 +69,7 @@
}
state->mLast.set(this);
- const float *f = static_cast<const float *>(mConstants->getPtr());
+ const float *f = static_cast<const float *>(mConstants[0]->getPtr());
glMatrixMode(GL_TEXTURE);
if (mTextureMatrixEnable) {
@@ -116,16 +116,36 @@
{
mShader.setTo("");
- for (uint32_t ct=0; ct < mUniformCount; ct++) {
- mShader.append("uniform mat4 ");
- mShader.append(mUniformNames[ct]);
- mShader.append(";\n");
- }
-
mShader.append("varying vec4 varColor;\n");
mShader.append("varying vec4 varTex0;\n");
if (mUserShader.length() > 1) {
+ mShader.append("uniform mat4 ");
+ mShader.append(mUniformNames[0]);
+ mShader.append(";\n");
+
+ for (uint32_t ct=0; ct < mConstantCount; ct++) {
+ const Element *e = mConstantTypes[ct]->getElement();
+ for (uint32_t field=0; field < e->getFieldCount(); field++) {
+ const Element *f = e->getField(field);
+
+ // Cannot be complex
+ rsAssert(!f->getFieldCount());
+ switch(f->getComponent().getVectorSize()) {
+ case 1: mShader.append("uniform float UNI_"); break;
+ case 2: mShader.append("uniform vec2 UNI_"); break;
+ case 3: mShader.append("uniform vec3 UNI_"); break;
+ case 4: mShader.append("uniform vec4 UNI_"); break;
+ default:
+ rsAssert(0);
+ }
+
+ mShader.append(e->getFieldName(field));
+ mShader.append(";\n");
+ }
+ }
+
+
for (uint32_t ct=0; ct < mInputCount; ct++) {
const Element *e = mInputElements[ct].get();
for (uint32_t field=0; field < e->getFieldCount(); field++) {
@@ -148,6 +168,12 @@
}
mShader.append(mUserShader);
} else {
+ for (uint32_t ct=0; ct < mUniformCount; ct++) {
+ mShader.append("uniform mat4 ");
+ mShader.append(mUniformNames[ct]);
+ mShader.append(";\n");
+ }
+
for (uint32_t ct=VertexArray::POSITION; ct < mAttribCount; ct++) {
mShader.append("attribute vec4 ");
mShader.append(mAttribNames[ct]);
@@ -155,12 +181,12 @@
}
mShader.append("void main() {\n");
- mShader.append(" gl_Position = uni_MVP * ATTRIB_Position;\n");
+ mShader.append(" gl_Position = UNI_MVP * ATTRIB_Position;\n");
mShader.append(" gl_PointSize = ATTRIB_PointSize.x;\n");
mShader.append(" varColor = ATTRIB_Color;\n");
if (mTextureMatrixEnable) {
- mShader.append(" varTex0 = uni_TexMatrix * ATTRIB_Texture;\n");
+ mShader.append(" varTex0 = UNI_TexMatrix * ATTRIB_Texture;\n");
} else {
mShader.append(" varTex0 = ATTRIB_Texture;\n");
}
@@ -180,7 +206,7 @@
glVertexAttrib4f(1, state->color[0], state->color[1], state->color[2], state->color[3]);
- const float *f = static_cast<const float *>(mConstants->getPtr());
+ const float *f = static_cast<const float *>(mConstants[0]->getPtr());
Matrix mvp;
mvp.load(&f[RS_PROGRAM_VERTEX_PROJECTION_OFFSET]);
@@ -194,6 +220,54 @@
&f[RS_PROGRAM_VERTEX_TEXTURE_OFFSET]);
}
+ uint32_t uidx = 1;
+ for (uint32_t ct=0; ct < mConstantCount; ct++) {
+ Allocation *alloc = mConstants[ct+1].get();
+ if (!alloc) {
+ continue;
+ }
+
+ const uint8_t *data = static_cast<const uint8_t *>(alloc->getPtr());
+ const Element *e = mConstantTypes[ct]->getElement();
+ for (uint32_t field=0; field < e->getFieldCount(); field++) {
+ const Element *f = e->getField(field);
+ uint32_t offset = e->getFieldOffsetBytes(field);
+ int32_t slot = sc->vtxUniformSlot(uidx);
+
+ const float *fd = reinterpret_cast<const float *>(&data[offset]);
+
+ //LOGE("Uniform slot=%i, offset=%i, constant=%i, field=%i, uidx=%i", slot, offset, ct, field, uidx);
+ if (slot >= 0) {
+ switch(f->getComponent().getVectorSize()) {
+ case 1:
+ //LOGE("Uniform 1 = %f", fd[0]);
+ glUniform1fv(slot, 1, fd);
+ break;
+ case 2:
+ //LOGE("Uniform 2 = %f %f", fd[0], fd[1]);
+ glUniform2fv(slot, 1, fd);
+ break;
+ case 3:
+ //LOGE("Uniform 3 = %f %f %f", fd[0], fd[1], fd[2]);
+ glUniform3fv(slot, 1, fd);
+ break;
+ case 4:
+ //LOGE("Uniform 4 = %f %f %f %f", fd[0], fd[1], fd[2], fd[3]);
+ glUniform4fv(slot, 1, fd);
+ break;
+ default:
+ rsAssert(0);
+ }
+ }
+ uidx ++;
+ }
+ }
+
+ for (uint32_t ct=0; ct < mConstantCount; ct++) {
+ uint32_t glSlot = sc->vtxUniformSlot(ct + 1);
+
+ }
+
state->mLast.set(this);
rsc->checkError("ProgramVertex::setupGL2");
}
@@ -208,46 +282,46 @@
void ProgramVertex::setProjectionMatrix(const rsc_Matrix *m) const
{
- float *f = static_cast<float *>(mConstants->getPtr());
+ float *f = static_cast<float *>(mConstants[0]->getPtr());
memcpy(&f[RS_PROGRAM_VERTEX_PROJECTION_OFFSET], m, sizeof(rsc_Matrix));
mDirty = true;
}
void ProgramVertex::setModelviewMatrix(const rsc_Matrix *m) const
{
- float *f = static_cast<float *>(mConstants->getPtr());
+ float *f = static_cast<float *>(mConstants[0]->getPtr());
memcpy(&f[RS_PROGRAM_VERTEX_MODELVIEW_OFFSET], m, sizeof(rsc_Matrix));
mDirty = true;
}
void ProgramVertex::setTextureMatrix(const rsc_Matrix *m) const
{
- float *f = static_cast<float *>(mConstants->getPtr());
+ float *f = static_cast<float *>(mConstants[0]->getPtr());
memcpy(&f[RS_PROGRAM_VERTEX_TEXTURE_OFFSET], m, sizeof(rsc_Matrix));
mDirty = true;
}
void ProgramVertex::transformToScreen(const Context *rsc, float *v4out, const float *v3in) const
{
- float *f = static_cast<float *>(mConstants->getPtr());
+ float *f = static_cast<float *>(mConstants[0]->getPtr());
Matrix mvp;
mvp.loadMultiply((Matrix *)&f[RS_PROGRAM_VERTEX_MODELVIEW_OFFSET],
(Matrix *)&f[RS_PROGRAM_VERTEX_PROJECTION_OFFSET]);
mvp.vectorMultiply(v4out, v3in);
}
-void ProgramVertex::initAddUserAttrib(const Element *e)
+void ProgramVertex::initAddUserElement(const Element *e, String8 *names, uint32_t *count, const char *prefix)
{
rsAssert(e->getFieldCount());
for (uint32_t ct=0; ct < e->getFieldCount(); ct++) {
const Element *ce = e->getField(ct);
if (ce->getFieldCount()) {
- initAddUserAttrib(ce);
+ initAddUserElement(ce, names, count, prefix);
} else {
- String8 tmp("ATTRIB_");
+ String8 tmp(prefix);
tmp.append(e->getFieldName(ct));
- mAttribNames[mAttribCount].setTo(tmp.string());
- mAttribCount++;
+ names[*count].setTo(tmp.string());
+ (*count)++;
}
}
}
@@ -257,7 +331,13 @@
if (mUserShader.size() > 0) {
mAttribCount = 0;
for (uint32_t ct=0; ct < mInputCount; ct++) {
- initAddUserAttrib(mInputElements[ct].get());
+ initAddUserElement(mInputElements[ct].get(), mAttribNames, &mAttribCount, "ATTRIB_");
+ }
+
+ mUniformCount = 1;
+ mUniformNames[0].setTo("UNI_MVP");
+ for (uint32_t ct=0; ct < mConstantCount; ct++) {
+ initAddUserElement(mConstantTypes[ct]->getElement(), mUniformNames, &mUniformCount, "UNI_");
}
} else {
mAttribCount = 5;
@@ -266,11 +346,11 @@
mAttribNames[2].setTo("ATTRIB_Normal");
mAttribNames[3].setTo("ATTRIB_PointSize");
mAttribNames[4].setTo("ATTRIB_Texture");
- }
- mUniformCount = 2;
- mUniformNames[0].setTo("uni_MVP");
- mUniformNames[1].setTo("uni_TexMatrix");
+ mUniformCount = 2;
+ mUniformNames[0].setTo("UNI_MVP");
+ mUniformNames[1].setTo("UNI_TexMatrix");
+ }
createShader();
}
@@ -299,7 +379,7 @@
mDefaultAlloc.set(alloc);
mDefault.set(pv);
pv->init(rsc);
- pv->bindAllocation(alloc);
+ pv->bindAllocation(alloc, 0);
color[0] = 1.f;
color[1] = 1.f;
diff --git a/libs/rs/rsProgramVertex.h b/libs/rs/rsProgramVertex.h
index dcb988c..28554cc 100644
--- a/libs/rs/rsProgramVertex.h
+++ b/libs/rs/rsProgramVertex.h
@@ -61,7 +61,7 @@
bool mTextureMatrixEnable;
private:
- void initAddUserAttrib(const Element *e);
+ void initAddUserElement(const Element *e, String8 *names, uint32_t *count, const char *prefix);
};
diff --git a/libs/rs/rsScriptC_Lib.cpp b/libs/rs/rsScriptC_Lib.cpp
index 8a0ab71..3ba9cee 100644
--- a/libs/rs/rsScriptC_Lib.cpp
+++ b/libs/rs/rsScriptC_Lib.cpp
@@ -973,6 +973,13 @@
rsi_AllocationUploadToBufferObject(rsc, va);
}
+static void SC_syncToGL(RsAllocation va)
+{
+ GET_TLS();
+ Allocation *a = static_cast<Allocation *>(va);
+
+}
+
static void SC_ClearColor(float r, float g, float b, float a)
{
//LOGE("c %f %f %f %f", r, g, b, a);
@@ -1321,6 +1328,9 @@
{ "uploadToBufferObject", (void *)&SC_uploadToBufferObject,
"void", "(int)" },
+ { "syncToGL", (void *)&SC_syncToGL,
+ "void", "(int)" },
+
{ "colorFloatRGBAtoUNorm8", (void *)&SC_colorFloatRGBAtoUNorm8,
"int", "(float, float, float, float)" },
{ "colorFloatRGBto565", (void *)&SC_colorFloatRGBAto565,
diff --git a/libs/rs/rsShaderCache.cpp b/libs/rs/rsShaderCache.cpp
index 0d9863de..8ac2487 100644
--- a/libs/rs/rsShaderCache.cpp
+++ b/libs/rs/rsShaderCache.cpp
@@ -132,7 +132,7 @@
LOGV("vtx U, %s = %d\n", vtx->getUniformName(ct).string(), e->mVtxUniformSlots[ct]);
}
}
- for (uint32_t ct=0; ct < vtx->getUniformCount(); ct++) {
+ for (uint32_t ct=0; ct < frag->getUniformCount(); ct++) {
e->mFragUniformSlots[ct] = glGetUniformLocation(pgm, frag->getUniformName(ct));
if (rsc->props.mLogShaders) {
LOGV("frag U, %s = %d\n", frag->getUniformName(ct).string(), e->mFragUniformSlots[ct]);
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index cecf4f8..04f8b5d 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -255,5 +255,6 @@
public static final int METADATA_KEY_VIDEO_HEIGHT = 19;
public static final int METADATA_KEY_VIDEO_WIDTH = 20;
public static final int METADATA_KEY_WRITER = 21;
+ public static final int METADATA_KEY_MIMETYPE = 22;
// Add more here...
}
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index af57a4c..6563caa 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -1258,22 +1258,24 @@
static const int NUMVIZBUF = 32;
static const int VIZBUFFRAMES = 1024;
-static const int TOTALBUFTIMEMSEC = NUMVIZBUF * VIZBUFFRAMES * 1000 / 44100;
+static const int BUFTIMEMSEC = NUMVIZBUF * VIZBUFFRAMES * 1000 / 44100;
+static const int TOTALBUFTIMEMSEC = NUMVIZBUF * BUFTIMEMSEC;
static bool gotMem = false;
+static sp<MemoryHeapBase> heap;
static sp<MemoryBase> mem[NUMVIZBUF];
-static uint64_t timeStamp[NUMVIZBUF];
+static uint64_t endTime;
static uint64_t lastReadTime;
static uint64_t lastWriteTime;
static int writeIdx = 0;
static void allocVizBufs() {
if (!gotMem) {
+ heap = new MemoryHeapBase(NUMVIZBUF * VIZBUFFRAMES * 2, 0, "snooper");
for (int i=0;i<NUMVIZBUF;i++) {
- sp<MemoryHeapBase> heap = new MemoryHeapBase(VIZBUFFRAMES*2, 0, "snooper");
- mem[i] = new MemoryBase(heap, 0, heap->getSize());
- timeStamp[i] = 0;
+ mem[i] = new MemoryBase(heap, VIZBUFFRAMES * 2 * i, VIZBUFFRAMES * 2);
}
+ endTime = 0;
gotMem = true;
}
}
@@ -1290,68 +1292,48 @@
allocVizBufs();
- lastReadTime = uptimeMillis() + 100; // account for renderer delay (we shouldn't be doing this here)
+ lastReadTime = uptimeMillis();
// if there is no recent buffer (yet), just return empty handed
if (lastWriteTime + TOTALBUFTIMEMSEC < lastReadTime) {
- //LOGI("@@@@ no audio data to look at yet");
+ //LOGI("@@@@ no audio data to look at yet: %d + %d < %d", (int)lastWriteTime, TOTALBUFTIMEMSEC, (int)lastReadTime);
return NULL;
}
- char buf[200];
-
- int closestIdx = -1;
- uint32_t closestTime = 0x7ffffff;
-
- for (int i = 0; i < NUMVIZBUF; i++) {
- uint64_t tsi = timeStamp[i];
- uint64_t diff = tsi > lastReadTime ? tsi - lastReadTime : lastReadTime - tsi;
- if (diff < closestTime) {
- closestIdx = i;
- closestTime = diff;
- }
+ int timedelta = endTime - lastReadTime;
+ if (timedelta < 0) timedelta = 0;
+ int framedelta = timedelta * 44100 / 1000;
+ int headIdx = (writeIdx - framedelta) / VIZBUFFRAMES - 1;
+ while (headIdx < 0) {
+ headIdx += NUMVIZBUF;
}
-
-
- if (closestIdx >= 0) {
- //LOGI("@@@ return buffer %d, %d/%d", closestIdx, uint32_t(lastReadTime), uint32_t(timeStamp[closestIdx]));
- return mem[closestIdx];
- }
-
- // we won't get here, since we either bailed out early, or got a buffer
- LOGD("Didn't expect to be here");
- return NULL;
+ return mem[headIdx];
}
-static void storeVizBuf(const void *data, int len, uint64_t time) {
- // Copy the data in to the visualizer buffer
- // Assume a 16 bit stereo source for now.
- short *viz = (short*)mem[writeIdx]->pointer();
- short *src = (short*)data;
- for (int i = 0; i < VIZBUFFRAMES; i++) {
- // Degrade quality by mixing to mono and clearing the lowest 3 bits.
- // This should still be good enough for a visualization
- *viz++ = ((int(src[0]) + int(src[1])) >> 1) & ~0x7;
- src += 2;
- }
- timeStamp[writeIdx++] = time;
- if (writeIdx >= NUMVIZBUF) {
- writeIdx = 0;
- }
-}
-
+// Append the data to the vizualization buffer
static void makeVizBuffers(const char *data, int len, uint64_t time) {
allocVizBufs();
uint64_t startTime = time;
const int frameSize = 4; // 16 bit stereo sample is 4 bytes
- while (len >= VIZBUFFRAMES * frameSize) {
- storeVizBuf(data, len, time);
- data += VIZBUFFRAMES * frameSize;
- len -= VIZBUFFRAMES * frameSize;
- time += 1000 * VIZBUFFRAMES / 44100;
+ int offset = writeIdx;
+ int maxoff = heap->getSize() / 2; // in shorts
+ short *base = (short*)heap->getBase();
+ short *src = (short*)data;
+ while (len > 0) {
+
+ // Degrade quality by mixing to mono and clearing the lowest 3 bits.
+ // This should still be good enough for a visualization
+ base[offset++] = ((int(src[0]) + int(src[1])) >> 1) & ~0x7;
+ src += 2;
+ len -= frameSize;
+ if (offset >= maxoff) {
+ offset = 0;
+ }
}
+ writeIdx = offset;
+ endTime = time + (len / frameSize) / 44;
//LOGI("@@@ stored buffers from %d to %d", uint32_t(startTime), uint32_t(time));
}
@@ -1509,30 +1491,35 @@
}
}
+void MediaPlayerService::AudioOutput::snoopWrite(const void* buffer, size_t size) {
+ // Only make visualization buffers if anyone recently requested visualization data
+ uint64_t now = uptimeMillis();
+ if (lastReadTime + TOTALBUFTIMEMSEC >= now) {
+ // Based on the current play counter, the number of frames written and
+ // the current real time we can calculate the approximate real start
+ // time of the buffer we're about to write.
+ uint32_t pos;
+ mTrack->getPosition(&pos);
+
+ // we're writing ahead by this many frames:
+ int ahead = mNumFramesWritten - pos;
+ //LOGI("@@@ written: %d, playpos: %d, latency: %d", mNumFramesWritten, pos, mTrack->latency());
+ // which is this many milliseconds, assuming 44100 Hz:
+ ahead /= 44;
+
+ makeVizBuffers((const char*)buffer, size, now + ahead + mTrack->latency());
+ lastWriteTime = now;
+ }
+}
+
+
ssize_t MediaPlayerService::AudioOutput::write(const void* buffer, size_t size)
{
LOG_FATAL_IF(mCallback != NULL, "Don't call write if supplying a callback.");
//LOGV("write(%p, %u)", buffer, size);
if (mTrack) {
- // Only make visualization buffers if anyone recently requested visualization data
- uint64_t now = uptimeMillis();
- if (lastReadTime + TOTALBUFTIMEMSEC >= now) {
- // Based on the current play counter, the number of frames written and
- // the current real time we can calculate the approximate real start
- // time of the buffer we're about to write.
- uint32_t pos;
- mTrack->getPosition(&pos);
-
- // we're writing ahead by this many frames:
- int ahead = mNumFramesWritten - pos;
- //LOGI("@@@ written: %d, playpos: %d, latency: %d", mNumFramesWritten, pos, mTrack->latency());
- // which is this many milliseconds, assuming 44100 Hz:
- ahead /= 44;
-
- makeVizBuffers((const char*)buffer, size, now + ahead + mTrack->latency());
- lastWriteTime = now;
- }
+ snoopWrite(buffer, size);
ssize_t ret = mTrack->write(buffer, size);
mNumFramesWritten += ret / 4; // assume 16 bit stereo
return ret;
@@ -1580,6 +1567,7 @@
// static
void MediaPlayerService::AudioOutput::CallbackWrapper(
int event, void *cookie, void *info) {
+ //LOGV("callbackwrapper");
if (event != AudioTrack::EVENT_MORE_DATA) {
return;
}
@@ -1589,6 +1577,7 @@
(*me->mCallback)(
me, buffer->raw, buffer->size, me->mCallbackCookie);
+ me->snoopWrite(buffer->raw, buffer->size);
}
#undef LOG_TAG
diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h
index 1c90cf9..d1206b4 100644
--- a/media/libmediaplayerservice/MediaPlayerService.h
+++ b/media/libmediaplayerservice/MediaPlayerService.h
@@ -113,6 +113,7 @@
public: // visualization hack support
uint32_t mNumFramesWritten;
+ void snoopWrite(const void*, size_t);
};
class AudioCache : public MediaPlayerBase::AudioSink
diff --git a/media/libstagefright/AMRExtractor.cpp b/media/libstagefright/AMRExtractor.cpp
index bdd7550..3193d5e 100644
--- a/media/libstagefright/AMRExtractor.cpp
+++ b/media/libstagefright/AMRExtractor.cpp
@@ -128,6 +128,18 @@
AMRExtractor::~AMRExtractor() {
}
+sp<MetaData> AMRExtractor::getMetaData() {
+ sp<MetaData> meta = new MetaData;
+
+ if (mInitCheck != OK) {
+ return meta;
+ }
+
+ meta->setCString(kKeyMIMEType, mIsWide ? "audio/amr-wb" : "audio/amr");
+
+ return meta;
+}
+
size_t AMRExtractor::countTracks() {
return mInitCheck == OK ? 1 : 0;
}
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 30b4506..e36e78c 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -44,14 +44,17 @@
LOCAL_C_INCLUDES:= \
$(JNI_H_INCLUDE) \
$(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \
- $(TOP)/external/opencore/android
+ $(TOP)/external/opencore/android \
+ $(TOP)/external/tremor/Tremor
LOCAL_SHARED_LIBRARIES := \
libbinder \
libmedia \
libutils \
libcutils \
- libui
+ libui \
+ libsonivox \
+ libvorbisidec
ifeq ($(BUILD_WITH_FULL_STAGEFRIGHT),true)
@@ -62,7 +65,8 @@
libstagefright_amrwbdec \
libstagefright_avcdec \
libstagefright_m4vh263dec \
- libstagefright_mp3dec
+ libstagefright_mp3dec \
+ libstagefright_id3
LOCAL_SHARED_LIBRARIES += \
libstagefright_amrnb_common \
diff --git a/media/libstagefright/AudioPlayer.cpp b/media/libstagefright/AudioPlayer.cpp
index 4280683..14842c0 100644
--- a/media/libstagefright/AudioPlayer.cpp
+++ b/media/libstagefright/AudioPlayer.cpp
@@ -133,13 +133,14 @@
if (mAudioSink.get() != NULL) {
mAudioSink->stop();
+ mAudioSink->close();
} else {
mAudioTrack->stop();
delete mAudioTrack;
mAudioTrack = NULL;
}
-
+
// Make sure to release any buffer we hold onto so that the
// source is able to stop().
if (mInputBuffer != NULL) {
@@ -150,7 +151,7 @@
}
mSource->stop();
-
+
mNumFramesPlayed = 0;
mPositionTimeMediaUs = -1;
mPositionTimeRealUs = -1;
@@ -259,7 +260,7 @@
mInputBuffer->set_range(mInputBuffer->range_offset() + copy,
mInputBuffer->range_length() - copy);
-
+
size_done += copy;
size_remaining -= copy;
}
diff --git a/media/libstagefright/DataSource.cpp b/media/libstagefright/DataSource.cpp
index 2a6dbc4..741e5e00 100644
--- a/media/libstagefright/DataSource.cpp
+++ b/media/libstagefright/DataSource.cpp
@@ -19,7 +19,10 @@
#include "include/MPEG4Extractor.h"
#include "include/WAVExtractor.h"
+#include <media/stagefright/CachingDataSource.h>
#include <media/stagefright/DataSource.h>
+#include <media/stagefright/FileSource.h>
+#include <media/stagefright/HTTPDataSource.h>
#include <media/stagefright/MediaErrors.h>
#include <utils/String8.h>
@@ -91,4 +94,24 @@
RegisterSniffer(SniffWAV);
}
+// static
+sp<DataSource> DataSource::CreateFromURI(const char *uri) {
+ sp<DataSource> source;
+ if (!strncasecmp("file://", uri, 7)) {
+ source = new FileSource(uri + 7);
+ } else if (!strncasecmp("http://", uri, 7)) {
+ source = new HTTPDataSource(uri);
+ source = new CachingDataSource(source, 64 * 1024, 10);
+ } else {
+ // Assume it's a filename.
+ source = new FileSource(uri);
+ }
+
+ if (source == NULL || source->initCheck() != OK) {
+ return NULL;
+ }
+
+ return source;
+}
+
} // namespace android
diff --git a/media/libstagefright/FileSource.cpp b/media/libstagefright/FileSource.cpp
index 37c2450..b6f1af2 100644
--- a/media/libstagefright/FileSource.cpp
+++ b/media/libstagefright/FileSource.cpp
@@ -58,7 +58,10 @@
}
int err = fseeko(mFile, offset + mOffset, SEEK_SET);
- CHECK(err != -1);
+ if (err < 0) {
+ LOGE("seek to %lld failed", offset + mOffset);
+ return UNKNOWN_ERROR;
+ }
return fread(data, 1, size, mFile);
}
diff --git a/media/libstagefright/MP3Extractor.cpp b/media/libstagefright/MP3Extractor.cpp
index b8e76fd..accd94d 100644
--- a/media/libstagefright/MP3Extractor.cpp
+++ b/media/libstagefright/MP3Extractor.cpp
@@ -20,6 +20,8 @@
#include "include/MP3Extractor.h"
+#include "include/ID3.h"
+
#include <media/stagefright/DataSource.h>
#include <media/stagefright/MediaBuffer.h>
#include <media/stagefright/MediaBufferGroup.h>
@@ -667,7 +669,7 @@
}
// Lost sync.
- LOGW("lost sync!\n");
+ LOGV("lost sync!\n");
off_t pos = mCurrentPos;
if (!Resync(mDataSource, mFixedHeader, &pos, NULL)) {
@@ -706,6 +708,69 @@
return OK;
}
+sp<MetaData> MP3Extractor::getMetaData() {
+ sp<MetaData> meta = new MetaData;
+
+ if (mFirstFramePos < 0) {
+ return meta;
+ }
+
+ meta->setCString(kKeyMIMEType, "audio/mpeg");
+
+ ID3 id3(mDataSource);
+
+ if (!id3.isValid()) {
+ return meta;
+ }
+
+ struct Map {
+ int key;
+ const char *tag1;
+ const char *tag2;
+ };
+ static const Map kMap[] = {
+ { kKeyAlbum, "TALB", "TAL" },
+ { kKeyArtist, "TPE1", "TP1" },
+ { kKeyComposer, "TCOM", "TCM" },
+ { kKeyGenre, "TCON", "TCO" },
+ { kKeyTitle, "TIT2", "TT2" },
+ { kKeyYear, "TYE", "TYER" },
+ { kKeyAuthor, "TXT", "TEXT" },
+ { kKeyCDTrackNumber, "TRK", "TRCK" },
+ };
+ static const size_t kNumMapEntries = sizeof(kMap) / sizeof(kMap[0]);
+
+ for (size_t i = 0; i < kNumMapEntries; ++i) {
+ ID3::Iterator *it = new ID3::Iterator(id3, kMap[i].tag1);
+ if (it->done()) {
+ delete it;
+ it = new ID3::Iterator(id3, kMap[i].tag2);
+ }
+
+ if (it->done()) {
+ delete it;
+ continue;
+ }
+
+ String8 s;
+ it->getString(&s);
+ delete it;
+
+ meta->setCString(kMap[i].key, s);
+ }
+
+ size_t dataSize;
+ String8 mime;
+ const void *data = id3.getAlbumArt(&dataSize, &mime);
+
+ if (data) {
+ meta->setData(kKeyAlbumArt, MetaData::TYPE_NONE, data, dataSize);
+ meta->setCString(kKeyAlbumArtMIME, mime.string());
+ }
+
+ return meta;
+}
+
bool SniffMP3(
const sp<DataSource> &source, String8 *mimeType, float *confidence) {
off_t pos = 0;
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index 143e8ee..07a5a82 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -66,6 +66,8 @@
uint32_t mCurrentSampleIndex;
bool mIsAVC;
+ size_t mNALLengthSize;
+
bool mStarted;
MediaBufferGroup *mGroup;
@@ -76,6 +78,8 @@
uint8_t *mSrcBuffer;
+ size_t parseNALSize(const uint8_t *data) const;
+
MPEG4Source(const MPEG4Source &);
MPEG4Source &operator=(const MPEG4Source &);
};
@@ -148,6 +152,7 @@
MPEG4Extractor::MPEG4Extractor(const sp<DataSource> &source)
: mDataSource(source),
mHaveMetadata(false),
+ mHasVideo(false),
mFirstTrack(NULL),
mLastTrack(NULL) {
}
@@ -163,6 +168,23 @@
mFirstTrack = mLastTrack = NULL;
}
+sp<MetaData> MPEG4Extractor::getMetaData() {
+ sp<MetaData> meta = new MetaData;
+
+ status_t err;
+ if ((err = readMetaData()) != OK) {
+ return meta;
+ }
+
+ if (mHasVideo) {
+ meta->setCString(kKeyMIMEType, "video/mp4");
+ } else {
+ meta->setCString(kKeyMIMEType, "audio/mp4");
+ }
+
+ return meta;
+}
+
size_t MPEG4Extractor::countTracks() {
status_t err;
if ((err = readMetaData()) != OK) {
@@ -231,7 +253,7 @@
status_t err;
while ((err = parseChunk(&offset, 0)) == OK) {
}
-
+
if (mHaveMetadata) {
return OK;
}
@@ -557,6 +579,8 @@
case FOURCC('s', '2', '6', '3'):
case FOURCC('a', 'v', 'c', '1'):
{
+ mHasVideo = true;
+
if (mHandlerType != FOURCC('v', 'i', 'd', 'e')) {
return ERROR_MALFORMED;
}
@@ -770,6 +794,7 @@
mSampleTable(sampleTable),
mCurrentSampleIndex(0),
mIsAVC(false),
+ mNALLengthSize(0),
mStarted(false),
mGroup(NULL),
mBuffer(NULL),
@@ -780,6 +805,21 @@
CHECK(success);
mIsAVC = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC);
+
+ if (mIsAVC) {
+ uint32_t type;
+ const void *data;
+ size_t size;
+ CHECK(format->findData(kKeyAVCC, &type, &data, &size));
+
+ const uint8_t *ptr = (const uint8_t *)data;
+
+ CHECK(size >= 7);
+ CHECK_EQ(ptr[0], 1); // configurationVersion == 1
+
+ // The number of bytes used to encode the length of a NAL unit.
+ mNALLengthSize = 1 + (ptr[4] & 3);
+ }
}
MPEG4Source::~MPEG4Source() {
@@ -837,6 +877,25 @@
return mFormat;
}
+size_t MPEG4Source::parseNALSize(const uint8_t *data) const {
+ switch (mNALLengthSize) {
+ case 1:
+ return *data;
+ case 2:
+ return U16_AT(data);
+ case 3:
+ return ((size_t)data[0] << 16) | U16_AT(&data[1]);
+ case 4:
+ return U32_AT(data);
+ }
+
+ // This cannot happen, mNALLengthSize springs to life by adding 1 to
+ // a 2-bit integer.
+ CHECK(!"Should not be here.");
+
+ return 0;
+}
+
status_t MPEG4Source::read(
MediaBuffer **out, const ReadOptions *options) {
CHECK(mStarted);
@@ -919,21 +978,27 @@
// Each NAL unit is split up into its constituent fragments and
// each one of them returned in its own buffer.
- CHECK(mBuffer->range_length() >= 2);
+ CHECK(mBuffer->range_length() >= mNALLengthSize);
const uint8_t *src =
(const uint8_t *)mBuffer->data() + mBuffer->range_offset();
- size_t nal_size = U16_AT(src);
+ size_t nal_size = parseNALSize(src);
+ if (mBuffer->range_length() < mNALLengthSize + nal_size) {
+ LOGE("incomplete NAL unit.");
- CHECK(mBuffer->range_length() >= 2 + nal_size);
+ mBuffer->release();
+ mBuffer = NULL;
+
+ return ERROR_MALFORMED;
+ }
MediaBuffer *clone = mBuffer->clone();
- clone->set_range(mBuffer->range_offset() + 2, nal_size);
+ clone->set_range(mBuffer->range_offset() + mNALLengthSize, nal_size);
mBuffer->set_range(
- mBuffer->range_offset() + 2 + nal_size,
- mBuffer->range_length() - 2 - nal_size);
+ mBuffer->range_offset() + mNALLengthSize + nal_size,
+ mBuffer->range_length() - mNALLengthSize - nal_size);
if (mBuffer->range_length() == 0) {
mBuffer->release();
@@ -960,12 +1025,18 @@
uint8_t *dstData = (uint8_t *)mBuffer->data();
size_t srcOffset = 0;
size_t dstOffset = 0;
+
while (srcOffset < size) {
- CHECK(srcOffset + 1 < size);
- size_t nalLength =
- (mSrcBuffer[srcOffset] << 8) | mSrcBuffer[srcOffset + 1];
- CHECK(srcOffset + 1 + nalLength < size);
- srcOffset += 2;
+ CHECK(srcOffset + mNALLengthSize <= size);
+ size_t nalLength = parseNALSize(&mSrcBuffer[srcOffset]);
+ srcOffset += mNALLengthSize;
+
+ if (srcOffset + nalLength > size) {
+ mBuffer->release();
+ mBuffer = NULL;
+
+ return ERROR_MALFORMED;
+ }
if (nalLength == 0) {
continue;
@@ -981,6 +1052,7 @@
srcOffset += nalLength;
dstOffset += nalLength;
}
+ CHECK_EQ(srcOffset, size);
mBuffer->set_range(0, dstOffset);
mBuffer->meta_data()->clear();
diff --git a/media/libstagefright/MediaExtractor.cpp b/media/libstagefright/MediaExtractor.cpp
index 9d3deb7..e46f00e 100644
--- a/media/libstagefright/MediaExtractor.cpp
+++ b/media/libstagefright/MediaExtractor.cpp
@@ -23,16 +23,18 @@
#include "include/MPEG4Extractor.h"
#include "include/WAVExtractor.h"
-#include <media/stagefright/CachingDataSource.h>
#include <media/stagefright/DataSource.h>
-#include <media/stagefright/FileSource.h>
-#include <media/stagefright/HTTPDataSource.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaExtractor.h>
+#include <media/stagefright/MetaData.h>
#include <utils/String8.h>
namespace android {
+sp<MetaData> MediaExtractor::getMetaData() {
+ return new MetaData;
+}
+
// static
sp<MediaExtractor> MediaExtractor::Create(
const sp<DataSource> &source, const char *mime) {
@@ -40,7 +42,7 @@
if (mime == NULL) {
float confidence;
if (!source->sniff(&tmp, &confidence)) {
- LOGE("FAILED to autodetect media content.");
+ LOGV("FAILED to autodetect media content.");
return NULL;
}
@@ -68,16 +70,7 @@
// static
sp<MediaExtractor> MediaExtractor::CreateFromURI(
const char *uri, const char *mime) {
- sp<DataSource> source;
- if (!strncasecmp("file://", uri, 7)) {
- source = new FileSource(uri + 7);
- } else if (!strncasecmp("http://", uri, 7)) {
- source = new HTTPDataSource(uri);
- source = new CachingDataSource(source, 64 * 1024, 10);
- } else {
- // Assume it's a filename.
- source = new FileSource(uri);
- }
+ sp<DataSource> source = DataSource::CreateFromURI(uri);
if (source == NULL || source->initCheck() != OK) {
return NULL;
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index d8bd25d..c4d3b5d 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -1786,6 +1786,21 @@
void OMXCodec::fillOutputBuffers() {
CHECK_EQ(mState, EXECUTING);
+ // This is a workaround for some decoders not properly reporting
+ // end-of-output-stream. If we own all input buffers and also own
+ // all output buffers and we already signalled end-of-input-stream,
+ // the end-of-output-stream is implied.
+ if (mSignalledEOS
+ && countBuffersWeOwn(mPortBuffers[kPortIndexInput])
+ == mPortBuffers[kPortIndexInput].size()
+ && countBuffersWeOwn(mPortBuffers[kPortIndexOutput])
+ == mPortBuffers[kPortIndexOutput].size()) {
+ mNoMoreOutputData = true;
+ mBufferFilled.signal();
+
+ return;
+ }
+
Vector<BufferInfo> *buffers = &mPortBuffers[kPortIndexOutput];
for (size_t i = 0; i < buffers->size(); ++i) {
fillOutputBuffer(&buffers->editItemAt(i));
@@ -1833,6 +1848,8 @@
mNoMoreOutputData = false;
+ CODEC_LOGV("calling emptyBuffer with codec specific data");
+
status_t err = mOMX->emptyBuffer(
mNode, info->mBuffer, 0, size,
OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_CODECCONFIG,
diff --git a/media/libstagefright/SampleTable.cpp b/media/libstagefright/SampleTable.cpp
index 4aec0e9..2de96d4 100644
--- a/media/libstagefright/SampleTable.cpp
+++ b/media/libstagefright/SampleTable.cpp
@@ -291,7 +291,7 @@
status_t SampleTable::getChunkForSample(
uint32_t sample_index,
- uint32_t *chunk_index,
+ uint32_t *chunk_index,
uint32_t *chunk_relative_sample_index,
uint32_t *desc_index) {
*chunk_index = 0;
@@ -469,7 +469,7 @@
for (uint32_t i = 0; i < mNumSampleSizes; ++i) {
size_t sample_size;
status_t err = getSampleSize(i, &sample_size);
-
+
if (err != OK) {
return err;
}
@@ -502,7 +502,7 @@
return OK;
}
-
+
*time += delta * n;
cur_sample += n;
}
@@ -510,6 +510,10 @@
return ERROR_OUT_OF_RANGE;
}
+uint32_t abs_difference(uint32_t time1, uint32_t time2) {
+ return time1 > time2 ? time1 - time2 : time2 - time1;
+}
+
status_t SampleTable::findClosestSample(
uint32_t req_time, uint32_t *sample_index, uint32_t flags) {
Mutex::Autolock autoLock(mLock);
@@ -523,7 +527,16 @@
if (req_time < time + n * delta) {
int j = (req_time - time) / delta;
- *sample_index = cur_sample + j;
+ uint32_t time1 = time + j * delta;
+ uint32_t time2 = time1 + delta;
+
+ if (i+1 == mTimeToSampleCount
+ || (abs_difference(req_time, time1)
+ < abs_difference(req_time, time2))) {
+ *sample_index = cur_sample + j;
+ } else {
+ *sample_index = cur_sample + j + 1;
+ }
if (flags & kSyncSample_Flag) {
return findClosestSyncSample(*sample_index, sample_index);
@@ -554,8 +567,9 @@
uint32_t right = mNumSyncSamples;
while (left < right) {
uint32_t mid = (left + right) / 2;
+
if (mDataSource->readAt(
- mSyncSampleOffset + 8 + (mid - 1) * 4, &x, 4) != 4) {
+ mSyncSampleOffset + 8 + mid * 4, &x, 4) != 4) {
return ERROR_IO;
}
diff --git a/media/libstagefright/StagefrightMediaScanner.cpp b/media/libstagefright/StagefrightMediaScanner.cpp
index 9b41929..3451383 100644
--- a/media/libstagefright/StagefrightMediaScanner.cpp
+++ b/media/libstagefright/StagefrightMediaScanner.cpp
@@ -14,10 +14,21 @@
* limitations under the License.
*/
+//#define LOG_NDEBUG 0
+#define LOG_TAG "StagefrightMediaScanner"
+#include <utils/Log.h>
+
#include <media/stagefright/StagefrightMediaScanner.h>
#include "include/StagefrightMetadataRetriever.h"
+// Sonivox includes
+#include <libsonivox/eas.h>
+
+// Ogg Vorbis includes
+#include "ivorbiscodec.h"
+#include "ivorbisfile.h"
+
namespace android {
StagefrightMediaScanner::StagefrightMediaScanner()
@@ -26,15 +37,154 @@
StagefrightMediaScanner::~StagefrightMediaScanner() {}
+static bool FileHasAcceptableExtension(const char *extension) {
+ static const char *kValidExtensions[] = {
+ ".mp3", ".mp4", ".m4a", ".3gp", ".3gpp", ".3g2", ".3gpp2",
+ ".mpeg", ".ogg", ".mid", ".smf", ".imy", ".wma", ".aac",
+ ".wav", ".amr", ".midi", ".xmf", ".rtttl", ".rtx", ".ota"
+ };
+ static const size_t kNumValidExtensions =
+ sizeof(kValidExtensions) / sizeof(kValidExtensions[0]);
+
+ for (size_t i = 0; i < kNumValidExtensions; ++i) {
+ if (!strcasecmp(extension, kValidExtensions[i])) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static status_t HandleMIDI(
+ const char *filename, MediaScannerClient *client) {
+ // get the library configuration and do sanity check
+ const S_EAS_LIB_CONFIG* pLibConfig = EAS_Config();
+ if ((pLibConfig == NULL) || (LIB_VERSION != pLibConfig->libVersion)) {
+ LOGE("EAS library/header mismatch\n");
+ return UNKNOWN_ERROR;
+ }
+ EAS_I32 temp;
+
+ // spin up a new EAS engine
+ EAS_DATA_HANDLE easData = NULL;
+ EAS_HANDLE easHandle = NULL;
+ EAS_RESULT result = EAS_Init(&easData);
+ if (result == EAS_SUCCESS) {
+ EAS_FILE file;
+ file.path = filename;
+ file.fd = 0;
+ file.offset = 0;
+ file.length = 0;
+ result = EAS_OpenFile(easData, &file, &easHandle);
+ }
+ if (result == EAS_SUCCESS) {
+ result = EAS_Prepare(easData, easHandle);
+ }
+ if (result == EAS_SUCCESS) {
+ result = EAS_ParseMetaData(easData, easHandle, &temp);
+ }
+ if (easHandle) {
+ EAS_CloseFile(easData, easHandle);
+ }
+ if (easData) {
+ EAS_Shutdown(easData);
+ }
+
+ if (result != EAS_SUCCESS) {
+ return UNKNOWN_ERROR;
+ }
+
+ char buffer[20];
+ sprintf(buffer, "%ld", temp);
+ if (!client->addStringTag("duration", buffer)) return UNKNOWN_ERROR;
+
+ return OK;
+}
+
+static status_t HandleOGG(
+ const char *filename, MediaScannerClient *client) {
+ int duration;
+
+ FILE *file = fopen(filename,"r");
+ if (!file)
+ return UNKNOWN_ERROR;
+
+ OggVorbis_File vf;
+ if (ov_open(file, &vf, NULL, 0) < 0) {
+ return UNKNOWN_ERROR;
+ }
+
+ char **ptr=ov_comment(&vf,-1)->user_comments;
+ while(*ptr){
+ char *val = strstr(*ptr, "=");
+ if (val) {
+ int keylen = val++ - *ptr;
+ char key[keylen + 1];
+ strncpy(key, *ptr, keylen);
+ key[keylen] = 0;
+ if (!client->addStringTag(key, val)) goto failure;
+ }
+ ++ptr;
+ }
+
+ // Duration
+ duration = ov_time_total(&vf, -1);
+ if (duration > 0) {
+ char buffer[20];
+ sprintf(buffer, "%d", duration);
+ if (!client->addStringTag("duration", buffer)) goto failure;
+ }
+
+ ov_clear(&vf); // this also closes the FILE
+ return OK;
+
+failure:
+ ov_clear(&vf); // this also closes the FILE
+ return UNKNOWN_ERROR;
+}
+
status_t StagefrightMediaScanner::processFile(
const char *path, const char *mimeType,
MediaScannerClient &client) {
client.setLocale(locale());
client.beginFile();
+ const char *extension = strrchr(path, '.');
+
+ if (!extension) {
+ return UNKNOWN_ERROR;
+ }
+
+ if (!FileHasAcceptableExtension(extension)) {
+ client.endFile();
+
+ return UNKNOWN_ERROR;
+ }
+
+ if (!strcasecmp(extension, ".mid")
+ || !strcasecmp(extension, ".smf")
+ || !strcasecmp(extension, ".imy")
+ || !strcasecmp(extension, ".midi")
+ || !strcasecmp(extension, ".xmf")
+ || !strcasecmp(extension, ".rtttl")
+ || !strcasecmp(extension, ".rtx")
+ || !strcasecmp(extension, ".ota")) {
+ return HandleMIDI(path, &client);
+ }
+
+ if (!strcasecmp(extension, ".ogg")) {
+ return HandleOGG(path, &client);
+ }
+
if (mRetriever->setDataSource(path) == OK
&& mRetriever->setMode(
METADATA_MODE_METADATA_RETRIEVAL_ONLY) == OK) {
+ const char *value;
+ if ((value = mRetriever->extractMetadata(
+ METADATA_KEY_MIMETYPE)) != NULL) {
+ client.setMimeType(value);
+ }
+
struct KeyMap {
const char *tag;
int key;
@@ -66,12 +216,27 @@
}
char *StagefrightMediaScanner::extractAlbumArt(int fd) {
- if (mRetriever->setDataSource(fd, 0, 0) == OK
+ off_t size = lseek(fd, 0, SEEK_END);
+ if (size < 0) {
+ return NULL;
+ }
+ lseek(fd, 0, SEEK_SET);
+
+ if (mRetriever->setDataSource(fd, 0, size) == OK
&& mRetriever->setMode(
METADATA_MODE_FRAME_CAPTURE_ONLY) == OK) {
MediaAlbumArt *art = mRetriever->extractAlbumArt();
- // TODO: figure out what format the result should be in.
+ if (art != NULL) {
+ char *data = (char *)malloc(art->mSize + 4);
+ *(int32_t *)data = art->mSize;
+ memcpy(&data[4], art->mData, art->mSize);
+
+ delete art;
+ art = NULL;
+
+ return data;
+ }
}
return NULL;
diff --git a/media/libstagefright/StagefrightMetadataRetriever.cpp b/media/libstagefright/StagefrightMetadataRetriever.cpp
index 128e776..020887c 100644
--- a/media/libstagefright/StagefrightMetadataRetriever.cpp
+++ b/media/libstagefright/StagefrightMetadataRetriever.cpp
@@ -1,19 +1,18 @@
/*
-**
-** Copyright 2009, 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.
-*/
+ * Copyright (C) 2009 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.
+ */
//#define LOG_NDEBUG 0
#define LOG_TAG "StagefrightMetadataRetriever"
@@ -33,7 +32,9 @@
namespace android {
-StagefrightMetadataRetriever::StagefrightMetadataRetriever() {
+StagefrightMetadataRetriever::StagefrightMetadataRetriever()
+ : mParsedMetaData(false),
+ mAlbumArt(NULL) {
LOGV("StagefrightMetadataRetriever()");
DataSource::RegisterDefaultSniffers();
@@ -42,27 +43,168 @@
StagefrightMetadataRetriever::~StagefrightMetadataRetriever() {
LOGV("~StagefrightMetadataRetriever()");
+
+ delete mAlbumArt;
+ mAlbumArt = NULL;
+
mClient.disconnect();
}
status_t StagefrightMetadataRetriever::setDataSource(const char *uri) {
LOGV("setDataSource(%s)", uri);
- mExtractor = MediaExtractor::CreateFromURI(uri);
+ mParsedMetaData = false;
+ mMetaData.clear();
+ delete mAlbumArt;
+ mAlbumArt = NULL;
- return mExtractor.get() != NULL ? OK : UNKNOWN_ERROR;
-}
+ mSource = DataSource::CreateFromURI(uri);
-status_t StagefrightMetadataRetriever::setDataSource(
- int fd, int64_t offset, int64_t length) {
- LOGV("setDataSource(%d, %lld, %lld)", fd, offset, length);
+ if (mSource == NULL) {
+ return UNKNOWN_ERROR;
+ }
- mExtractor = MediaExtractor::Create(
- new FileSource(fd, offset, length));
+ mExtractor = MediaExtractor::Create(mSource);
+
+ if (mExtractor == NULL) {
+ mSource.clear();
+
+ return UNKNOWN_ERROR;
+ }
return OK;
}
+// Warning caller retains ownership of the filedescriptor! Dup it if necessary.
+status_t StagefrightMetadataRetriever::setDataSource(
+ int fd, int64_t offset, int64_t length) {
+ fd = dup(fd);
+
+ LOGV("setDataSource(%d, %lld, %lld)", fd, offset, length);
+
+ mParsedMetaData = false;
+ mMetaData.clear();
+ delete mAlbumArt;
+ mAlbumArt = NULL;
+
+ mSource = new FileSource(fd, offset, length);
+
+ status_t err;
+ if ((err = mSource->initCheck()) != OK) {
+ mSource.clear();
+
+ return err;
+ }
+
+ mExtractor = MediaExtractor::Create(mSource);
+
+ if (mExtractor == NULL) {
+ mSource.clear();
+
+ return UNKNOWN_ERROR;
+ }
+
+ return OK;
+}
+
+static VideoFrame *extractVideoFrameWithCodecFlags(
+ OMXClient *client,
+ const sp<MetaData> &trackMeta,
+ const sp<MediaSource> &source, uint32_t flags) {
+ sp<MediaSource> decoder =
+ OMXCodec::Create(
+ client->interface(), source->getFormat(), false, source,
+ NULL, flags);
+
+ if (decoder.get() == NULL) {
+ LOGV("unable to instantiate video decoder.");
+
+ return NULL;
+ }
+
+ decoder->start();
+
+ // Read one output buffer, ignore format change notifications
+ // and spurious empty buffers.
+
+ MediaSource::ReadOptions options;
+ int64_t thumbNailTime;
+ if (trackMeta->findInt64(kKeyThumbnailTime, &thumbNailTime)) {
+ options.setSeekTo(thumbNailTime);
+ } else {
+ thumbNailTime = -1;
+ }
+
+ MediaBuffer *buffer = NULL;
+ status_t err;
+ do {
+ if (buffer != NULL) {
+ buffer->release();
+ buffer = NULL;
+ }
+ err = decoder->read(&buffer, &options);
+ options.clearSeekTo();
+ } while (err == INFO_FORMAT_CHANGED
+ || (buffer != NULL && buffer->range_length() == 0));
+
+ if (err != OK) {
+ CHECK_EQ(buffer, NULL);
+
+ LOGV("decoding frame failed.");
+ decoder->stop();
+
+ return NULL;
+ }
+
+ LOGV("successfully decoded video frame.");
+
+ int64_t timeUs;
+ CHECK(buffer->meta_data()->findInt64(kKeyTime, &timeUs));
+ if (thumbNailTime >= 0) {
+ if (timeUs != thumbNailTime) {
+ const char *mime;
+ CHECK(trackMeta->findCString(kKeyMIMEType, &mime));
+
+ LOGV("thumbNailTime = %lld us, timeUs = %lld us, mime = %s",
+ thumbNailTime, timeUs, mime);
+ }
+ }
+
+ sp<MetaData> meta = decoder->getFormat();
+
+ int32_t width, height;
+ CHECK(meta->findInt32(kKeyWidth, &width));
+ CHECK(meta->findInt32(kKeyHeight, &height));
+
+ VideoFrame *frame = new VideoFrame;
+ frame->mWidth = width;
+ frame->mHeight = height;
+ frame->mDisplayWidth = width;
+ frame->mDisplayHeight = height;
+ frame->mSize = width * height * 2;
+ frame->mData = new uint8_t[frame->mSize];
+
+ int32_t srcFormat;
+ CHECK(meta->findInt32(kKeyColorFormat, &srcFormat));
+
+ ColorConverter converter(
+ (OMX_COLOR_FORMATTYPE)srcFormat, OMX_COLOR_Format16bitRGB565);
+ CHECK(converter.isValid());
+
+ converter.convert(
+ width, height,
+ (const uint8_t *)buffer->data() + buffer->range_offset(),
+ 0,
+ frame->mData, width * 2);
+
+ buffer->release();
+ buffer = NULL;
+
+ decoder->stop();
+
+ return frame;
+}
+
VideoFrame *StagefrightMetadataRetriever::captureFrame() {
LOGV("captureFrame");
@@ -99,99 +241,125 @@
return NULL;
}
- sp<MetaData> meta = source->getFormat();
+ VideoFrame *frame =
+ extractVideoFrameWithCodecFlags(
+ &mClient, trackMeta, source, OMXCodec::kPreferSoftwareCodecs);
- sp<MediaSource> decoder =
- OMXCodec::Create(
- mClient.interface(), meta, false, source,
- NULL, OMXCodec::kPreferSoftwareCodecs);
+ if (frame == NULL) {
+ LOGV("Software decoder failed to extract thumbnail, "
+ "trying hardware decoder.");
- if (decoder.get() == NULL) {
- LOGV("unable to instantiate video decoder.");
-
- return NULL;
+ frame = extractVideoFrameWithCodecFlags(&mClient, trackMeta, source, 0);
}
- decoder->start();
-
- // Read one output buffer, ignore format change notifications
- // and spurious empty buffers.
-
- MediaSource::ReadOptions options;
- int64_t thumbNailTime;
- if (trackMeta->findInt64(kKeyThumbnailTime, &thumbNailTime)) {
- options.setSeekTo(thumbNailTime);
- }
-
- MediaBuffer *buffer = NULL;
- status_t err;
- do {
- if (buffer != NULL) {
- buffer->release();
- buffer = NULL;
- }
- err = decoder->read(&buffer, &options);
- options.clearSeekTo();
- } while (err == INFO_FORMAT_CHANGED
- || (buffer != NULL && buffer->range_length() == 0));
-
- if (err != OK) {
- CHECK_EQ(buffer, NULL);
-
- LOGV("decoding frame failed.");
- decoder->stop();
-
- return NULL;
- }
-
- LOGV("successfully decoded video frame.");
-
- meta = decoder->getFormat();
-
- int32_t width, height;
- CHECK(meta->findInt32(kKeyWidth, &width));
- CHECK(meta->findInt32(kKeyHeight, &height));
-
- VideoFrame *frame = new VideoFrame;
- frame->mWidth = width;
- frame->mHeight = height;
- frame->mDisplayWidth = width;
- frame->mDisplayHeight = height;
- frame->mSize = width * height * 2;
- frame->mData = new uint8_t[frame->mSize];
-
- int32_t srcFormat;
- CHECK(meta->findInt32(kKeyColorFormat, &srcFormat));
-
- ColorConverter converter(
- (OMX_COLOR_FORMATTYPE)srcFormat, OMX_COLOR_Format16bitRGB565);
- CHECK(converter.isValid());
-
- converter.convert(
- width, height,
- (const uint8_t *)buffer->data() + buffer->range_offset(),
- 0,
- frame->mData, width * 2);
-
- buffer->release();
- buffer = NULL;
-
- decoder->stop();
-
return frame;
}
MediaAlbumArt *StagefrightMetadataRetriever::extractAlbumArt() {
LOGV("extractAlbumArt (extractor: %s)", mExtractor.get() != NULL ? "YES" : "NO");
+ if (mExtractor == NULL) {
+ return NULL;
+ }
+
+ if (!mParsedMetaData) {
+ parseMetaData();
+
+ mParsedMetaData = true;
+ }
+
+ if (mAlbumArt) {
+ return new MediaAlbumArt(*mAlbumArt);
+ }
+
return NULL;
}
const char *StagefrightMetadataRetriever::extractMetadata(int keyCode) {
- LOGV("extractMetadata %d (extractor: %s)",
- keyCode, mExtractor.get() != NULL ? "YES" : "NO");
+ LOGV("extractMetadata %d", keyCode);
- return NULL;
+ if (mExtractor == NULL) {
+ return NULL;
+ }
+
+ if (!mParsedMetaData) {
+ parseMetaData();
+
+ mParsedMetaData = true;
+ }
+
+ ssize_t index = mMetaData.indexOfKey(keyCode);
+
+ if (index < 0) {
+ return NULL;
+ }
+
+ return strdup(mMetaData.valueAt(index).string());
}
+void StagefrightMetadataRetriever::parseMetaData() {
+ sp<MetaData> meta = mExtractor->getMetaData();
+
+ struct Map {
+ int from;
+ int to;
+ };
+ static const Map kMap[] = {
+ { kKeyMIMEType, METADATA_KEY_MIMETYPE },
+ { kKeyCDTrackNumber, METADATA_KEY_CD_TRACK_NUMBER },
+ { kKeyAlbum, METADATA_KEY_ALBUM },
+ { kKeyArtist, METADATA_KEY_ARTIST },
+ { kKeyAuthor, METADATA_KEY_AUTHOR },
+ { kKeyComposer, METADATA_KEY_COMPOSER },
+ { kKeyDate, METADATA_KEY_DATE },
+ { kKeyGenre, METADATA_KEY_GENRE },
+ { kKeyTitle, METADATA_KEY_TITLE },
+ { kKeyYear, METADATA_KEY_YEAR },
+ };
+ static const size_t kNumMapEntries = sizeof(kMap) / sizeof(kMap[0]);
+
+ for (size_t i = 0; i < kNumMapEntries; ++i) {
+ const char *value;
+ if (meta->findCString(kMap[i].from, &value)) {
+ mMetaData.add(kMap[i].to, String8(value));
+ }
+ }
+
+ const void *data;
+ uint32_t type;
+ size_t dataSize;
+ if (meta->findData(kKeyAlbumArt, &type, &data, &dataSize)) {
+ mAlbumArt = new MediaAlbumArt;
+ mAlbumArt->mSize = dataSize;
+ mAlbumArt->mData = new uint8_t[dataSize];
+ memcpy(mAlbumArt->mData, data, dataSize);
+ }
+
+ size_t numTracks = mExtractor->countTracks();
+
+ char tmp[32];
+ sprintf(tmp, "%d", numTracks);
+
+ mMetaData.add(METADATA_KEY_NUM_TRACKS, String8(tmp));
+
+ // The overall duration is the duration of the longest track.
+ int64_t maxDurationUs = 0;
+ for (size_t i = 0; i < numTracks; ++i) {
+ sp<MetaData> trackMeta = mExtractor->getTrackMetaData(i);
+
+ int64_t durationUs;
+ if (trackMeta->findInt64(kKeyDuration, &durationUs)) {
+ if (durationUs > maxDurationUs) {
+ maxDurationUs = durationUs;
+ }
+ }
+ }
+
+ // The duration value is a string representing the duration in ms.
+ sprintf(tmp, "%lld", (maxDurationUs + 500) / 1000);
+
+ mMetaData.add(METADATA_KEY_DURATION, String8(tmp));
+}
+
+
} // namespace android
diff --git a/media/libstagefright/WAVExtractor.cpp b/media/libstagefright/WAVExtractor.cpp
index 542c764..959a767 100644
--- a/media/libstagefright/WAVExtractor.cpp
+++ b/media/libstagefright/WAVExtractor.cpp
@@ -82,6 +82,18 @@
WAVExtractor::~WAVExtractor() {
}
+sp<MetaData> WAVExtractor::getMetaData() {
+ sp<MetaData> meta = new MetaData;
+
+ if (mInitCheck != OK) {
+ return meta;
+ }
+
+ meta->setCString(kKeyMIMEType, "audio/x-wav");
+
+ return meta;
+}
+
size_t WAVExtractor::countTracks() {
return mInitCheck == OK ? 1 : 0;
}
diff --git a/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp b/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp
index 229e933..d874224 100644
--- a/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp
+++ b/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp
@@ -383,7 +383,7 @@
return OK;
} else {
- LOGE("failed to decode frame (res = %d)", res);
+ LOGV("failed to decode frame (res = %d)", res);
return UNKNOWN_ERROR;
}
}
diff --git a/media/libstagefright/codecs/mp3dec/MP3Decoder.cpp b/media/libstagefright/codecs/mp3dec/MP3Decoder.cpp
index 2f919c2..f1f7194 100644
--- a/media/libstagefright/codecs/mp3dec/MP3Decoder.cpp
+++ b/media/libstagefright/codecs/mp3dec/MP3Decoder.cpp
@@ -160,7 +160,15 @@
mConfig->outputFrameSize = buffer->size() / sizeof(int16_t);
mConfig->pOutputBuffer = static_cast<int16_t *>(buffer->data());
- CHECK_EQ(pvmp3_framedecoder(mConfig, mDecoderBuf), NO_DECODING_ERROR);
+ if (pvmp3_framedecoder(mConfig, mDecoderBuf) != NO_DECODING_ERROR) {
+ buffer->release();
+ buffer = NULL;
+
+ mInputBuffer->release();
+ mInputBuffer = NULL;
+
+ return UNKNOWN_ERROR;
+ }
buffer->set_range(
0, mConfig->outputFrameSize * sizeof(int16_t));
diff --git a/media/libstagefright/id3/Android.mk b/media/libstagefright/id3/Android.mk
new file mode 100644
index 0000000..3c47e2e
--- /dev/null
+++ b/media/libstagefright/id3/Android.mk
@@ -0,0 +1,27 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ ID3.cpp
+
+LOCAL_MODULE := libstagefright_id3
+
+include $(BUILD_STATIC_LIBRARY)
+
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ testid3.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libstagefright libutils libbinder
+
+LOCAL_STATIC_LIBRARIES := \
+ libstagefright_id3
+
+LOCAL_MODULE := testid3
+
+include $(BUILD_EXECUTABLE)
+
diff --git a/media/libstagefright/id3/ID3.cpp b/media/libstagefright/id3/ID3.cpp
new file mode 100644
index 0000000..2b3ef1a
--- /dev/null
+++ b/media/libstagefright/id3/ID3.cpp
@@ -0,0 +1,465 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "ID3"
+#include <utils/Log.h>
+
+#include "../include/ID3.h"
+
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/Utils.h>
+#include <utils/String8.h>
+
+namespace android {
+
+ID3::ID3(const sp<DataSource> &source)
+ : mIsValid(false),
+ mData(NULL),
+ mSize(0),
+ mFirstFrameOffset(0),
+ mVersion(ID3_UNKNOWN) {
+ mIsValid = parse(source);
+}
+
+ID3::~ID3() {
+ if (mData) {
+ free(mData);
+ mData = NULL;
+ }
+}
+
+bool ID3::isValid() const {
+ return mIsValid;
+}
+
+ID3::Version ID3::version() const {
+ return mVersion;
+}
+
+bool ID3::parse(const sp<DataSource> &source) {
+ struct id3_header {
+ char id[3];
+ uint8_t version_major;
+ uint8_t version_minor;
+ uint8_t flags;
+ uint8_t enc_size[4];
+ };
+
+ id3_header header;
+ if (source->readAt(
+ 0, &header, sizeof(header)) != (ssize_t)sizeof(header)) {
+ return false;
+ }
+
+ if (memcmp(header.id, "ID3", 3)) {
+ return false;
+ }
+
+ if (header.version_major == 0xff || header.version_minor == 0xff) {
+ return false;
+ }
+
+ if (header.version_major == 2) {
+ if (header.flags & 0x3f) {
+ // We only support the 2 high bits, if any of the lower bits are
+ // set, we cannot guarantee to understand the tag format.
+ return false;
+ }
+
+ if (header.flags & 0x40) {
+ // No compression scheme has been decided yet, ignore the
+ // tag if compression is indicated.
+
+ return false;
+ }
+ } else if (header.version_major == 3) {
+ if (header.flags & 0x1f) {
+ // We only support the 3 high bits, if any of the lower bits are
+ // set, we cannot guarantee to understand the tag format.
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ size_t size = 0;
+ for (int32_t i = 0; i < 4; ++i) {
+ if (header.enc_size[i] & 0x80) {
+ return false;
+ }
+
+ size = (size << 7) | header.enc_size[i];
+ }
+
+ mData = (uint8_t *)malloc(size);
+
+ if (mData == NULL) {
+ return false;
+ }
+
+ mSize = size;
+
+ if (source->readAt(sizeof(header), mData, mSize) != (ssize_t)mSize) {
+ return false;
+ }
+
+ if (header.flags & 0x80) {
+ LOGI("removing unsynchronization");
+ removeUnsynchronization();
+ }
+
+ mFirstFrameOffset = 0;
+ if (header.version_major == 3 && (header.flags & 0x40)) {
+ // Version 2.3 has an optional extended header.
+
+ if (mSize < 4) {
+ return false;
+ }
+
+ size_t extendedHeaderSize = U32_AT(&mData[0]) + 4;
+
+ if (extendedHeaderSize > mSize) {
+ return false;
+ }
+
+ mFirstFrameOffset = extendedHeaderSize;
+
+ uint16_t extendedFlags = 0;
+ if (extendedHeaderSize >= 6) {
+ extendedFlags = U16_AT(&mData[4]);
+
+ if (extendedHeaderSize >= 10) {
+ size_t paddingSize = U32_AT(&mData[6]);
+
+ if (mFirstFrameOffset + paddingSize > mSize) {
+ return false;
+ }
+
+ mSize -= paddingSize;
+ }
+
+ if (extendedFlags & 0x8000) {
+ LOGI("have crc");
+ }
+ }
+ }
+
+ if (header.version_major == 2) {
+ mVersion = ID3_V2_2;
+ } else {
+ CHECK_EQ(header.version_major, 3);
+ mVersion = ID3_V2_3;
+ }
+
+ return true;
+}
+
+void ID3::removeUnsynchronization() {
+ for (size_t i = 0; i + 1 < mSize; ++i) {
+ if (mData[i] == 0xff && mData[i + 1] == 0x00) {
+ memmove(&mData[i + 1], &mData[i + 2], mSize - i - 2);
+ --mSize;
+ }
+ }
+}
+
+ID3::Iterator::Iterator(const ID3 &parent, const char *id)
+ : mParent(parent),
+ mID(NULL),
+ mOffset(mParent.mFirstFrameOffset),
+ mFrameData(NULL),
+ mFrameSize(0) {
+ if (id) {
+ mID = strdup(id);
+ }
+
+ findFrame();
+}
+
+ID3::Iterator::~Iterator() {
+ if (mID) {
+ free(mID);
+ mID = NULL;
+ }
+}
+
+bool ID3::Iterator::done() const {
+ return mFrameData == NULL;
+}
+
+void ID3::Iterator::next() {
+ if (mFrameData == NULL) {
+ return;
+ }
+
+ mOffset += mFrameSize;
+
+ findFrame();
+}
+
+void ID3::Iterator::getID(String8 *id) const {
+ id->setTo("");
+
+ if (mFrameData == NULL) {
+ return;
+ }
+
+ if (mParent.mVersion == ID3_V2_2) {
+ id->setTo((const char *)&mParent.mData[mOffset], 3);
+ } else {
+ CHECK_EQ(mParent.mVersion, ID3_V2_3);
+ id->setTo((const char *)&mParent.mData[mOffset], 4);
+ }
+}
+
+static void convertISO8859ToString8(
+ const uint8_t *data, size_t size,
+ String8 *s) {
+ size_t utf8len = 0;
+ for (size_t i = 0; i < size; ++i) {
+ if (data[i] < 0x80) {
+ ++utf8len;
+ } else {
+ utf8len += 2;
+ }
+ }
+
+ if (utf8len == size) {
+ // Only ASCII characters present.
+
+ s->setTo((const char *)data, size);
+ return;
+ }
+
+ char *tmp = new char[utf8len];
+ char *ptr = tmp;
+ for (size_t i = 0; i < size; ++i) {
+ if (data[i] < 0x80) {
+ *ptr++ = data[i];
+ } else if (data[i] < 0xc0) {
+ *ptr++ = 0xc2;
+ *ptr++ = data[i];
+ } else {
+ *ptr++ = 0xc3;
+ *ptr++ = data[i] - 64;
+ }
+ }
+
+ s->setTo(tmp, utf8len);
+
+ delete[] tmp;
+ tmp = NULL;
+}
+
+void ID3::Iterator::getString(String8 *id) const {
+ id->setTo("");
+
+ if (mFrameData == NULL) {
+ return;
+ }
+
+ size_t n = mFrameSize - getHeaderLength() - 1;
+
+ if (*mFrameData == 0x00) {
+ // ISO 8859-1
+ convertISO8859ToString8(mFrameData + 1, n, id);
+ } else {
+ // UCS-2
+ id->setTo((const char16_t *)(mFrameData + 1), n);
+ }
+}
+
+const uint8_t *ID3::Iterator::getData(size_t *length) const {
+ *length = 0;
+
+ if (mFrameData == NULL) {
+ return NULL;
+ }
+
+ *length = mFrameSize - getHeaderLength();
+
+ return mFrameData;
+}
+
+size_t ID3::Iterator::getHeaderLength() const {
+ if (mParent.mVersion == ID3_V2_2) {
+ return 6;
+ } else {
+ CHECK_EQ(mParent.mVersion, ID3_V2_3);
+ return 10;
+ }
+}
+
+void ID3::Iterator::findFrame() {
+ for (;;) {
+ mFrameData = NULL;
+ mFrameSize = 0;
+
+ if (mParent.mVersion == ID3_V2_2) {
+ if (mOffset + 6 > mParent.mSize) {
+ return;
+ }
+
+ if (!memcmp(&mParent.mData[mOffset], "\0\0\0", 3)) {
+ return;
+ }
+
+ mFrameSize =
+ (mParent.mData[mOffset + 3] << 16)
+ | (mParent.mData[mOffset + 4] << 8)
+ | mParent.mData[mOffset + 5];
+
+ mFrameSize += 6;
+
+ if (mOffset + mFrameSize > mParent.mSize) {
+ LOGV("partial frame at offset %d (size = %d, bytes-remaining = %d)",
+ mOffset, mFrameSize, mParent.mSize - mOffset - 6);
+ return;
+ }
+
+ mFrameData = &mParent.mData[mOffset + 6];
+
+ if (!mID) {
+ break;
+ }
+
+ char id[4];
+ memcpy(id, &mParent.mData[mOffset], 3);
+ id[3] = '\0';
+
+ if (!strcmp(id, mID)) {
+ break;
+ }
+ } else {
+ CHECK_EQ(mParent.mVersion, ID3_V2_3);
+
+ if (mOffset + 10 > mParent.mSize) {
+ return;
+ }
+
+ if (!memcmp(&mParent.mData[mOffset], "\0\0\0\0", 4)) {
+ return;
+ }
+
+ mFrameSize = 10 + U32_AT(&mParent.mData[mOffset + 4]);
+
+ if (mOffset + mFrameSize > mParent.mSize) {
+ LOGV("partial frame at offset %d (size = %d, bytes-remaining = %d)",
+ mOffset, mFrameSize, mParent.mSize - mOffset - 10);
+ return;
+ }
+
+ mFrameData = &mParent.mData[mOffset + 10];
+
+ if (!mID) {
+ break;
+ }
+
+ char id[5];
+ memcpy(id, &mParent.mData[mOffset], 4);
+ id[4] = '\0';
+
+ if (!strcmp(id, mID)) {
+ break;
+ }
+ }
+
+ mOffset += mFrameSize;
+ }
+}
+
+static size_t StringSize(const uint8_t *start, uint8_t encoding) {
+ if (encoding== 0x00) {
+ // ISO 8859-1
+ return strlen((const char *)start) + 1;
+ }
+
+ // UCS-2
+ size_t n = 0;
+ while (start[n] != '\0' || start[n + 1] != '\0') {
+ n += 2;
+ }
+
+ return n;
+}
+
+const void *
+ID3::getAlbumArt(size_t *length, String8 *mime) const {
+ *length = 0;
+ mime->setTo("");
+
+ Iterator it(*this, mVersion == ID3_V2_3 ? "APIC" : "PIC");
+
+ while (!it.done()) {
+ size_t size;
+ const uint8_t *data = it.getData(&size);
+
+ if (mVersion == ID3_V2_3) {
+ uint8_t encoding = data[0];
+ mime->setTo((const char *)&data[1]);
+ size_t mimeLen = strlen((const char *)&data[1]) + 1;
+
+ uint8_t picType = data[1 + mimeLen];
+#if 0
+ if (picType != 0x03) {
+ // Front Cover Art
+ it.next();
+ continue;
+ }
+#endif
+
+ size_t descLen = StringSize(&data[2 + mimeLen], encoding);
+
+ *length = size - 2 - mimeLen - descLen;
+
+ return &data[2 + mimeLen + descLen];
+ } else {
+ uint8_t encoding = data[0];
+
+ if (!memcmp(&data[1], "PNG", 3)) {
+ mime->setTo("image/png");
+ } else if (!memcmp(&data[1], "JPG", 3)) {
+ mime->setTo("image/jpeg");
+ } else if (!memcmp(&data[1], "-->", 3)) {
+ mime->setTo("text/plain");
+ } else {
+ return NULL;
+ }
+
+#if 0
+ uint8_t picType = data[4];
+ if (picType != 0x03) {
+ // Front Cover Art
+ it.next();
+ continue;
+ }
+#endif
+
+ size_t descLen = StringSize(&data[5], encoding);
+
+ *length = size - 5 - descLen;
+
+ return &data[5 + descLen];
+ }
+ }
+
+ return NULL;
+}
+
+
+} // namespace android
diff --git a/media/libstagefright/id3/testid3.cpp b/media/libstagefright/id3/testid3.cpp
new file mode 100644
index 0000000..305b065
--- /dev/null
+++ b/media/libstagefright/id3/testid3.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "../include/ID3.h"
+
+#include <ctype.h>
+#include <dirent.h>
+
+#include <binder/ProcessState.h>
+#include <media/stagefright/FileSource.h>
+#include <media/stagefright/MediaDebug.h>
+
+#define MAXPATHLEN 256
+
+using namespace android;
+
+static void hexdump(const void *_data, size_t size) {
+ const uint8_t *data = (const uint8_t *)_data;
+ size_t offset = 0;
+ while (offset < size) {
+ printf("0x%04x ", offset);
+
+ size_t n = size - offset;
+ if (n > 16) {
+ n = 16;
+ }
+
+ for (size_t i = 0; i < 16; ++i) {
+ if (i == 8) {
+ printf(" ");
+ }
+
+ if (offset + i < size) {
+ printf("%02x ", data[offset + i]);
+ } else {
+ printf(" ");
+ }
+ }
+
+ printf(" ");
+
+ for (size_t i = 0; i < n; ++i) {
+ if (isprint(data[offset + i])) {
+ printf("%c", data[offset + i]);
+ } else {
+ printf(".");
+ }
+ }
+
+ printf("\n");
+
+ offset += 16;
+ }
+}
+
+void scanFile(const char *path) {
+ sp<FileSource> file = new FileSource(path);
+ CHECK_EQ(file->initCheck(), OK);
+
+ ID3 tag(file);
+ if (!tag.isValid()) {
+ printf("FAIL %s\n", path);
+ } else {
+ printf("SUCCESS %s\n", path);
+
+ ID3::Iterator it(tag, NULL);
+ while (!it.done()) {
+ String8 id;
+ it.getID(&id);
+
+ CHECK(id.length() > 0);
+ if (id[0] == 'T') {
+ String8 text;
+ it.getString(&text);
+
+ printf(" found text frame '%s': %s\n", id.string(), text.string());
+ } else {
+ printf(" found frame '%s'.\n", id.string());
+ }
+
+ it.next();
+ }
+
+ size_t dataSize;
+ String8 mime;
+ const void *data = tag.getAlbumArt(&dataSize, &mime);
+
+ if (data) {
+ printf("found album art: size=%d mime='%s'\n", dataSize,
+ mime.string());
+
+ hexdump(data, dataSize > 128 ? 128 : dataSize);
+ }
+ }
+}
+
+void scan(const char *path) {
+ DIR *dir = opendir(path);
+
+ if (dir == NULL) {
+ return;
+ }
+
+ rewinddir(dir);
+
+ struct dirent *ent;
+ while ((ent = readdir(dir)) != NULL) {
+ if (!strcmp(".", ent->d_name) || !strcmp("..", ent->d_name)) {
+ continue;
+ }
+
+ char newPath[MAXPATHLEN];
+ strcpy(newPath, path);
+ strcat(newPath, "/");
+ strcat(newPath, ent->d_name);
+
+ if (ent->d_type == DT_DIR) {
+ scan(newPath);
+ } else if (ent->d_type == DT_REG) {
+ size_t len = strlen(ent->d_name);
+
+ if (len >= 4
+ && !strcasecmp(ent->d_name + len - 4, ".mp3")) {
+ scanFile(newPath);
+ }
+ }
+ }
+
+ closedir(dir);
+ dir = NULL;
+}
+
+int main(int argc, char **argv) {
+ android::ProcessState::self()->startThreadPool();
+
+ DataSource::RegisterDefaultSniffers();
+
+ for (int i = 1; i < argc; ++i) {
+ scan(argv[i]);
+ }
+
+ return 0;
+}
diff --git a/media/libstagefright/include/AMRExtractor.h b/media/libstagefright/include/AMRExtractor.h
index 1972a1c..db49fe4 100644
--- a/media/libstagefright/include/AMRExtractor.h
+++ b/media/libstagefright/include/AMRExtractor.h
@@ -32,6 +32,8 @@
virtual sp<MediaSource> getTrack(size_t index);
virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
+ virtual sp<MetaData> getMetaData();
+
protected:
virtual ~AMRExtractor();
diff --git a/media/libstagefright/include/ID3.h b/media/libstagefright/include/ID3.h
new file mode 100644
index 0000000..79931ac
--- /dev/null
+++ b/media/libstagefright/include/ID3.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ID3_H_
+
+#define ID3_H_
+
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct DataSource;
+struct String8;
+
+struct ID3 {
+ enum Version {
+ ID3_UNKNOWN,
+ ID3_V2_2,
+ ID3_V2_3
+ };
+
+ ID3(const sp<DataSource> &source);
+ ~ID3();
+
+ bool isValid() const;
+
+ Version version() const;
+
+ const void *getAlbumArt(size_t *length, String8 *mime) const;
+
+ struct Iterator {
+ Iterator(const ID3 &parent, const char *id);
+ ~Iterator();
+
+ bool done() const;
+ void getID(String8 *id) const;
+ void getString(String8 *s) const;
+ const uint8_t *getData(size_t *length) const;
+ void next();
+
+ private:
+ const ID3 &mParent;
+ char *mID;
+ size_t mOffset;
+
+ const uint8_t *mFrameData;
+ size_t mFrameSize;
+
+ void findFrame();
+
+ size_t getHeaderLength() const;
+
+ Iterator(const Iterator &);
+ Iterator &operator=(const Iterator &);
+ };
+
+private:
+ bool mIsValid;
+ uint8_t *mData;
+ size_t mSize;
+ size_t mFirstFrameOffset;
+ Version mVersion;
+
+ bool parse(const sp<DataSource> &source);
+ void removeUnsynchronization();
+
+ ID3(const ID3 &);
+ ID3 &operator=(const ID3 &);
+};
+
+} // namespace android
+
+#endif // ID3_H_
+
diff --git a/media/libstagefright/include/MP3Extractor.h b/media/libstagefright/include/MP3Extractor.h
index b5a6b3c..3ce6df3 100644
--- a/media/libstagefright/include/MP3Extractor.h
+++ b/media/libstagefright/include/MP3Extractor.h
@@ -34,6 +34,8 @@
virtual sp<MediaSource> getTrack(size_t index);
virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
+ virtual sp<MetaData> getMetaData();
+
protected:
virtual ~MP3Extractor();
diff --git a/media/libstagefright/include/MPEG4Extractor.h b/media/libstagefright/include/MPEG4Extractor.h
index ce4736d..0e360e8 100644
--- a/media/libstagefright/include/MPEG4Extractor.h
+++ b/media/libstagefright/include/MPEG4Extractor.h
@@ -31,9 +31,11 @@
// Extractor assumes ownership of "source".
MPEG4Extractor(const sp<DataSource> &source);
- size_t countTracks();
- sp<MediaSource> getTrack(size_t index);
- sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
+ virtual size_t countTracks();
+ virtual sp<MediaSource> getTrack(size_t index);
+ virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
+
+ virtual sp<MetaData> getMetaData();
protected:
virtual ~MPEG4Extractor();
@@ -49,6 +51,7 @@
sp<DataSource> mDataSource;
bool mHaveMetadata;
+ bool mHasVideo;
Track *mFirstTrack, *mLastTrack;
diff --git a/media/libstagefright/include/StagefrightMetadataRetriever.h b/media/libstagefright/include/StagefrightMetadataRetriever.h
index 16127d7..b80387f 100644
--- a/media/libstagefright/include/StagefrightMetadataRetriever.h
+++ b/media/libstagefright/include/StagefrightMetadataRetriever.h
@@ -1,19 +1,18 @@
/*
-**
-** Copyright 2009, 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.
-*/
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
#ifndef STAGEFRIGHT_METADATA_RETRIEVER_H_
@@ -22,9 +21,11 @@
#include <media/MediaMetadataRetrieverInterface.h>
#include <media/stagefright/OMXClient.h>
+#include <utils/KeyedVector.h>
namespace android {
+struct DataSource;
class MediaExtractor;
struct StagefrightMetadataRetriever : public MediaMetadataRetrieverInterface {
@@ -40,8 +41,15 @@
private:
OMXClient mClient;
+ sp<DataSource> mSource;
sp<MediaExtractor> mExtractor;
+ bool mParsedMetaData;
+ KeyedVector<int, String8> mMetaData;
+ MediaAlbumArt *mAlbumArt;
+
+ void parseMetaData();
+
StagefrightMetadataRetriever(const StagefrightMetadataRetriever &);
StagefrightMetadataRetriever &operator=(
diff --git a/media/libstagefright/include/WAVExtractor.h b/media/libstagefright/include/WAVExtractor.h
index 10b9700..8545efc 100644
--- a/media/libstagefright/include/WAVExtractor.h
+++ b/media/libstagefright/include/WAVExtractor.h
@@ -34,6 +34,8 @@
virtual sp<MediaSource> getTrack(size_t index);
virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
+ virtual sp<MetaData> getMetaData();
+
protected:
virtual ~WAVExtractor();
diff --git a/preloaded-classes b/preloaded-classes
index ddcecc9..ec4d74c 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -225,7 +225,6 @@
android.net.Uri$PathSegments
android.net.Uri$StringUri
android.net.WebAddress
-android.net.http.DomainNameChecker
android.net.http.CertificateChainValidator
android.net.http.EventHandler
android.net.http.HttpsConnection
diff --git a/services/java/com/android/server/INativeDaemonConnectorCallbacks.java b/services/java/com/android/server/INativeDaemonConnectorCallbacks.java
new file mode 100644
index 0000000..6fbf713
--- /dev/null
+++ b/services/java/com/android/server/INativeDaemonConnectorCallbacks.java
@@ -0,0 +1,24 @@
+
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+interface INativeDaemonConnectorCallbacks {
+
+ void onDaemonConnected();
+ boolean onEvent(int code, String raw, String[] cooked);
+}
diff --git a/services/java/com/android/server/MountListener.java b/services/java/com/android/server/MountListener.java
deleted file mode 100644
index 9443ff8..0000000
--- a/services/java/com/android/server/MountListener.java
+++ /dev/null
@@ -1,377 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.net.LocalSocketAddress;
-import android.net.LocalSocket;
-import android.os.Environment;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.util.Config;
-import android.util.Log;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.Socket;
-import java.lang.IllegalStateException;
-
-import java.util.List;
-import java.util.ArrayList;
-import java.util.ListIterator;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-
-/**
- * Vold Connection class
- */
-final class MountListener implements Runnable {
- private static final String TAG = "MountListener";
- private static final String VOLD_SOCKET = "vold";
- private static final int RESPONSE_QUEUE_SIZE = 10;
-
- private MountService mService;
- private BlockingQueue<String> mResponseQueue;
- private OutputStream mOutputStream;
-
- class ResponseCode {
- public static final int ActionInitiated = 100;
- public static final int VolumeListResult = 110;
- public static final int AsecListResult = 111;
-
- public static final int CommandOkay = 200;
- public static final int ShareAvailabilityResult = 210;
- public static final int AsecPathResult = 211;
-
- public static final int UnsolicitedInformational = 600;
- public static final int VolumeStateChange = 605;
- public static final int VolumeMountFailedBlank = 610;
- public static final int VolumeMountFailedDamaged = 611;
- public static final int VolumeMountFailedNoMedia = 612;
- public static final int ShareAvailabilityChange = 620;
- public static final int VolumeDiskInserted = 630;
- public static final int VolumeDiskRemoved = 631;
- public static final int VolumeBadRemoval = 632;
- }
-
- MountListener(MountService service) {
- mService = service;
- mResponseQueue = new LinkedBlockingQueue<String>(RESPONSE_QUEUE_SIZE);
- }
-
- public void run() {
- // Vold does not run in the simulator, so fake out a mounted event to trigger the Media Scanner
- if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
- mService.notifyMediaMounted(Environment.getExternalStorageDirectory().getPath(), false);
- return;
- }
-
- try {
- while (true) {
- listenToSocket();
- }
- } catch (Throwable t) {
- Log.e(TAG, "Fatal error " + t + " in MountListener thread!");
- }
- }
-
- private void listenToSocket() {
- LocalSocket socket = null;
-
- try {
- socket = new LocalSocket();
- LocalSocketAddress address = new LocalSocketAddress(VOLD_SOCKET,
- LocalSocketAddress.Namespace.RESERVED);
-
- socket.connect(address);
- mService.onVoldConnected();
-
- InputStream inputStream = socket.getInputStream();
- mOutputStream = socket.getOutputStream();
-
- byte[] buffer = new byte[4096];
-
- while (true) {
- int count = inputStream.read(buffer);
- if (count < 0) break;
-
- int start = 0;
- for (int i = 0; i < count; i++) {
- if (buffer[i] == 0) {
- String event = new String(buffer, start, i - start);
-// Log.d(TAG, "Got packet {" + event + "}");
-
- String[] tokens = event.split(" ");
- try {
- int code = Integer.parseInt(tokens[0]);
-
- if (code >= ResponseCode.UnsolicitedInformational) {
- try {
- handleUnsolicitedEvent(code, event, tokens);
- } catch (Exception ex) {
- Log.e(TAG, String.format(
- "Error handling '%s'", event), ex);
- }
- } else {
- try {
- mResponseQueue.put(event);
- } catch (InterruptedException ex) {
- Log.e(TAG, "InterruptedException");
- }
- }
- } catch (NumberFormatException nfe) {
- Log.w(TAG,
- "Unknown msg from Vold '" + event + "'");
- }
- start = i + 1;
- }
- }
- }
- } catch (IOException ex) {
- Log.e(TAG, "IOException in listenToSocket");
- }
-
- synchronized (this) {
- if (mOutputStream != null) {
- try {
- mOutputStream.close();
- } catch (IOException e) {
- Log.w(TAG, "IOException closing output stream");
- }
-
- mOutputStream = null;
- }
- }
-
- try {
- if (socket != null) {
- socket.close();
- }
- } catch (IOException ex) {
- Log.w(TAG, "IOException closing socket");
- }
-
- Log.e(TAG, "Failed to connect to Vold", new IllegalStateException());
- SystemClock.sleep(5000);
- }
-
- private void handleUnsolicitedEvent(int code, String raw,
- String[] cooked) throws IllegalStateException {
-// Log.d(TAG, "unsolicited {" + raw + "}");
- if (code == ResponseCode.VolumeStateChange) {
- // FMT: NNN Volume <label> <mountpoint> state changed from <old_#> (<old_str>) to <new_#> (<new_str>)
- mService.notifyVolumeStateChange(cooked[2], cooked[3],
- Integer.parseInt(cooked[7]),
- Integer.parseInt(cooked[10]));
- } else if (code == ResponseCode.VolumeMountFailedBlank) {
- // FMT: NNN Volume <label> <mountpoint> mount failed - no supported file-systems
- mService.notifyMediaNoFs(cooked[3]);
- // FMT: NNN Volume <label> <mountpoint> mount failed - no media
- } else if (code == ResponseCode.VolumeMountFailedNoMedia) {
- mService.notifyMediaRemoved(cooked[3]);
- } else if (code == ResponseCode.VolumeMountFailedDamaged) {
- // FMT: NNN Volume <label> <mountpoint> mount failed - filesystem check failed
- mService.notifyMediaUnmountable(cooked[3]);
- } else if (code == ResponseCode.ShareAvailabilityChange) {
- // FMT: NNN Share method <method> now <available|unavailable>
- boolean avail = false;
- if (cooked[5].equals("available")) {
- avail = true;
- }
- mService.notifyShareAvailabilityChange(cooked[3], avail);
- } else if (code == ResponseCode.VolumeDiskInserted) {
- // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>)
- mService.notifyMediaInserted(cooked[3]);
- } else if (code == ResponseCode.VolumeDiskRemoved) {
- // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>)
- mService.notifyMediaRemoved(cooked[3]);
- } else if (code == ResponseCode.VolumeBadRemoval) {
- // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>)
- mService.notifyMediaBadRemoval(cooked[3]);
- } else {
- Log.w(TAG, "Unhandled event {" + raw + "}");
- }
- }
-
-
- private void sendCommand(String command) {
- sendCommand(command, null);
- }
-
- /**
- * Sends a command to Vold with a single argument
- *
- * @param command The command to send to the mount service daemon
- * @param argument The argument to send with the command (or null)
- */
- private void sendCommand(String command, String argument) {
- synchronized (this) {
- // Log.d(TAG, "sendCommand {" + command + "} {" + argument + "}");
- if (mOutputStream == null) {
- Log.e(TAG, "No connection to Vold", new IllegalStateException());
- } else {
- StringBuilder builder = new StringBuilder(command);
- if (argument != null) {
- builder.append(argument);
- }
- builder.append('\0');
-
- try {
- mOutputStream.write(builder.toString().getBytes());
- } catch (IOException ex) {
- Log.e(TAG, "IOException in sendCommand", ex);
- }
- }
- }
- }
-
- private synchronized ArrayList<String> doCommand(String cmd) throws IllegalStateException {
- sendCommand(cmd);
-
- ArrayList<String> response = new ArrayList<String>();
- boolean complete = false;
- int code = -1;
-
- while (!complete) {
- try {
- String line = mResponseQueue.take();
-// Log.d(TAG, "Removed off queue -> " + line);
- String[] tokens = line.split(" ");
- code = Integer.parseInt(tokens[0]);
-
- if ((code >= 200) && (code < 600))
- complete = true;
- response.add(line);
- } catch (InterruptedException ex) {
- Log.e(TAG, "InterruptedException");
- }
- }
-
- if (code >= 400 && code < 600) {
- throw new IllegalStateException(String.format(
- "Command %s failed with code %d",
- cmd, code));
- }
- return response;
- }
-
- boolean getShareAvailable(String method) throws IllegalStateException {
- ArrayList<String> rsp = doCommand("share_available " + method);
-
- for (String line : rsp) {
- String []tok = line.split(" ");
- int code = Integer.parseInt(tok[0]);
- if (code == ResponseCode.ShareAvailabilityResult) {
- if (tok[2].equals("available"))
- return true;
- return false;
- } else {
- throw new IllegalStateException(String.format("Unexpected response code %d", code));
- }
- }
- throw new IllegalStateException("Got an empty response");
- }
-
- /**
- * Enables or disables USB mass storage support.
- *
- * @param enable true to enable USB mass storage support
- */
- void setShareMethodEnabled(String mountPoint, String method,
- boolean enable) throws IllegalStateException {
- doCommand((enable ? "" : "un") + "share " + mountPoint + " " + method);
- }
-
- /**
- * Mount media at given mount point.
- */
- public void mountVolume(String label) throws IllegalStateException {
- doCommand("mount " + label);
- }
-
- /**
- * Unmount media at given mount point.
- */
- public void unmountVolume(String label) throws IllegalStateException {
- doCommand("unmount " + label);
- }
-
- /**
- * Format media at given mount point.
- */
- public void formatVolume(String label) throws IllegalStateException {
- doCommand("format " + label);
- }
-
- public String createAsec(String id, int sizeMb, String fstype, String key,
- int ownerUid) throws IllegalStateException {
- String cmd = String.format("create_asec %s %d %s %s %d",
- id, sizeMb, fstype, key, ownerUid);
- doCommand(cmd);
- return getAsecPath(id);
- }
-
- public void finalizeAsec(String id) throws IllegalStateException {
- doCommand("finalize_asec " + id);
- }
-
- public void destroyAsec(String id) throws IllegalStateException {
- doCommand("destroy_asec " + id);
- }
-
- public String mountAsec(String id, String key, int ownerUid) throws IllegalStateException {
- String cmd = String.format("mount_asec %s %s %d",
- id, key, ownerUid);
- doCommand(cmd);
- return getAsecPath(id);
- }
-
- public String getAsecPath(String id) throws IllegalStateException {
- ArrayList<String> rsp = doCommand("asec_path " + id);
-
- for (String line : rsp) {
- String []tok = line.split(" ");
- int code = Integer.parseInt(tok[0]);
- if (code == ResponseCode.AsecPathResult) {
- return tok[1];
- } else {
- throw new IllegalStateException(String.format("Unexpected response code %d", code));
- }
- }
- throw new IllegalStateException("Got an empty response");
- }
-
- public String[] listAsec() throws IllegalStateException {
- ArrayList<String> rsp = doCommand("list_asec");
-
- String[] rdata = new String[rsp.size()];
- int idx = 0;
-
- for (String line : rsp) {
- String []tok = line.split(" ");
- int code = Integer.parseInt(tok[0]);
- if (code == ResponseCode.AsecListResult) {
- rdata[idx++] = tok[1];
- } else if (code == ResponseCode.CommandOkay) {
- return rdata;
- } else {
- throw new IllegalStateException(String.format("Unexpected response code %d", code));
- }
- }
- throw new IllegalStateException("Got an empty response");
- }
-}
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index cd17bd2..c8a6915 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -33,6 +33,7 @@
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
+import java.util.ArrayList;
import android.provider.Settings;
import android.content.ContentResolver;
@@ -46,7 +47,8 @@
* MountService implements an to the mount service daemon
* @hide
*/
-class MountService extends IMountService.Stub {
+class MountService extends IMountService.Stub
+ implements INativeDaemonConnectorCallbacks {
private static final String TAG = "MountService";
@@ -63,15 +65,33 @@
public static final int SharedMnt = 8;
}
+ class VoldResponseCode {
+ public static final int VolumeListResult = 110;
+ public static final int AsecListResult = 111;
+
+ public static final int ShareAvailabilityResult = 210;
+ public static final int AsecPathResult = 211;
+
+ public static final int VolumeStateChange = 605;
+ public static final int VolumeMountFailedBlank = 610;
+ public static final int VolumeMountFailedDamaged = 611;
+ public static final int VolumeMountFailedNoMedia = 612;
+ public static final int ShareAvailabilityChange = 620;
+ public static final int VolumeDiskInserted = 630;
+ public static final int VolumeDiskRemoved = 631;
+ public static final int VolumeBadRemoval = 632;
+ }
+
+
/**
* Binder context for this service
*/
private Context mContext;
/**
- * listener object for communicating with the mount service daemon
+ * connectorr object for communicating with vold
*/
- private MountListener mListener;
+ private NativeDaemonConnector mConnector;
/**
* The notification that is shown when a USB mass storage host
@@ -119,12 +139,12 @@
mContext = context;
// Register a BOOT_COMPLETED handler so that we can start
- // MountListener. We defer the startup so that we don't
+ // our NativeDaemonConnector. We defer the startup so that we don't
// start processing events before we ought-to
mContext.registerReceiver(mBroadcastReceiver,
new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
- mListener = new MountListener(this);
+ mConnector = new NativeDaemonConnector(this, "vold", 10, "VoldConnector");
mShowSafeUnmountNotificationWhenUnmounted = false;
mPlaySounds = SystemProperties.get("persist.service.mount.playsnd", "1").equals("1");
@@ -202,7 +222,18 @@
String action = intent.getAction();
if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
- Thread thread = new Thread(mListener, MountListener.class.getName());
+ /*
+ * Vold does not run in the simulator, so fake out a mounted
+ * event to trigger MediaScanner
+ */
+ if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
+ notifyMediaMounted(
+ Environment.getExternalStorageDirectory().getPath(), false);
+ return;
+ }
+
+ Thread thread = new Thread(
+ mConnector, NativeDaemonConnector.class.getName());
thread.start();
}
}
@@ -215,7 +246,7 @@
throw new SecurityException("Requires SHUTDOWN permission");
}
- Log.i(TAG, "Shutting down");
+ Log.d(TAG, "Shutting down");
String state = Environment.getExternalStorageState();
if (state.equals(Environment.MEDIA_SHARED)) {
@@ -258,7 +289,21 @@
*/
try {
String m = Environment.getExternalStorageDirectory().toString();
- unmountMedia(m);
+ unmountVolume(m);
+
+ int retries = 12;
+ while (!state.equals(Environment.MEDIA_UNMOUNTED) && (retries-- >=0)) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException iex) {
+ Log.e(TAG, "Interrupted while waiting for media", iex);
+ break;
+ }
+ state = Environment.getExternalStorageState();
+ }
+ if (retries == 0) {
+ Log.e(TAG, "Timed out waiting for media to unmount");
+ }
} catch (Exception e) {
Log.e(TAG, "external storage unmount failed", e);
}
@@ -288,17 +333,14 @@
String vs = getVolumeState(vp);
if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
- mListener.unmountVolume(vp);
+ unmountVolume(vp);
updateUsbMassStorageNotification(true, false);
}
- mListener.setShareMethodEnabled(Environment
- .getExternalStorageDirectory()
- .getPath(),
- "ums", enable);
+ setShareMethodEnabled(vp, "ums", enable);
mUmsEnabled = enable;
if (!enable) {
- mountMedia(vp);
+ mountVolume(vp);
if (mPromptUms) {
updateUsbMassStorageNotification(false, false);
} else {
@@ -338,19 +380,19 @@
/**
* Attempt to mount external media
*/
- public void mountMedia(String mountPath) throws IllegalStateException {
+ public void mountVolume(String mountPath) throws IllegalStateException {
if (mContext.checkCallingOrSelfPermission(
android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires MOUNT_UNMOUNT_FILESYSTEMS permission");
}
- mListener.mountVolume(mountPath);
+ mConnector.doCommand(String.format("mount %s", mountPath));
}
/**
* Attempt to unmount external media to prepare for eject
*/
- public void unmountMedia(String mountPath) throws IllegalStateException {
+ public void unmountVolume(String mountPath) throws IllegalStateException {
if (mContext.checkCallingOrSelfPermission(
android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS)
!= PackageManager.PERMISSION_GRANTED) {
@@ -361,23 +403,51 @@
// to display the notification
mShowSafeUnmountNotificationWhenUnmounted = true;
- // tell mountd to unmount the media
- mListener.unmountVolume(mountPath);
+ mConnector.doCommand(String.format("unmount %s", mountPath));
}
/**
* Attempt to format external media
*/
- public void formatMedia(String formatPath) throws IllegalStateException {
+ public void formatVolume(String formatPath) throws IllegalStateException {
if (mContext.checkCallingOrSelfPermission(
android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires MOUNT_FORMAT_FILESYSTEMS permission");
}
- mListener.formatVolume(formatPath);
+ mConnector.doCommand(String.format("format %s", formatPath));
}
+ boolean getShareAvailable(String method) throws IllegalStateException {
+ ArrayList<String> rsp = mConnector.doCommand("share_available " + method);
+
+ for (String line : rsp) {
+ String []tok = line.split(" ");
+ int code = Integer.parseInt(tok[0]);
+ if (code == VoldResponseCode.ShareAvailabilityResult) {
+ if (tok[2].equals("available"))
+ return true;
+ return false;
+ } else {
+ throw new IllegalStateException(String.format("Unexpected response code %d", code));
+ }
+ }
+ throw new IllegalStateException("Got an empty response");
+ }
+
+ /**
+ * Enables or disables USB mass storage support.
+ *
+ * @param enable true to enable USB mass storage support
+ */
+ void setShareMethodEnabled(String mountPoint, String method,
+ boolean enable) throws IllegalStateException {
+ mConnector.doCommand(String.format(
+ "%sshare %s %s", (enable ? "" : "un"), mountPoint, method));
+ }
+
+
/**
* Returns true if we're playing media notification sounds.
*/
@@ -403,7 +473,7 @@
Log.w(TAG, "Multiple volumes not currently supported");
return;
}
- Log.w(TAG, "State for {" + mountPoint + "} = {" + state + "}");
+ Log.i(TAG, "State for {" + mountPoint + "} = {" + state + "}");
mLegacyState = state;
}
@@ -441,14 +511,18 @@
}
}
- void onVoldConnected() {
+ /**
+ *
+ * Callback from NativeDaemonConnector
+ */
+ public void onDaemonConnected() {
new Thread() {
public void run() {
try {
if (!getVolumeState(Environment.getExternalStorageDirectory().getPath())
.equals(Environment.MEDIA_MOUNTED)) {
try {
- mountMedia(Environment.getExternalStorageDirectory().getPath());
+ mountVolume(Environment.getExternalStorageDirectory().getPath());
} catch (Exception ex) {
Log.w(TAG, "Connection-mount failed");
}
@@ -460,7 +534,7 @@
}
try {
- boolean avail = mListener.getShareAvailable("ums");
+ boolean avail = getShareAvailable("ums");
notifyShareAvailabilityChange("ums", avail);
} catch (Exception ex) {
Log.w(TAG, "Failed to get share availability");
@@ -469,6 +543,49 @@
}.start();
}
+ /**
+ *
+ * Callback from NativeDaemonConnector
+ */
+ public boolean onEvent(int code, String raw, String[] cooked) {
+ // Log.d(TAG, "event {" + raw + "}");
+ if (code == VoldResponseCode.VolumeStateChange) {
+ // FMT: NNN Volume <label> <mountpoint> state changed
+ // from <old_#> (<old_str>) to <new_#> (<new_str>)
+ notifyVolumeStateChange(
+ cooked[2], cooked[3], Integer.parseInt(cooked[7]),
+ Integer.parseInt(cooked[10]));
+ } else if (code == VoldResponseCode.VolumeMountFailedBlank) {
+ // FMT: NNN Volume <label> <mountpoint> mount failed - no supported file-systems
+ notifyMediaNoFs(cooked[3]);
+ // FMT: NNN Volume <label> <mountpoint> mount failed - no media
+ } else if (code == VoldResponseCode.VolumeMountFailedNoMedia) {
+ notifyMediaRemoved(cooked[3]);
+ } else if (code == VoldResponseCode.VolumeMountFailedDamaged) {
+ // FMT: NNN Volume <label> <mountpoint> mount failed - filesystem check failed
+ notifyMediaUnmountable(cooked[3]);
+ } else if (code == VoldResponseCode.ShareAvailabilityChange) {
+ // FMT: NNN Share method <method> now <available|unavailable>
+ boolean avail = false;
+ if (cooked[5].equals("available")) {
+ avail = true;
+ }
+ notifyShareAvailabilityChange(cooked[3], avail);
+ } else if (code == VoldResponseCode.VolumeDiskInserted) {
+ // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>)
+ notifyMediaInserted(cooked[3]);
+ } else if (code == VoldResponseCode.VolumeDiskRemoved) {
+ // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>)
+ notifyMediaRemoved(cooked[3]);
+ } else if (code == VoldResponseCode.VolumeBadRemoval) {
+ // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>)
+ notifyMediaBadRemoval(cooked[3]);
+ } else {
+ return false;
+ }
+ return true;
+ }
+
void notifyVolumeStateChange(String label, String mountPoint, int oldState,
int newState) throws IllegalStateException {
String vs = getVolumeState(mountPoint);
@@ -573,7 +690,7 @@
new Thread() {
public void run() {
try {
- mountMedia(path);
+ mountVolume(path);
} catch (Exception ex) {
Log.w(TAG, "Failed to mount media on insertion", ex);
}
@@ -889,29 +1006,62 @@
}
public String[] getSecureContainerList() throws IllegalStateException {
- return mListener.listAsec();
+ ArrayList<String> rsp = mConnector.doCommand("list_asec");
+
+ String[] rdata = new String[rsp.size()];
+ int idx = 0;
+
+ for (String line : rsp) {
+ String []tok = line.split(" ");
+ int code = Integer.parseInt(tok[0]);
+ if (code == VoldResponseCode.AsecListResult) {
+ rdata[idx++] = tok[1];
+ } else if (code == NativeDaemonConnector.ResponseCode.CommandOkay) {
+ return rdata;
+ } else {
+ throw new IllegalStateException(String.format("Unexpected response code %d", code));
+ }
+ }
+ throw new IllegalStateException("Got an empty response");
}
public String createSecureContainer(String id, int sizeMb, String fstype,
String key, int ownerUid) throws IllegalStateException {
- return mListener.createAsec(id, sizeMb, fstype, key, ownerUid);
+ String cmd = String.format("create_asec %s %d %s %s %d",
+ id, sizeMb, fstype, key, ownerUid);
+ mConnector.doCommand(cmd);
+ return getSecureContainerPath(id);
}
public void finalizeSecureContainer(String id) throws IllegalStateException {
- mListener.finalizeAsec(id);
+ mConnector.doCommand(String.format("finalize_asec %s", id));
}
public void destroySecureContainer(String id) throws IllegalStateException {
- mListener.destroyAsec(id);
+ mConnector.doCommand(String.format("destroy_asec %s", id));
}
- public String mountSecureContainer(String id, String key, int ownerUid) throws IllegalStateException {
- return mListener.mountAsec(id, key, ownerUid);
+ public String mountSecureContainer(String id, String key,
+ int ownerUid) throws IllegalStateException {
+ String cmd = String.format("mount_asec %s %s %d",
+ id, key, ownerUid);
+ mConnector.doCommand(cmd);
+ return getSecureContainerPath(id);
}
public String getSecureContainerPath(String id) throws IllegalStateException {
- return mListener.getAsecPath(id);
- }
+ ArrayList<String> rsp = mConnector.doCommand("asec_path " + id);
+ for (String line : rsp) {
+ String []tok = line.split(" ");
+ int code = Integer.parseInt(tok[0]);
+ if (code == VoldResponseCode.AsecPathResult) {
+ return tok[1];
+ } else {
+ throw new IllegalStateException(String.format("Unexpected response code %d", code));
+ }
+ }
+ throw new IllegalStateException("Got an empty response");
+ }
}
diff --git a/services/java/com/android/server/NativeDaemonConnector.java b/services/java/com/android/server/NativeDaemonConnector.java
new file mode 100644
index 0000000..da3e562
--- /dev/null
+++ b/services/java/com/android/server/NativeDaemonConnector.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.net.LocalSocketAddress;
+import android.net.LocalSocket;
+import android.os.Environment;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Config;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.lang.IllegalStateException;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.ListIterator;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * Generic connector class for interfacing with a native
+ * daemon which uses the libsysutils FrameworkListener
+ * protocol.
+ */
+final class NativeDaemonConnector implements Runnable {
+
+ private BlockingQueue<String> mResponseQueue;
+ private OutputStream mOutputStream;
+ private String TAG = "NativeDaemonConnector";
+ private String mSocket;
+ private INativeDaemonConnectorCallbacks mCallbacks;
+
+ class ResponseCode {
+ public static final int ActionInitiated = 100;
+
+ public static final int CommandOkay = 200;
+
+ // The range of 400 -> 599 is reserved for cmd failures
+ public static final int OperationFailed = 400;
+ public static final int CommandSyntaxError = 500;
+ public static final int CommandParameterError = 501;
+
+ public static final int UnsolicitedInformational = 600;
+
+ //
+ public static final int FailedRangeStart = 400;
+ public static final int FailedRangeEnd = 599;
+ }
+
+ NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks,
+ String socket, int responseQueueSize, String logTag) {
+ mCallbacks = callbacks;
+ if (logTag != null)
+ TAG = logTag;
+ mSocket = socket;
+ mResponseQueue = new LinkedBlockingQueue<String>(responseQueueSize);
+ }
+
+ public void run() {
+
+ while (true) {
+ try {
+ listenToSocket();
+ } catch (Exception e) {
+ Log.e(TAG, "Error in NativeDaemonConnector", e);
+ SystemClock.sleep(1000);
+ }
+ }
+ }
+
+ private void listenToSocket() {
+ LocalSocket socket = null;
+
+ try {
+ socket = new LocalSocket();
+ LocalSocketAddress address = new LocalSocketAddress(mSocket,
+ LocalSocketAddress.Namespace.RESERVED);
+
+ socket.connect(address);
+ mCallbacks.onDaemonConnected();
+
+ InputStream inputStream = socket.getInputStream();
+ mOutputStream = socket.getOutputStream();
+
+ byte[] buffer = new byte[4096];
+
+ while (true) {
+ int count = inputStream.read(buffer);
+ if (count < 0) break;
+
+ int start = 0;
+ for (int i = 0; i < count; i++) {
+ if (buffer[i] == 0) {
+ String event = new String(buffer, start, i - start);
+// Log.d(TAG, "Got packet {" + event + "}");
+
+ String[] tokens = event.split(" ");
+ try {
+ int code = Integer.parseInt(tokens[0]);
+
+ if (code >= ResponseCode.UnsolicitedInformational) {
+ try {
+ if (!mCallbacks.onEvent(code, event, tokens)) {
+ Log.w(TAG, String.format(
+ "Unhandled event (%s)", event));
+ }
+ } catch (Exception ex) {
+ Log.e(TAG, String.format(
+ "Error handling '%s'", event), ex);
+ }
+ } else {
+ try {
+ mResponseQueue.put(event);
+ } catch (InterruptedException ex) {
+ Log.e(TAG, "Failed to put response onto queue", ex);
+ }
+ }
+ } catch (NumberFormatException nfe) {
+ Log.w(TAG, String.format("Bad msg (%s)", event));
+ }
+ start = i + 1;
+ }
+ }
+ }
+ } catch (IOException ex) {
+ Log.e(TAG, "Communications error", ex);
+ }
+
+ synchronized (this) {
+ if (mOutputStream != null) {
+ try {
+ mOutputStream.close();
+ } catch (IOException e) {
+ Log.w(TAG, "Failed closing output stream", e);
+ }
+
+ mOutputStream = null;
+ }
+ }
+
+ try {
+ if (socket != null) {
+ socket.close();
+ }
+ } catch (IOException ex) {
+ Log.w(TAG, "Failed closing socket", ex);
+ }
+
+ Log.e(TAG, "Failed to connect to native daemon",
+ new IllegalStateException());
+ SystemClock.sleep(5000);
+ }
+
+ private void sendCommand(String command) {
+ sendCommand(command, null);
+ }
+
+ /**
+ * Sends a command to the daemon with a single argument
+ *
+ * @param command The command to send to the daemon
+ * @param argument The argument to send with the command (or null)
+ */
+ private void sendCommand(String command, String argument) {
+ synchronized (this) {
+ Log.d(TAG, "sendCommand {" + command + "} {" + argument + "}");
+ if (mOutputStream == null) {
+ Log.e(TAG, "No connection to daemon", new IllegalStateException());
+ } else {
+ StringBuilder builder = new StringBuilder(command);
+ if (argument != null) {
+ builder.append(argument);
+ }
+ builder.append('\0');
+
+ try {
+ mOutputStream.write(builder.toString().getBytes());
+ } catch (IOException ex) {
+ Log.e(TAG, "IOException in sendCommand", ex);
+ }
+ }
+ }
+ }
+
+ public synchronized ArrayList<String> doCommand(String cmd) throws IllegalStateException {
+ sendCommand(cmd);
+
+ ArrayList<String> response = new ArrayList<String>();
+ boolean complete = false;
+ int code = -1;
+
+ while (!complete) {
+ try {
+ String line = mResponseQueue.take();
+// Log.d(TAG, "Removed off queue -> " + line);
+ String[] tokens = line.split(" ");
+ try {
+ code = Integer.parseInt(tokens[0]);
+ } catch (NumberFormatException nfe) {
+ throw new IllegalStateException(
+ String.format("Invalid response from daemon (%s)", line));
+ }
+
+ if ((code >= 200) && (code < 600))
+ complete = true;
+ response.add(line);
+ } catch (InterruptedException ex) {
+ Log.e(TAG, "InterruptedException");
+ }
+ }
+
+ if (code >= ResponseCode.FailedRangeStart &&
+ code <= ResponseCode.FailedRangeEnd) {
+ throw new IllegalStateException(String.format(
+ "Command %s failed with code %d",
+ cmd, code));
+ }
+ return response;
+ }
+}
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 2b12268..170477f 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -135,8 +135,8 @@
static final int SCAN_NO_DEX = 1<<1;
static final int SCAN_FORCE_DEX = 1<<2;
static final int SCAN_UPDATE_SIGNATURE = 1<<3;
- static final int SCAN_FORWARD_LOCKED = 1<<4;
- static final int SCAN_NEW_INSTALL = 1<<5;
+ static final int SCAN_NEW_INSTALL = 1<<4;
+ static final int SCAN_NO_PATHS = 1<<5;
final HandlerThread mHandlerThread = new HandlerThread("PackageManager",
Process.THREAD_PRIORITY_BACKGROUND);
@@ -281,8 +281,10 @@
final HashMap<String, ArrayList<String>> mPendingBroadcasts
= new HashMap<String, ArrayList<String>>();
static final int SEND_PENDING_BROADCAST = 1;
+ static final int DESTROY_SD_CONTAINER = 2;
// Delay time in millisecs
static final int BROADCAST_DELAY = 10 * 1000;
+ static final int DESTROY_SD_CONTAINER_DELAY = 30 * 1000;
class PackageHandler extends Handler {
PackageHandler(Looper looper) {
@@ -290,6 +292,15 @@
}
public void handleMessage(Message msg) {
switch (msg.what) {
+ case DESTROY_SD_CONTAINER:
+ String pkgName = (String) msg.obj;
+ if (pkgName != null) {
+ // Too bad we cannot handle the errors from destroying the containers.
+ if (!destroySdDir(pkgName)) {
+ Log.e(TAG, "Failed to destroy container for pkg : " + pkgName);
+ }
+ }
+ break;
case SEND_PENDING_BROADCAST : {
String packages[];
ArrayList components[];
@@ -444,7 +455,9 @@
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START,
startTime);
- int scanMode = SCAN_MONITOR;
+ // Set flag to monitor and not change apk file paths when
+ // scanning install directories.
+ int scanMode = SCAN_MONITOR | SCAN_NO_PATHS;
if (mNoDexOpt) {
Log.w(TAG, "Running ENG build: no pre-dexopt!");
scanMode |= SCAN_NO_DEX;
@@ -594,7 +607,7 @@
mDrmAppInstallObserver = new AppDirObserver(
mDrmAppPrivateInstallDir.getPath(), OBSERVER_EVENTS, false);
mDrmAppInstallObserver.startWatching();
- scanDirLI(mDrmAppPrivateInstallDir, 0, scanMode | SCAN_FORWARD_LOCKED);
+ scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK, scanMode);
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
SystemClock.uptimeMillis());
@@ -1961,12 +1974,7 @@
int i;
for (i=0; i<files.length; i++) {
File file = new File(dir, files[i]);
- File resFile = file;
- // Pick up the resource path from settings for fwd locked apps
- if ((scanMode & SCAN_FORWARD_LOCKED) != 0) {
- resFile = null;
- }
- PackageParser.Package pkg = scanPackageLI(file, file, resFile,
+ PackageParser.Package pkg = scanPackageLI(file,
flags|PackageParser.PARSE_MUST_BE_APK, scanMode);
}
}
@@ -2009,14 +2017,15 @@
* Returns null in case of errors and the error code is stored in mLastScanError
*/
private PackageParser.Package scanPackageLI(File scanFile,
- File destCodeFile, File destResourceFile, int parseFlags,
+ int parseFlags,
int scanMode) {
mLastScanError = PackageManager.INSTALL_SUCCEEDED;
parseFlags |= mDefParseFlags;
PackageParser pp = new PackageParser(scanFile.getPath());
pp.setSeparateProcesses(mSeparateProcesses);
final PackageParser.Package pkg = pp.parsePackage(scanFile,
- destCodeFile.getAbsolutePath(), mMetrics, parseFlags);
+ scanFile.getPath(),
+ mMetrics, parseFlags);
if (pkg == null) {
mLastScanError = pp.getParseError();
return null;
@@ -2062,16 +2071,12 @@
}
// The apk is forward locked (not public) if its code and resources
// are kept in different files.
+ // TODO grab this value from PackageSettings
if (ps != null && !ps.codePath.equals(ps.resourcePath)) {
- scanMode |= SCAN_FORWARD_LOCKED;
- }
- File resFile = destResourceFile;
- if (ps != null && ((scanMode & SCAN_FORWARD_LOCKED) != 0)) {
- resFile = getFwdLockedResource(ps.name);
+ parseFlags |= PackageParser.PARSE_FORWARD_LOCK;
}
// Note that we invoke the following method only if we are about to unpack an application
- return scanPackageLI(scanFile, destCodeFile, resFile,
- pkg, parseFlags, scanMode | SCAN_UPDATE_SIGNATURE);
+ return scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_SIGNATURE);
}
private static String fixProcessName(String defProcessName,
@@ -2138,7 +2143,7 @@
try {
if (forceDex || dalvik.system.DexFile.isDexOptNeeded(path)) {
ret = mInstaller.dexopt(path, pkg.applicationInfo.uid,
- !pkg.mForwardLocked);
+ !isForwardLocked(pkg));
pkg.mDidDexOpt = true;
performed = true;
}
@@ -2164,9 +2169,8 @@
}
private PackageParser.Package scanPackageLI(
- File scanFile, File destCodeFile, File destResourceFile,
PackageParser.Package pkg, int parseFlags, int scanMode) {
-
+ File scanFile = new File(pkg.mScanPath);
mScanningPath = scanFile;
if (pkg == null) {
mLastScanError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
@@ -2223,6 +2227,43 @@
return null;
}
+ // Initialize package source and resource directories
+ File destResourceFile = null;
+ File destCodeFile = null;
+ if ((scanMode & SCAN_NO_PATHS) == 0) {
+ boolean fwdLocked = (parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0;
+ final String pkgFileName = pkgName + ".apk";
+ File destDir = null;
+
+ if (fwdLocked) {
+ destDir = mDrmAppPrivateInstallDir;
+ destResourceFile = new File(mAppInstallDir, pkgName + ".zip");
+ } else {
+ boolean onSd = (parseFlags & PackageParser.PARSE_ON_SDCARD) != 0;
+ if (!onSd) {
+ destDir = mAppInstallDir;
+ } else {
+ String cachePath = getSdDir(pkgName);
+ if (cachePath == null) {
+ Log.e(TAG, "Secure container path for pkg: " + pkgName + " at location: " + cachePath +
+ " not found");
+ mLastScanError = PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
+ return null;
+ }
+ destDir = new File(cachePath);
+ }
+ destResourceFile = new File(destDir, pkgFileName);
+ }
+ destCodeFile = new File(destDir, pkgFileName);
+ pkg.mPath = destCodeFile.getAbsolutePath();
+ } else {
+ pkg.mPath = pkg.mScanPath;
+ destCodeFile = new File(pkg.mScanPath);
+ destResourceFile = new File(pkg.mScanPath);
+ }
+ pkg.applicationInfo.sourceDir = destCodeFile.getAbsolutePath();
+ pkg.applicationInfo.publicSourceDir = destResourceFile.getAbsolutePath();
+
SharedUserSetting suid = null;
PackageSetting pkgSetting = null;
@@ -2394,7 +2435,6 @@
pkg.applicationInfo.packageName,
pkg.applicationInfo.processName,
pkg.applicationInfo.uid);
- pkg.applicationInfo.publicSourceDir = destResourceFile.toString();
File dataPath;
if (mPlatformPackage == pkg) {
@@ -2509,8 +2549,6 @@
return null;
}
}
-
- pkg.mForwardLocked = (scanMode&SCAN_FORWARD_LOCKED) != 0;
pkg.mScanPath = path;
if ((scanMode&SCAN_NO_DEX) == 0) {
@@ -2545,7 +2583,7 @@
}
synchronized (mPackages) {
// Add the new setting to mSettings
- mSettings.insertPackageSettingLP(pkgSetting, pkg, destCodeFile, destResourceFile);
+ mSettings.insertPackageSettingLP(pkgSetting, pkg);
// Add the new setting to mPackages
mPackages.put(pkg.applicationInfo.packageName, pkg);
int N = pkg.providers.size();
@@ -3714,11 +3752,11 @@
if ((event&ADD_EVENTS) != 0) {
PackageParser.Package p = mAppDirs.get(fullPathStr);
if (p == null) {
- p = scanPackageLI(fullPath, fullPath, fullPath,
+ p = scanPackageLI(fullPath,
(mIsRom ? PackageParser.PARSE_IS_SYSTEM : 0) |
PackageParser.PARSE_CHATTY |
PackageParser.PARSE_MUST_BE_APK,
- SCAN_MONITOR);
+ SCAN_MONITOR | SCAN_NO_PATHS);
if (p != null) {
synchronized (mPackages) {
grantPermissionsLP(p, false);
@@ -3823,13 +3861,14 @@
/*
* Install a non-existing package.
*/
- private void installNewPackageLI(String pkgName,
- File tmpPackageFile,
- String destFilePath, File destPackageFile, File destResourceFile,
- PackageParser.Package pkg, boolean forwardLocked, boolean newInstall,
+ private void installNewPackageLI(PackageParser.Package pkg,
+ int parseFlags,
+ int scanMode,
String installerPackageName, PackageInstalledInfo res) {
// Remember this for later, in case we need to rollback this install
boolean dataDirExists;
+ String pkgName = pkg.packageName;
+ boolean onSd = (parseFlags & PackageParser.PARSE_ON_SDCARD) != 0;
if (useEncryptedFilesystemForPackage(pkg)) {
dataDirExists = (new File(mSecureAppDataDir, pkgName)).exists();
@@ -3838,7 +3877,7 @@
}
res.name = pkgName;
synchronized(mPackages) {
- if (mPackages.containsKey(pkgName) || mAppDirs.containsKey(destFilePath)) {
+ if (mPackages.containsKey(pkgName) || mAppDirs.containsKey(pkg.mPath)) {
// Don't allow installation over an existing package with the same name.
Log.w(TAG, "Attempt to re-install " + pkgName
+ " without first uninstalling.");
@@ -3846,32 +3885,37 @@
return;
}
}
- if (destPackageFile.exists()) {
- // It's safe to do this because we know (from the above check) that the file
- // isn't currently used for an installed package.
- destPackageFile.delete();
- }
mLastScanError = PackageManager.INSTALL_SUCCEEDED;
- PackageParser.Package newPackage = scanPackageLI(tmpPackageFile, destPackageFile,
- destResourceFile, pkg, 0,
- SCAN_MONITOR | SCAN_FORCE_DEX
- | SCAN_UPDATE_SIGNATURE
- | (forwardLocked ? SCAN_FORWARD_LOCKED : 0)
- | (newInstall ? SCAN_NEW_INSTALL : 0));
+ if (onSd) {
+ // Create secure container mount point for package
+ String cPath = createSdDir(new File(pkg.mScanPath), pkgName);
+ if (cPath == null) {
+ mLastScanError = res.returnCode = PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
+ return;
+ }
+ }
+ PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanMode);
if (newPackage == null) {
- Log.w(TAG, "Package couldn't be installed in " + destPackageFile);
+ Log.w(TAG, "Package couldn't be installed in " + pkg.mPath);
if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK;
}
} else {
- updateSettingsLI(pkgName, tmpPackageFile,
- destFilePath, destPackageFile,
- destResourceFile, pkg,
- newPackage,
- true,
- forwardLocked,
+ File destPackageFile = new File(pkg.mPath);
+ if (destPackageFile.exists()) {
+ // It's safe to do this because we know (from the above check) that the file
+ // isn't currently used for an installed package.
+ destPackageFile.delete();
+ }
+ updateSettingsLI(newPackage,
installerPackageName,
res);
+ if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
+ // Check if container can be finalized
+ if(onSd && !finalizeSdDir(pkgName)) {
+ res.returnCode = PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
+ }
+ }
// delete the partially installed application. the data directory will have to be
// restored if it was already existing
if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
@@ -3885,15 +3929,19 @@
res.removedInfo);
}
}
+ if (onSd && res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
+ // Destroy cache
+ destroySdDir(pkgName);
+ }
}
- private void replacePackageLI(String pkgName,
- File tmpPackageFile,
- String destFilePath, File destPackageFile, File destResourceFile,
- PackageParser.Package pkg, boolean forwardLocked, boolean newInstall,
+ private void replacePackageLI(PackageParser.Package pkg,
+ int parseFlags,
+ int scanMode,
String installerPackageName, PackageInstalledInfo res) {
PackageParser.Package oldPackage;
+ String pkgName = pkg.packageName;
// First find the old package info and check signatures
synchronized(mPackages) {
oldPackage = mPackages.get(pkgName);
@@ -3905,21 +3953,15 @@
}
boolean sysPkg = ((oldPackage.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
if(sysPkg) {
- replaceSystemPackageLI(oldPackage,
- tmpPackageFile, destFilePath,
- destPackageFile, destResourceFile, pkg, forwardLocked,
- newInstall, installerPackageName, res);
+ replaceSystemPackageLI(oldPackage, pkg, parseFlags, scanMode, installerPackageName, res);
} else {
- replaceNonSystemPackageLI(oldPackage, tmpPackageFile, destFilePath,
- destPackageFile, destResourceFile, pkg, forwardLocked,
- newInstall, installerPackageName, res);
+ replaceNonSystemPackageLI(oldPackage, pkg, parseFlags, scanMode, installerPackageName, res);
}
}
private void replaceNonSystemPackageLI(PackageParser.Package deletedPackage,
- File tmpPackageFile,
- String destFilePath, File destPackageFile, File destResourceFile,
- PackageParser.Package pkg, boolean forwardLocked, boolean newInstall,
+ PackageParser.Package pkg,
+ int parseFlags, int scanMode,
String installerPackageName, PackageInstalledInfo res) {
PackageParser.Package newPackage = null;
String pkgName = deletedPackage.packageName;
@@ -3931,7 +3973,7 @@
oldInstallerPackageName = mSettings.getInstallerPackageName(pkgName);
}
- int parseFlags = PackageManager.INSTALL_REPLACE_EXISTING;
+ parseFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
// First delete the existing package while retaining the data directory
if (!deletePackageLI(pkgName, false, PackageManager.DONT_DELETE_DATA,
res.removedInfo)) {
@@ -3941,24 +3983,14 @@
} else {
// Successfully deleted the old package. Now proceed with re-installation
mLastScanError = PackageManager.INSTALL_SUCCEEDED;
- newPackage = scanPackageLI(tmpPackageFile, destPackageFile,
- destResourceFile, pkg, parseFlags,
- SCAN_MONITOR | SCAN_FORCE_DEX
- | SCAN_UPDATE_SIGNATURE
- | (forwardLocked ? SCAN_FORWARD_LOCKED : 0)
- | (newInstall ? SCAN_NEW_INSTALL : 0));
+ newPackage = scanPackageLI(pkg, parseFlags, scanMode);
if (newPackage == null) {
- Log.w(TAG, "Package couldn't be installed in " + destPackageFile);
+ Log.w(TAG, "Package couldn't be installed in " + pkg.mPath);
if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK;
}
} else {
- updateSettingsLI(pkgName, tmpPackageFile,
- destFilePath, destPackageFile,
- destResourceFile, pkg,
- newPackage,
- true,
- forwardLocked,
+ updateSettingsLI(newPackage,
installerPackageName,
res);
updatedSettings = true;
@@ -4028,13 +4060,12 @@
}
private void replaceSystemPackageLI(PackageParser.Package deletedPackage,
- File tmpPackageFile,
- String destFilePath, File destPackageFile, File destResourceFile,
- PackageParser.Package pkg, boolean forwardLocked, boolean newInstall,
+ PackageParser.Package pkg,
+ int parseFlags, int scanMode,
String installerPackageName, PackageInstalledInfo res) {
PackageParser.Package newPackage = null;
boolean updatedSettings = false;
- int parseFlags = PackageManager.INSTALL_REPLACE_EXISTING |
+ parseFlags |= PackageManager.INSTALL_REPLACE_EXISTING |
PackageParser.PARSE_IS_SYSTEM;
String packageName = deletedPackage.packageName;
res.returnCode = PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE;
@@ -4064,26 +4095,14 @@
// Successfully disabled the old package. Now proceed with re-installation
mLastScanError = PackageManager.INSTALL_SUCCEEDED;
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
- newPackage = scanPackageLI(tmpPackageFile, destPackageFile,
- destResourceFile, pkg, parseFlags,
- SCAN_MONITOR | SCAN_FORCE_DEX
- | SCAN_UPDATE_SIGNATURE
- | (forwardLocked ? SCAN_FORWARD_LOCKED : 0)
- | (newInstall ? SCAN_NEW_INSTALL : 0));
+ newPackage = scanPackageLI(pkg, parseFlags, scanMode);
if (newPackage == null) {
- Log.w(TAG, "Package couldn't be installed in " + destPackageFile);
+ Log.w(TAG, "Package couldn't be installed in " + pkg.mPath);
if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK;
}
} else {
- updateSettingsLI(packageName, tmpPackageFile,
- destFilePath, destPackageFile,
- destResourceFile, pkg,
- newPackage,
- true,
- forwardLocked,
- installerPackageName,
- res);
+ updateSettingsLI(newPackage, installerPackageName, res);
updatedSettings = true;
}
@@ -4102,9 +4121,7 @@
removePackageLI(newPackage, true);
}
// Add back the old system package
- scanPackageLI(oldPkgSetting.codePath, oldPkgSetting.codePath,
- oldPkgSetting.resourcePath,
- oldPkg, parseFlags,
+ scanPackageLI(oldPkg, parseFlags,
SCAN_MONITOR
| SCAN_UPDATE_SIGNATURE);
// Restore the old system information in Settings
@@ -4119,14 +4136,9 @@
}
}
- private void updateSettingsLI(String pkgName, File tmpPackageFile,
- String destFilePath, File destPackageFile,
- File destResourceFile,
- PackageParser.Package pkg,
- PackageParser.Package newPackage,
- boolean replacingExistingPackage,
- boolean forwardLocked,
+ private void updateSettingsLI(PackageParser.Package newPackage,
String installerPackageName, PackageInstalledInfo res) {
+ String pkgName = newPackage.packageName;
synchronized (mPackages) {
//write settings. the installStatus will be incomplete at this stage.
//note that the new package setting would have already been
@@ -4136,11 +4148,10 @@
}
int retCode = 0;
- if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) {
- retCode = mInstaller.movedex(tmpPackageFile.toString(),
- destPackageFile.toString());
+ if ((newPackage.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) {
+ retCode = mInstaller.movedex(newPackage.mScanPath, newPackage.mPath);
if (retCode != 0) {
- Log.e(TAG, "Couldn't rename dex file: " + destPackageFile);
+ Log.e(TAG, "Couldn't rename dex file: " + newPackage.mPath);
res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
return;
}
@@ -4148,22 +4159,25 @@
// XXX There are probably some big issues here: upon doing
// the rename, we have reached the point of no return (the
// original .apk is gone!), so we can't fail. Yet... we can.
- if (!tmpPackageFile.renameTo(destPackageFile)) {
- Log.e(TAG, "Couldn't move package file to: " + destPackageFile);
- res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+ File scanFile = new File(newPackage.mScanPath);
+ if (!scanFile.renameTo(new File(newPackage.mPath))) {
+ Log.e(TAG, "Couldn't move package file: " + newPackage.mScanPath + " to: " + newPackage.mPath);
+ // TODO rename should work. Workaround
+ if (!FileUtils.copyFile(scanFile, new File(newPackage.mPath))) {
+ Log.e(TAG, "Couldn't move package file to: " + newPackage.mPath);
+ res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+ }
} else {
- res.returnCode = setPermissionsLI(pkgName, newPackage, destFilePath,
- destResourceFile,
- forwardLocked);
+ res.returnCode = setPermissionsLI(newPackage);
if(res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
return;
} else {
- Log.d(TAG, "New package installed in " + destPackageFile);
+ Log.d(TAG, "New package installed in " + newPackage.mPath);
}
}
if(res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
if (mInstaller != null) {
- mInstaller.rmdex(tmpPackageFile.getPath());
+ mInstaller.rmdex(newPackage.mScanPath);
}
}
@@ -4180,11 +4194,6 @@
}
}
- private File getFwdLockedResource(String pkgName) {
- final String publicZipFileName = pkgName + ".zip";
- return new File(mAppInstallDir, publicZipFileName);
- }
-
private File copyTempInstallFile(Uri pPackageURI,
PackageInstalledInfo res) {
File tmpPackageFile = createTempPackageFile();
@@ -4246,46 +4255,29 @@
private void installPackageLI(Uri pPackageURI,
int pFlags, boolean newInstall, String installerPackageName,
File tmpPackageFile, PackageInstalledInfo res) {
- String pkgName = null;
- boolean forwardLocked = false;
+ boolean forwardLocked = ((pFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0);
+ boolean onSd = ((pFlags & PackageManager.INSTALL_ON_SDCARD) != 0);
boolean replacingExistingPackage = false;
+ int scanMode = SCAN_MONITOR | SCAN_FORCE_DEX | SCAN_UPDATE_SIGNATURE
+ | (newInstall ? SCAN_NEW_INSTALL : 0);
// Result object to be returned
res.returnCode = PackageManager.INSTALL_SUCCEEDED;
main_flow: try {
- pkgName = PackageParser.parsePackageName(
- tmpPackageFile.getAbsolutePath(), 0);
- if (pkgName == null) {
- Log.e(TAG, "Couldn't find a package name in : " + tmpPackageFile);
- res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK;
- break main_flow;
- }
- res.name = pkgName;
- //initialize some variables before installing pkg
- final String pkgFileName = pkgName + ".apk";
- final File destDir = ((pFlags&PackageManager.INSTALL_FORWARD_LOCK) != 0)
- ? mDrmAppPrivateInstallDir
- : mAppInstallDir;
- final File destPackageFile = new File(destDir, pkgFileName);
- final String destFilePath = destPackageFile.getAbsolutePath();
- File destResourceFile;
- if ((pFlags&PackageManager.INSTALL_FORWARD_LOCK) != 0) {
- destResourceFile = getFwdLockedResource(pkgName);
- forwardLocked = true;
- } else {
- destResourceFile = destPackageFile;
- }
// Retrieve PackageSettings and parse package
- int parseFlags = PackageParser.PARSE_CHATTY;
+ int parseFlags = PackageParser.PARSE_CHATTY |
+ (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0) |
+ (onSd ? PackageParser.PARSE_ON_SDCARD : 0);
parseFlags |= mDefParseFlags;
PackageParser pp = new PackageParser(tmpPackageFile.getPath());
pp.setSeparateProcesses(mSeparateProcesses);
final PackageParser.Package pkg = pp.parsePackage(tmpPackageFile,
- destPackageFile.getAbsolutePath(), mMetrics, parseFlags);
+ null, mMetrics, parseFlags);
if (pkg == null) {
res.returnCode = pp.getParseError();
break main_flow;
}
+ String pkgName = res.name = pkg.packageName;
if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_TEST_ONLY) != 0) {
if ((pFlags&PackageManager.INSTALL_ALLOW_TEST) == 0) {
res.returnCode = PackageManager.INSTALL_FAILED_TEST_ONLY;
@@ -4306,17 +4298,11 @@
}
if(replacingExistingPackage) {
- replacePackageLI(pkgName,
- tmpPackageFile,
- destFilePath, destPackageFile, destResourceFile,
- pkg, forwardLocked, newInstall, installerPackageName,
- res);
+ replacePackageLI(pkg, parseFlags, scanMode,
+ installerPackageName, res);
} else {
- installNewPackageLI(pkgName,
- tmpPackageFile,
- destFilePath, destPackageFile, destResourceFile,
- pkg, forwardLocked, newInstall, installerPackageName,
- res);
+ installNewPackageLI(pkg, parseFlags, scanMode,
+ installerPackageName,res);
}
} finally {
if (tmpPackageFile != null && tmpPackageFile.exists()) {
@@ -4325,13 +4311,12 @@
}
}
- private int setPermissionsLI(String pkgName,
- PackageParser.Package newPackage,
- String destFilePath,
- File destResourceFile,
- boolean forwardLocked) {
+ private int setPermissionsLI(PackageParser.Package newPackage) {
+ String pkgName = newPackage.packageName;
int retCode;
- if (forwardLocked) {
+ if ((newPackage.applicationInfo.flags
+ & ApplicationInfo.FLAG_FORWARD_LOCK) != 0) {
+ File destResourceFile = new File(newPackage.applicationInfo.publicSourceDir);
try {
extractPublicFiles(newPackage, destResourceFile);
} catch (IOException e) {
@@ -4347,25 +4332,25 @@
} else {
final int filePermissions =
FileUtils.S_IRUSR|FileUtils.S_IWUSR|FileUtils.S_IRGRP;
- retCode = FileUtils.setPermissions(destFilePath, filePermissions, -1,
+ retCode = FileUtils.setPermissions(newPackage.mPath, filePermissions, -1,
newPackage.applicationInfo.uid);
}
} else {
final int filePermissions =
FileUtils.S_IRUSR|FileUtils.S_IWUSR|FileUtils.S_IRGRP
|FileUtils.S_IROTH;
- retCode = FileUtils.setPermissions(destFilePath, filePermissions, -1, -1);
+ retCode = FileUtils.setPermissions(newPackage.mPath, filePermissions, -1, -1);
}
if (retCode != 0) {
- Log.e(TAG, "Couldn't set new package file permissions for " + destFilePath
+ Log.e(TAG, "Couldn't set new package file permissions for " +
+ newPackage.mPath
+ ". The return code was: " + retCode);
}
return PackageManager.INSTALL_SUCCEEDED;
}
- private boolean isForwardLocked(PackageParser.Package deletedPackage) {
- final ApplicationInfo applicationInfo = deletedPackage.applicationInfo;
- return applicationInfo.sourceDir.startsWith(mDrmAppPrivateInstallDir.getAbsolutePath());
+ private boolean isForwardLocked(PackageParser.Package pkg) {
+ return ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0);
}
private void extractPublicFiles(PackageParser.Package newPackage,
@@ -4643,9 +4628,9 @@
mSettings.enableSystemPackageLP(p.packageName);
}
// Install the system package
- PackageParser.Package newPkg = scanPackageLI(ps.codePath, ps.codePath, ps.resourcePath,
+ PackageParser.Package newPkg = scanPackageLI(ps.codePath,
PackageParser.PARSE_MUST_BE_APK | PackageParser.PARSE_IS_SYSTEM,
- SCAN_MONITOR);
+ SCAN_MONITOR | SCAN_NO_PATHS);
if (newPkg == null) {
Log.w(TAG, "Failed to restore system package:"+p.packageName+" with error:" + mLastScanError);
@@ -4749,14 +4734,32 @@
Log.w(TAG, "Package " + p.packageName + " has no applicationInfo.");
return false;
}
+ boolean onSd = (p.applicationInfo.flags & ApplicationInfo.FLAG_ON_SDCARD) != 0;
+ // Mount sd container if needed
+ if (onSd) {
+ // TODO Better error handling from MountService api later
+ mountSdDir(p.packageName, Process.SYSTEM_UID) ;
+ }
+ boolean ret = false;
if ( (p.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
Log.i(TAG, "Removing system package:"+p.packageName);
// When an updated system application is deleted we delete the existing resources as well and
// fall back to existing code in system partition
- return deleteSystemPackageLI(p, flags, outInfo);
+ ret = deleteSystemPackageLI(p, flags, outInfo);
+ } else {
+ Log.i(TAG, "Removing non-system package:"+p.packageName);
+ ret = deleteInstalledPackageLI (p, deleteCodeAndResources, flags, outInfo);
}
- Log.i(TAG, "Removing non-system package:"+p.packageName);
- return deleteInstalledPackageLI (p, deleteCodeAndResources, flags, outInfo);
+ if (ret && onSd) {
+ // Post a delayed destroy on the container since there might
+ // be active processes holding open file handles to package
+ // resources which will get killed by the process killer when
+ // destroying the container. This might even kill the current
+ // process and crash the system. Delay the destroy a bit so
+ // that the active processes get to handle the uninstall broadcasts.
+ sendDelayedDestroySdDir(packageName);
+ }
+ return ret;
}
public void clearApplicationUserData(final String packageName,
@@ -6343,22 +6346,23 @@
return p;
}
- private void insertPackageSettingLP(PackageSetting p, PackageParser.Package pkg,
- File codePath, File resourcePath) {
+ private void insertPackageSettingLP(PackageSetting p, PackageParser.Package pkg) {
p.pkg = pkg;
+ String codePath = pkg.applicationInfo.sourceDir;
+ String resourcePath = pkg.applicationInfo.publicSourceDir;
// Update code path if needed
- if (!codePath.toString().equalsIgnoreCase(p.codePathString)) {
+ if (!codePath.equalsIgnoreCase(p.codePathString)) {
Log.w(TAG, "Code path for pkg : " + p.pkg.packageName +
" changing from " + p.codePathString + " to " + codePath);
- p.codePath = codePath;
- p.codePathString = codePath.toString();
+ p.codePath = new File(codePath);
+ p.codePathString = codePath;
}
//Update resource path if needed
- if (!resourcePath.toString().equalsIgnoreCase(p.resourcePathString)) {
+ if (!resourcePath.equalsIgnoreCase(p.resourcePathString)) {
Log.w(TAG, "Resource path for pkg : " + p.pkg.packageName +
" changing from " + p.resourcePathString + " to " + resourcePath);
- p.resourcePath = resourcePath;
- p.resourcePathString = resourcePath.toString();
+ p.resourcePath = new File(resourcePath);
+ p.resourcePathString = resourcePath;
}
// Update version code if needed
if (pkg.mVersionCode != p.versionCode) {
@@ -7432,4 +7436,110 @@
|| packageSettings.enabledComponents.contains(componentInfo.name));
}
}
+
+ // ------- apps on sdcard specific code -------
+ static final boolean DEBUG_SD_INSTALL = false;
+ final private String mSdEncryptKey = "none";
+
+ private MountService getMountService() {
+ return (MountService) ServiceManager.getService("mount");
+ }
+
+ private String createSdDir(File tmpPackageFile, String pkgName) {
+ // Create mount point via MountService
+ MountService mountService = getMountService();
+ long len = tmpPackageFile.length();
+ int mbLen = (int) (len/(1024*1024));
+ if ((len - (mbLen * 1024 * 1024)) > 0) {
+ mbLen++;
+ }
+ if (DEBUG_SD_INSTALL) Log.i(TAG, "mbLen="+mbLen);
+ String cachePath = null;
+ // Remove any pending destroy messages
+ mHandler.removeMessages(DESTROY_SD_CONTAINER, pkgName);
+ try {
+ cachePath = mountService.createSecureContainer(pkgName,
+ mbLen,
+ "vfat", mSdEncryptKey, Process.SYSTEM_UID);
+ if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to install " + pkgName + ", cachePath =" + cachePath);
+ return cachePath;
+ } catch(IllegalStateException e) {
+ Log.e(TAG, "Failed to create storage on sdcard with exception: " + e);
+ }
+ // TODO just fail here and let the user delete later on.
+ try {
+ mountService.destroySecureContainer(pkgName);
+ if (DEBUG_SD_INSTALL) Log.i(TAG, "Destroying cache for " + pkgName + ", cachePath =" + cachePath);
+ } catch(IllegalStateException e) {
+ Log.e(TAG, "Failed to destroy existing cache: " + e);
+ return null;
+ }
+ try {
+ cachePath = mountService.createSecureContainer(pkgName,
+ mbLen,
+ "vfat", mSdEncryptKey, Process.SYSTEM_UID);
+ if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to install again " + pkgName + ", cachePath =" + cachePath);
+ return cachePath;
+ } catch(IllegalStateException e) {
+ Log.e(TAG, "Failed to create storage on sdcard with exception: " + e);
+ return null;
+ }
+ }
+
+ private String mountSdDir(String pkgName, int ownerUid) {
+ try {
+ return getMountService().mountSecureContainer(pkgName, mSdEncryptKey, ownerUid);
+ } catch (IllegalStateException e) {
+ Log.i(TAG, "Failed to mount container for pkg : " + pkgName + " exception : " + e);
+ }
+ return null;
+ }
+
+ private String getSdDir(String pkgName) {
+ String cachePath = null;
+ try {
+ cachePath = getMountService().getSecureContainerPath(pkgName);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Failed to retrieve secure container path for pkg : " + pkgName + " with exception " + e);
+ }
+ return cachePath;
+ }
+
+ private boolean finalizeSdDir(String pkgName) {
+ try {
+ getMountService().finalizeSecureContainer(pkgName);
+ return true;
+ } catch (IllegalStateException e) {
+ Log.i(TAG, "Failed to destroy container for pkg : " + pkgName);
+ return false;
+ }
+ }
+
+ private boolean destroySdDir(String pkgName) {
+ try {
+ if (mHandler.hasMessages(DESTROY_SD_CONTAINER, pkgName)) {
+ // Don't have to send message again
+ mHandler.removeMessages(DESTROY_SD_CONTAINER, pkgName);
+ }
+ // We need to destroy right away
+ getMountService().destroySecureContainer(pkgName);
+ return true;
+ } catch (IllegalStateException e) {
+ Log.i(TAG, "Failed to destroy container for pkg : " + pkgName);
+ return false;
+ }
+ }
+
+ private void sendDelayedDestroySdDir(String pkgName) {
+ if (mHandler.hasMessages(DESTROY_SD_CONTAINER, pkgName)) {
+ // Don't have to send message again
+ return;
+ }
+ Message msg = mHandler.obtainMessage(DESTROY_SD_CONTAINER, pkgName);
+ mHandler.sendMessageDelayed(msg, DESTROY_SD_CONTAINER_DELAY);
+ }
+
+ public void updateExternalMediaStatus(boolean mediaStatus) {
+ // TODO
+ }
}
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index de3dcb0..d5de1f0 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -330,6 +330,17 @@
return false;
}
+ /**
+ * Multiple calls to unregisterReceiver() cause exception and a system crash.
+ * This can happen if a supplicant is lost (or firmware crash occurs) and user indicates
+ * disable wifi at the same time.
+ * Avoid doing a disable when the current Wifi state is UNKNOWN
+ * TODO: Handle driver load fail and supplicant lost as seperate states
+ */
+ if (mWifiState == WIFI_STATE_UNKNOWN && !enable) {
+ return false;
+ }
+
setWifiEnabledState(enable ? WIFI_STATE_ENABLING : WIFI_STATE_DISABLING, uid);
if (enable) {
diff --git a/services/java/com/android/server/status/StatusBarService.java b/services/java/com/android/server/status/StatusBarService.java
index 0a731a8..2d0f254 100644
--- a/services/java/com/android/server/status/StatusBarService.java
+++ b/services/java/com/android/server/status/StatusBarService.java
@@ -1496,6 +1496,7 @@
// lp.token = mStatusBarView.getWindowToken();
lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
lp.setTitle("TrackingView");
+ lp.y = mTrackingPosition;
mTrackingParams = lp;
WindowManagerImpl.getDefault().addView(mTrackingView, lp);
@@ -1575,8 +1576,11 @@
// Maybe the view was resized.
if (!mExpandedVisible) {
if (mTrackingView != null) {
- mTrackingPosition = mTrackingParams.y = -disph;
- WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams);
+ mTrackingPosition = -disph;
+ if (mTrackingParams != null) {
+ mTrackingParams.y = mTrackingPosition;
+ WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams);
+ }
}
if (mExpandedParams != null) {
mExpandedParams.y = -disph;
diff --git a/telephony/java/com/android/internal/telephony/Call.java b/telephony/java/com/android/internal/telephony/Call.java
index b95dd11..4967ab8 100644
--- a/telephony/java/com/android/internal/telephony/Call.java
+++ b/telephony/java/com/android/internal/telephony/Call.java
@@ -18,6 +18,8 @@
import java.util.List;
+import android.util.Log;
+
/**
* {@hide}
*/
@@ -54,6 +56,8 @@
// merged, etc.
protected boolean isGeneric = false;
+ protected final String LOG_TAG = "Call";
+
/* Instance Methods */
/** Do not modify the List result!!! This list is not yours to keep
@@ -235,4 +239,17 @@
public void setGeneric(boolean generic) {
isGeneric = generic;
}
+
+ /**
+ * Hangup call if it is alive
+ */
+ public void hangupIfAlive() {
+ if (getState().isAlive()) {
+ try {
+ hangup();
+ } catch (CallStateException ex) {
+ Log.w(LOG_TAG, " hangupIfActive: caught " + ex);
+ }
+ }
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/CallerInfo.java b/telephony/java/com/android/internal/telephony/CallerInfo.java
index 01b1746..cf89848 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfo.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfo.java
@@ -22,6 +22,7 @@
import android.net.Uri;
import android.provider.ContactsContract.PhoneLookup;
import android.provider.ContactsContract.CommonDataKinds.Phone;
+import static android.provider.ContactsContract.RawContacts;
import android.text.TextUtils;
import android.telephony.TelephonyManager;
import android.telephony.PhoneNumberUtils;
@@ -118,7 +119,6 @@
* number. The returned CallerInfo is null if no number is supplied.
*/
public static CallerInfo getCallerInfo(Context context, Uri contactRef, Cursor cursor) {
-
CallerInfo info = new CallerInfo();
info.photoResource = 0;
info.phoneLabel = null;
@@ -132,6 +132,9 @@
if (cursor != null) {
if (cursor.moveToFirst()) {
+ // TODO: photo_id is always available but not taken
+ // care of here. Maybe we should store it in the
+ // CallerInfo object as well.
int columnIndex;
@@ -160,10 +163,34 @@
}
}
- // Look for the person ID
- columnIndex = cursor.getColumnIndex(PhoneLookup._ID);
+ // Look for the person ID.
+
+ // TODO: This is pretty ugly now, see bug 2269240 for
+ // more details. With tel: URI the contact id is in
+ // col "_id" while when we use a
+ // content://contacts/data/phones URI, the contact id
+ // is col "contact_id". As a work around we use the
+ // type of the contact url to figure out which column
+ // we should look at to get the contact_id.
+
+ final String mimeType = context.getContentResolver().getType(contactRef);
+
+ columnIndex = -1;
+ if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ // content://com.android.contacts/data/phones URL
+ columnIndex = cursor.getColumnIndex(RawContacts.CONTACT_ID);
+ } else {
+ // content://com.android.contacts/phone_lookup URL
+ // TODO: mime type is null here so we cannot test
+ // if we have the right url type. phone_lookup URL
+ // should resolve to a mime type.
+ columnIndex = cursor.getColumnIndex(PhoneLookup._ID);
+ }
+
if (columnIndex != -1) {
info.person_id = cursor.getLong(columnIndex);
+ } else {
+ Log.e(TAG, "Column missing for " + contactRef);
}
// look for the custom ringtone, create from the string stored
diff --git a/telephony/java/com/android/internal/telephony/ServiceStateTracker.java b/telephony/java/com/android/internal/telephony/ServiceStateTracker.java
index 6892998..7383649 100644
--- a/telephony/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -271,6 +271,13 @@
protected abstract void updateSpnDisplay();
protected abstract void setPowerStateToDesired();
+ /**
+ * Clean up existing voice and data connection then turn off radio power.
+ *
+ * Hang up the existing voice calls to decrease call drop rate.
+ */
+ protected abstract void powerOffRadioSafely();
+
/** Cancel a pending (if any) pollState() operation */
protected void cancelPollState() {
// This will effectively cancel the rest of the poll requests.
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
index af3cbd5..42feaa9 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
@@ -514,7 +514,7 @@
synchronized(this) {
if (mPendingRadioPowerOffAfterDataOff) {
if (DBG) log("EVENT_SET_RADIO_OFF, turn radio off now.");
- cm.setRadioPower(false, null);
+ hangupAndPowerOff();
mPendingRadioPowerOffAfterDataOff = false;
}
}
@@ -541,32 +541,42 @@
dcTracker.getStateInString(),
dcTracker.getAnyDataEnabled() ? 1 : 0);
}
- Message msg = dcTracker.obtainMessage(DataConnectionTracker.EVENT_CLEAN_UP_CONNECTION);
- msg.arg1 = 1; // tearDown is true
- msg.obj = CDMAPhone.REASON_RADIO_TURNED_OFF;
- dcTracker.sendMessage(msg);
- synchronized(this) {
- if (!mPendingRadioPowerOffAfterDataOff) {
- DataConnectionTracker.State currentState = dcTracker.getState();
- if (currentState != DataConnectionTracker.State.CONNECTED
- && currentState != DataConnectionTracker.State.DISCONNECTING
- && currentState != DataConnectionTracker.State.INITING) {
- if (DBG) log("Data disconnected, turn off radio right away.");
- cm.setRadioPower(false, null);
+ // If it's on and available and we want it off gracefully
+ powerOffRadioSafely();
+ } // Otherwise, we're in the desired state
+ }
+
+ @Override
+ protected void powerOffRadioSafely(){
+ // clean data connection
+ DataConnectionTracker dcTracker = phone.mDataConnection;
+
+ Message msg = dcTracker.obtainMessage(DataConnectionTracker.EVENT_CLEAN_UP_CONNECTION);
+ msg.arg1 = 1; // tearDown is true
+ msg.obj = CDMAPhone.REASON_RADIO_TURNED_OFF;
+ dcTracker.sendMessage(msg);
+
+ synchronized(this) {
+ if (!mPendingRadioPowerOffAfterDataOff) {
+ DataConnectionTracker.State currentState = dcTracker.getState();
+ if (currentState != DataConnectionTracker.State.CONNECTED
+ && currentState != DataConnectionTracker.State.DISCONNECTING
+ && currentState != DataConnectionTracker.State.INITING) {
+ if (DBG) log("Data disconnected, turn off radio right away.");
+ hangupAndPowerOff();
+ }
+ else if (sendEmptyMessageDelayed(EVENT_SET_RADIO_POWER_OFF, 30000)) {
+ if (DBG) {
+ log("Wait up to 30 sec for data to disconnect, then turn off radio.");
}
- else if (sendEmptyMessageDelayed(EVENT_SET_RADIO_POWER_OFF, 30000)) {
- if (DBG) {
- log("Wait up to 30 sec for data to disconnect, then turn off radio.");
- }
- mPendingRadioPowerOffAfterDataOff = true;
- } else {
- Log.w(LOG_TAG, "Cannot send delayed Msg, turn off radio right away.");
- cm.setRadioPower(false, null);
- }
+ mPendingRadioPowerOffAfterDataOff = true;
+ } else {
+ Log.w(LOG_TAG, "Cannot send delayed Msg, turn off radio right away.");
+ hangupAndPowerOff();
}
}
- } // Otherwise, we're in the desired state
+ }
}
@Override
@@ -1644,11 +1654,19 @@
if (mPendingRadioPowerOffAfterDataOff) {
if (DBG) log("Process pending request to turn radio off.");
removeMessages(EVENT_SET_RADIO_POWER_OFF);
- cm.setRadioPower(false, null);
+ hangupAndPowerOff();
mPendingRadioPowerOffAfterDataOff = false;
return true;
}
return false;
}
}
+
+ private void hangupAndPowerOff() {
+ // hang up all active voice calls
+ phone.mCT.ringingCall.hangupIfAlive();
+ phone.mCT.backgroundCall.hangupIfAlive();
+ phone.mCT.foregroundCall.hangupIfAlive();
+ cm.setRadioPower(false, null);
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index 3f52eff..6695ccb 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -808,7 +808,7 @@
protected void restartRadio() {
Log.d(LOG_TAG, "************TURN OFF RADIO**************");
cleanUpConnection(true, Phone.REASON_RADIO_TURNED_OFF);
- phone.mCM.setRadioPower(false, null);
+ mGsmPhone.mSST.powerOffRadioSafely();
/* Note: no need to call setRadioPower(true). Assuming the desired
* radio power state is still ON (as tracked by ServiceStateTracker),
* ServiceStateTracker will call setRadioPower when it receives the
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
index c2c89c01..48e5c97 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
@@ -518,26 +518,41 @@
EventLog.writeEvent(TelephonyEventLog.EVENT_LOG_DATA_STATE_RADIO_OFF,
dcTracker.getStateInString(), dcTracker.getAnyDataEnabled() ? 1 : 0);
}
- Message msg = dcTracker.obtainMessage(DataConnectionTracker.EVENT_CLEAN_UP_CONNECTION);
- msg.arg1 = 1; // tearDown is true
- msg.obj = GSMPhone.REASON_RADIO_TURNED_OFF;
- dcTracker.sendMessage(msg);
-
- // poll data state up to 15 times, with a 100ms delay
- // totaling 1.5 sec. Normal data disable action will finish in 100ms.
- for (int i = 0; i < MAX_NUM_DATA_STATE_READS; i++) {
- if (dcTracker.getState() != DataConnectionTracker.State.CONNECTED
- && dcTracker.getState() != DataConnectionTracker.State.DISCONNECTING) {
- Log.d(LOG_TAG, "Data shutdown complete.");
- break;
- }
- SystemClock.sleep(DATA_STATE_POLL_SLEEP_MS);
- }
- // If it's on and available and we want it off..
- cm.setRadioPower(false, null);
+ // If it's on and available and we want it off gracefully
+ powerOffRadioSafely();
} // Otherwise, we're in the desired state
}
+ @Override
+ protected void powerOffRadioSafely() {
+ // clean data connection
+ DataConnectionTracker dcTracker = phone.mDataConnection;
+ Message msg = dcTracker.obtainMessage(DataConnectionTracker.EVENT_CLEAN_UP_CONNECTION);
+ msg.arg1 = 1; // tearDown is true
+ msg.obj = GSMPhone.REASON_RADIO_TURNED_OFF;
+ dcTracker.sendMessage(msg);
+
+ // poll data state up to 15 times, with a 100ms delay
+ // totaling 1.5 sec. Normal data disable action will finish in 100ms.
+ for (int i = 0; i < MAX_NUM_DATA_STATE_READS; i++) {
+ if (dcTracker.getState() != DataConnectionTracker.State.CONNECTED
+ && dcTracker.getState() != DataConnectionTracker.State.DISCONNECTING) {
+ Log.d(LOG_TAG, "Data shutdown complete.");
+ break;
+ }
+ SystemClock.sleep(DATA_STATE_POLL_SLEEP_MS);
+ }
+
+ // hang up all active voice calls
+ if (phone.isInCall()) {
+ phone.mCT.ringingCall.hangupIfAlive();
+ phone.mCT.backgroundCall.hangupIfAlive();
+ phone.mCT.foregroundCall.hangupIfAlive();
+ }
+
+ cm.setRadioPower(false, null);
+ }
+
protected void updateSpnDisplay() {
int rule = phone.mSIMRecords.getDisplayRule(ss.getOperatorNumeric());
String spn = phone.mSIMRecords.getServiceProviderName();
diff --git a/tests/AndroidTests/src/com/android/unit_tests/AndroidTests.java b/tests/AndroidTests/src/com/android/unit_tests/AndroidTests.java
deleted file mode 100644
index 4b86add..0000000
--- a/tests/AndroidTests/src/com/android/unit_tests/AndroidTests.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2005 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.unit_tests;
-
-import android.test.FrameworkTests;
-import android.test.suitebuilder.TestSuiteBuilder;
-
-import junit.framework.TestSuite;
-
-public class AndroidTests extends TestSuite {
-
- public static TestSuite suite() {
- TestSuiteBuilder suiteBuilder = new TestSuiteBuilder(AndroidTests.class);
- TestSuite suite = suiteBuilder.includeAllPackagesUnderHere().build();
-
- return suite;
- }
-}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/DatabaseCursorTest.java b/tests/AndroidTests/src/com/android/unit_tests/DatabaseCursorTest.java
deleted file mode 100644
index 5df499d..0000000
--- a/tests/AndroidTests/src/com/android/unit_tests/DatabaseCursorTest.java
+++ /dev/null
@@ -1,626 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.unit_tests;
-
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.CursorIndexOutOfBoundsException;
-import android.database.DataSetObserver;
-import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteCursor;
-import android.database.sqlite.SQLiteCursorDriver;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteQuery;
-import android.database.sqlite.SQLiteStatement;
-import android.os.Looper;
-import android.test.PerformanceTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.test.suitebuilder.annotation.Suppress;
-import android.util.Log;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.Random;
-
-import junit.framework.TestCase;
-
-public class DatabaseCursorTest extends TestCase implements PerformanceTestCase {
-
- private static final String sString1 = "this is a test";
- private static final String sString2 = "and yet another test";
- private static final String sString3 = "this string is a little longer, but still a test";
-
- private static final int CURRENT_DATABASE_VERSION = 42;
- private SQLiteDatabase mDatabase;
- private File mDatabaseFile;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mDatabaseFile = new File("/sqlite_stmt_journals", "database_test.db");
- if (mDatabaseFile.exists()) {
- mDatabaseFile.delete();
- }
- mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
- assertNotNull(mDatabase);
- mDatabase.setVersion(CURRENT_DATABASE_VERSION);
- }
-
- @Override
- protected void tearDown() throws Exception {
- mDatabase.close();
- mDatabaseFile.delete();
- super.tearDown();
- }
-
- public boolean isPerformanceOnly() {
- return false;
- }
-
- // These test can only be run once.
- public int startPerformance(Intermediates intermediates) {
- return 1;
- }
-
- private void populateDefaultTable() {
- mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);");
-
- mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString1 + "');");
- mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString2 + "');");
- mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString3 + "');");
- }
-
- @MediumTest
- public void testCursorUpdate() {
- mDatabase.execSQL(
- "CREATE TABLE test (_id INTEGER PRIMARY KEY, d INTEGER, s INTEGER);");
- for(int i = 0; i < 20; i++) {
- mDatabase.execSQL("INSERT INTO test (d, s) VALUES (" + i +
- "," + i%2 + ");");
- }
-
- Cursor c = mDatabase.query("test", null, "s = 0", null, null, null, null);
- int dCol = c.getColumnIndexOrThrow("d");
- int sCol = c.getColumnIndexOrThrow("s");
-
- int count = 0;
- while (c.moveToNext()) {
- assertTrue(c.updateInt(dCol, 3));
- count++;
- }
- assertEquals(10, count);
-
- assertTrue(c.commitUpdates());
-
- assertTrue(c.requery());
-
- count = 0;
- while (c.moveToNext()) {
- assertEquals(3, c.getInt(dCol));
- count++;
- }
-
- assertEquals(10, count);
- assertTrue(c.moveToFirst());
- assertTrue(c.deleteRow());
- assertEquals(9, c.getCount());
- c.close();
- }
-
- @MediumTest
- public void testBlob() throws Exception {
- // create table
- mDatabase.execSQL(
- "CREATE TABLE test (_id INTEGER PRIMARY KEY, s TEXT, d REAL, l INTEGER, b BLOB);");
- // insert blob
- Object[] args = new Object[4];
-
- byte[] blob = new byte[1000];
- byte value = 99;
- Arrays.fill(blob, value);
- args[3] = blob;
-
- String s = new String("text");
- args[0] = s;
- Double d = 99.9;
- args[1] = d;
- Long l = (long)1000;
- args[2] = l;
-
- String sql = "INSERT INTO test (s, d, l, b) VALUES (?,?,?,?)";
- mDatabase.execSQL(sql, args);
- // use cursor to access blob
- Cursor c = mDatabase.query("test", null, null, null, null, null, null);
- c.moveToNext();
- ContentValues cv = new ContentValues();
- DatabaseUtils.cursorRowToContentValues(c, cv);
-
- int bCol = c.getColumnIndexOrThrow("b");
- int sCol = c.getColumnIndexOrThrow("s");
- int dCol = c.getColumnIndexOrThrow("d");
- int lCol = c.getColumnIndexOrThrow("l");
- byte[] cBlob = c.getBlob(bCol);
- assertTrue(Arrays.equals(blob, cBlob));
- assertEquals(s, c.getString(sCol));
- assertEquals((double)d, c.getDouble(dCol));
- assertEquals((long)l, c.getLong(lCol));
-
- // new byte[]
- byte[] newblob = new byte[1000];
- value = 98;
- Arrays.fill(blob, value);
-
- c.updateBlob(bCol, newblob);
- cBlob = c.getBlob(bCol);
- assertTrue(Arrays.equals(newblob, cBlob));
-
- // commit
- assertTrue(c.commitUpdates());
- assertTrue(c.requery());
- c.moveToNext();
- cBlob = c.getBlob(bCol);
- assertTrue(Arrays.equals(newblob, cBlob));
- c.close();
- }
-
- @MediumTest
- public void testRealColumns() throws Exception {
- mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data REAL);");
- ContentValues values = new ContentValues();
- values.put("data", 42.11);
- long id = mDatabase.insert("test", "data", values);
- assertTrue(id > 0);
- Cursor c = mDatabase.rawQuery("SELECT data FROM test", null);
- assertNotNull(c);
- assertTrue(c.moveToFirst());
- assertEquals(42.11, c.getDouble(0));
- c.close();
- }
-
- @MediumTest
- public void testCursor1() throws Exception {
- populateDefaultTable();
-
- Cursor c = mDatabase.query("test", null, null, null, null, null, null);
-
- int dataColumn = c.getColumnIndexOrThrow("data");
-
- // The cursor should ignore text before the last period when looking for a column. (This
- // is a temporary hack in all implementations of getColumnIndex.)
- int dataColumn2 = c.getColumnIndexOrThrow("junk.data");
- assertEquals(dataColumn, dataColumn2);
-
- assertSame(3, c.getCount());
-
- assertTrue(c.isBeforeFirst());
-
- try {
- c.getInt(0);
- fail("CursorIndexOutOfBoundsException expected");
- } catch (CursorIndexOutOfBoundsException ex) {
- // expected
- }
-
- c.moveToNext();
- assertEquals(1, c.getInt(0));
-
- String s = c.getString(dataColumn);
- assertEquals(sString1, s);
-
- c.moveToNext();
- s = c.getString(dataColumn);
- assertEquals(sString2, s);
-
- c.moveToNext();
- s = c.getString(dataColumn);
- assertEquals(sString3, s);
-
- c.moveToPosition(-1);
- c.moveToNext();
- s = c.getString(dataColumn);
- assertEquals(sString1, s);
-
- c.moveToPosition(2);
- s = c.getString(dataColumn);
- assertEquals(sString3, s);
-
- int i;
-
- for (c.moveToFirst(), i = 0; !c.isAfterLast(); c.moveToNext(), i++) {
- c.getInt(0);
- }
-
- assertEquals(3, i);
-
- try {
- c.getInt(0);
- fail("CursorIndexOutOfBoundsException expected");
- } catch (CursorIndexOutOfBoundsException ex) {
- // expected
- }
- c.close();
- }
-
- @MediumTest
- public void testCursor2() throws Exception {
- populateDefaultTable();
-
- Cursor c = mDatabase.query("test", null, "_id > 1000", null, null, null, null);
- assertEquals(0, c.getCount());
- assertTrue(c.isBeforeFirst());
-
- try {
- c.getInt(0);
- fail("CursorIndexOutOfBoundsException expected");
- } catch (CursorIndexOutOfBoundsException ex) {
- // expected
- }
-
- int i;
- for (c.moveToFirst(), i = 0; !c.isAfterLast(); c.moveToNext(), i++) {
- c.getInt(0);
- }
- assertEquals(0, i);
- try {
- c.getInt(0);
- fail("CursorIndexOutOfBoundsException expected");
- } catch (CursorIndexOutOfBoundsException ex) {
- // expected
- }
- c.close();
- }
-
- @MediumTest
- public void testLargeField() throws Exception {
- mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);");
-
- StringBuilder sql = new StringBuilder(2100);
- sql.append("INSERT INTO test (data) VALUES ('");
- Random random = new Random(System.currentTimeMillis());
- StringBuilder randomString = new StringBuilder(1979);
- for (int i = 0; i < 1979; i++) {
- randomString.append((random.nextInt() & 0xf) % 10);
- }
- sql.append(randomString);
- sql.append("');");
- mDatabase.execSQL(sql.toString());
-
- Cursor c = mDatabase.query("test", null, null, null, null, null, null);
- assertNotNull(c);
- assertEquals(1, c.getCount());
-
- assertTrue(c.moveToFirst());
- assertEquals(0, c.getPosition());
- String largeString = c.getString(c.getColumnIndexOrThrow("data"));
- assertNotNull(largeString);
- assertEquals(randomString.toString(), largeString);
- c.close();
- }
-
- class TestObserver extends DataSetObserver {
- int total;
- SQLiteCursor c;
- boolean quit = false;
- public TestObserver(int total_, SQLiteCursor cursor) {
- c = cursor;
- total = total_;
- }
-
- @Override
- public void onChanged() {
- int count = c.getCount();
- if (total == count) {
- int i = 0;
- while (c.moveToNext()) {
- assertEquals(i, c.getInt(1));
- i++;
- }
- assertEquals(count, i);
- quit = true;
- Looper.myLooper().quit();
- }
- }
-
- @Override
- public void onInvalidated() {
- }
- }
-
- //@Large
- @Suppress
- public void testLoadingThreadDelayRegisterData() throws Exception {
- mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data INT);");
-
- final int count = 505;
- String sql = "INSERT INTO test (data) VALUES (?);";
- SQLiteStatement s = mDatabase.compileStatement(sql);
- for (int i = 0; i < count; i++) {
- s.bindLong(1, i);
- s.execute();
- }
-
- int maxRead = 500;
- int initialRead = 5;
- SQLiteCursor c = (SQLiteCursor)mDatabase.rawQuery("select * from test;",
- null, initialRead, maxRead);
-
- TestObserver observer = new TestObserver(count, c);
- c.getCount();
- c.registerDataSetObserver(observer);
- if (!observer.quit) {
- Looper.loop();
- }
- c.close();
- }
-
- @LargeTest
- public void testLoadingThread() throws Exception {
- mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data INT);");
-
- final int count = 50000;
- String sql = "INSERT INTO test (data) VALUES (?);";
- SQLiteStatement s = mDatabase.compileStatement(sql);
- for (int i = 0; i < count; i++) {
- s.bindLong(1, i);
- s.execute();
- }
-
- int maxRead = 1000;
- int initialRead = 5;
- SQLiteCursor c = (SQLiteCursor)mDatabase.rawQuery("select * from test;",
- null, initialRead, maxRead);
-
- TestObserver observer = new TestObserver(count, c);
- c.registerDataSetObserver(observer);
- c.getCount();
-
- Looper.loop();
- c.close();
- }
-
- @LargeTest
- public void testLoadingThreadClose() throws Exception {
- mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data INT);");
-
- final int count = 1000;
- String sql = "INSERT INTO test (data) VALUES (?);";
- SQLiteStatement s = mDatabase.compileStatement(sql);
- for (int i = 0; i < count; i++) {
- s.bindLong(1, i);
- s.execute();
- }
-
- int maxRead = 11;
- int initialRead = 5;
- SQLiteCursor c = (SQLiteCursor)mDatabase.rawQuery("select * from test;",
- null, initialRead, maxRead);
-
- TestObserver observer = new TestObserver(count, c);
- c.registerDataSetObserver(observer);
- c.getCount();
- c.close();
- }
-
- @LargeTest
- public void testLoadingThreadDeactivate() throws Exception {
- mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data INT);");
-
- final int count = 1000;
- String sql = "INSERT INTO test (data) VALUES (?);";
- SQLiteStatement s = mDatabase.compileStatement(sql);
- for (int i = 0; i < count; i++) {
- s.bindLong(1, i);
- s.execute();
- }
-
- int maxRead = 11;
- int initialRead = 5;
- SQLiteCursor c = (SQLiteCursor)mDatabase.rawQuery("select * from test;",
- null, initialRead, maxRead);
-
- TestObserver observer = new TestObserver(count, c);
- c.registerDataSetObserver(observer);
- c.getCount();
- c.deactivate();
- c.close();
- }
-
- @LargeTest
- public void testManyRowsLong() throws Exception {
- mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data INT);");
-
- final int count = 36799;
- for (int i = 0; i < count; i++) {
- mDatabase.execSQL("INSERT INTO test (data) VALUES (" + i + ");");
- }
-
- Cursor c = mDatabase.query("test", new String[]{"data"}, null, null, null, null, null);
- assertNotNull(c);
-
- int i = 0;
- while (c.moveToNext()) {
- assertEquals(i, c.getInt(0));
- i++;
- }
- assertEquals(count, i);
- assertEquals(count, c.getCount());
-
- Log.d("testManyRows", "count " + Integer.toString(i));
- c.close();
- }
-
- @LargeTest
- public void testManyRowsTxt() throws Exception {
- mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);");
- StringBuilder sql = new StringBuilder(2100);
- sql.append("INSERT INTO test (data) VALUES ('");
- Random random = new Random(System.currentTimeMillis());
- StringBuilder randomString = new StringBuilder(1979);
- for (int i = 0; i < 1979; i++) {
- randomString.append((random.nextInt() & 0xf) % 10);
- }
- sql.append(randomString);
- sql.append("');");
-
- // if cursor window size changed, adjust this value too
- final int count = 600; // more than two fillWindow needed
- for (int i = 0; i < count; i++) {
- mDatabase.execSQL(sql.toString());
- }
-
- Cursor c = mDatabase.query("test", new String[]{"data"}, null, null, null, null, null);
- assertNotNull(c);
-
- int i = 0;
- while (c.moveToNext()) {
- assertEquals(randomString.toString(), c.getString(0));
- i++;
- }
- assertEquals(count, i);
- assertEquals(count, c.getCount());
- c.close();
- }
-
- @LargeTest
- public void testManyRowsTxtLong() throws Exception {
- mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, txt TEXT, data INT);");
-
- Random random = new Random(System.currentTimeMillis());
- StringBuilder randomString = new StringBuilder(1979);
- for (int i = 0; i < 1979; i++) {
- randomString.append((random.nextInt() & 0xf) % 10);
- }
-
- // if cursor window size changed, adjust this value too
- final int count = 600;
- for (int i = 0; i < count; i++) {
- StringBuilder sql = new StringBuilder(2100);
- sql.append("INSERT INTO test (txt, data) VALUES ('");
- sql.append(randomString);
- sql.append("','");
- sql.append(i);
- sql.append("');");
- mDatabase.execSQL(sql.toString());
- }
-
- Cursor c = mDatabase.query("test", new String[]{"txt", "data"}, null, null, null, null, null);
- assertNotNull(c);
-
- int i = 0;
- while (c.moveToNext()) {
- assertEquals(randomString.toString(), c.getString(0));
- assertEquals(i, c.getInt(1));
- i++;
- }
- assertEquals(count, i);
- assertEquals(count, c.getCount());
- c.close();
- }
-
- @MediumTest
- public void testRequery() throws Exception {
- populateDefaultTable();
-
- Cursor c = mDatabase.rawQuery("SELECT * FROM test", null);
- assertNotNull(c);
- assertEquals(3, c.getCount());
- c.deactivate();
- c.requery();
- assertEquals(3, c.getCount());
- c.close();
- }
-
- @MediumTest
- public void testRequeryWithSelection() throws Exception {
- populateDefaultTable();
-
- Cursor c = mDatabase.rawQuery("SELECT data FROM test WHERE data = '" + sString1 + "'",
- null);
- assertNotNull(c);
- assertEquals(1, c.getCount());
- assertTrue(c.moveToFirst());
- assertEquals(sString1, c.getString(0));
- c.deactivate();
- c.requery();
- assertEquals(1, c.getCount());
- assertTrue(c.moveToFirst());
- assertEquals(sString1, c.getString(0));
- c.close();
- }
-
- @MediumTest
- public void testRequeryWithSelectionArgs() throws Exception {
- populateDefaultTable();
-
- Cursor c = mDatabase.rawQuery("SELECT data FROM test WHERE data = ?",
- new String[]{sString1});
- assertNotNull(c);
- assertEquals(1, c.getCount());
- assertTrue(c.moveToFirst());
- assertEquals(sString1, c.getString(0));
- c.deactivate();
- c.requery();
- assertEquals(1, c.getCount());
- assertTrue(c.moveToFirst());
- assertEquals(sString1, c.getString(0));
- c.close();
- }
-
- @MediumTest
- public void testRequeryWithAlteredSelectionArgs() throws Exception {
- /**
- * Test the ability of a subclass of SQLiteCursor to change its query arguments.
- */
- populateDefaultTable();
-
- SQLiteDatabase.CursorFactory factory = new SQLiteDatabase.CursorFactory() {
- public Cursor newCursor(
- SQLiteDatabase db, SQLiteCursorDriver masterQuery, String editTable,
- SQLiteQuery query) {
- return new SQLiteCursor(db, masterQuery, editTable, query) {
- @Override
- public boolean requery() {
- setSelectionArguments(new String[]{"2"});
- return super.requery();
- }
- };
- }
- };
- Cursor c = mDatabase.rawQueryWithFactory(
- factory, "SELECT data FROM test WHERE _id <= ?", new String[]{"1"},
- null);
- assertNotNull(c);
- assertEquals(1, c.getCount());
- assertTrue(c.moveToFirst());
- assertEquals(sString1, c.getString(0));
-
- // Our hacked requery() changes the query arguments in the cursor.
- c.requery();
-
- assertEquals(2, c.getCount());
- assertTrue(c.moveToFirst());
- assertEquals(sString1, c.getString(0));
- assertTrue(c.moveToNext());
- assertEquals(sString2, c.getString(0));
-
- // Test that setting query args on a deactivated cursor also works.
- c.deactivate();
- c.requery();
- }
-}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/DatabaseGeneralTest.java b/tests/AndroidTests/src/com/android/unit_tests/DatabaseGeneralTest.java
index 7a4d934..69d55c1 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/DatabaseGeneralTest.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/DatabaseGeneralTest.java
@@ -987,4 +987,18 @@
ih.close();
}
+ @MediumTest
+ public void testDbCloseReleasingAllCachedSql() {
+ mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, text1 TEXT, text2 TEXT, " +
+ "num1 INTEGER, num2 INTEGER, image BLOB);");
+ final String statement = "DELETE FROM test WHERE _id=?;";
+ SQLiteStatement statementDoNotClose = mDatabase.compileStatement(statement);
+ assertTrue(statementDoNotClose.getUniqueId() > 0);
+ int nStatement = statementDoNotClose.getUniqueId();
+ assertTrue(statementDoNotClose.getUniqueId() == nStatement);
+ /* do not close statementDoNotClose object.
+ * That should leave it in SQLiteDatabase.mPrograms.
+ * mDatabase.close() in tearDown() should release it.
+ */
+ }
}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/DatabaseStatementTest.java b/tests/AndroidTests/src/com/android/unit_tests/DatabaseStatementTest.java
deleted file mode 100644
index 16ca59f..0000000
--- a/tests/AndroidTests/src/com/android/unit_tests/DatabaseStatementTest.java
+++ /dev/null
@@ -1,320 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.unit_tests;
-
-import android.database.Cursor;
-import android.database.sqlite.SQLiteConstraintException;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteDoneException;
-import android.database.sqlite.SQLiteStatement;
-import android.test.PerformanceTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
-import junit.framework.TestCase;
-
-import java.io.File;
-
-public class DatabaseStatementTest extends TestCase implements PerformanceTestCase {
-
- private static final String sString1 = "this is a test";
- private static final String sString2 = "and yet another test";
- private static final String sString3 = "this string is a little longer, but still a test";
-
- private static final int CURRENT_DATABASE_VERSION = 42;
- private SQLiteDatabase mDatabase;
- private File mDatabaseFile;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mDatabaseFile = new File("/sqlite_stmt_journals", "database_test.db");
- if (mDatabaseFile.exists()) {
- mDatabaseFile.delete();
- }
- mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
- assertNotNull(mDatabase);
- mDatabase.setVersion(CURRENT_DATABASE_VERSION);
- }
-
- @Override
- protected void tearDown() throws Exception {
- mDatabase.close();
- mDatabaseFile.delete();
- super.tearDown();
- }
-
- public boolean isPerformanceOnly() {
- return false;
- }
-
- // These test can only be run once.
- public int startPerformance(Intermediates intermediates) {
- return 1;
- }
-
- private void populateDefaultTable() {
- mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);");
-
- mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString1 + "');");
- mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString2 + "');");
- mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString3 + "');");
- }
-
- @MediumTest
- public void testExecuteStatement() throws Exception {
- populateDefaultTable();
- SQLiteStatement statement = mDatabase.compileStatement("DELETE FROM test");
- statement.execute();
-
- Cursor c = mDatabase.query("test", null, null, null, null, null, null);
- assertEquals(0, c.getCount());
- c.deactivate();
- statement.close();
- }
-
- @MediumTest
- public void testSimpleQuery() throws Exception {
- mDatabase.execSQL("CREATE TABLE test (num INTEGER NOT NULL, str TEXT NOT NULL);");
- mDatabase.execSQL("INSERT INTO test VALUES (1234, 'hello');");
- SQLiteStatement statement1 =
- mDatabase.compileStatement("SELECT num FROM test WHERE str = ?");
- SQLiteStatement statement2 =
- mDatabase.compileStatement("SELECT str FROM test WHERE num = ?");
-
- try {
- statement1.bindString(1, "hello");
- long value = statement1.simpleQueryForLong();
- assertEquals(1234, value);
-
- statement1.bindString(1, "world");
- statement1.simpleQueryForLong();
- fail("shouldn't get here");
- } catch (SQLiteDoneException e) {
- // expected
- }
-
- try {
- statement2.bindLong(1, 1234);
- String value = statement1.simpleQueryForString();
- assertEquals("hello", value);
-
- statement2.bindLong(1, 5678);
- statement1.simpleQueryForString();
- fail("shouldn't get here");
- } catch (SQLiteDoneException e) {
- // expected
- }
-
- statement1.close();
- statement2.close();
- }
-
- @MediumTest
- public void testStatementLongBinding() throws Exception {
- mDatabase.execSQL("CREATE TABLE test (num INTEGER);");
- SQLiteStatement statement = mDatabase.compileStatement("INSERT INTO test (num) VALUES (?)");
-
- for (int i = 0; i < 10; i++) {
- statement.bindLong(1, i);
- statement.execute();
- }
- statement.close();
-
- Cursor c = mDatabase.query("test", null, null, null, null, null, null);
- int numCol = c.getColumnIndexOrThrow("num");
- c.moveToFirst();
- for (long i = 0; i < 10; i++) {
- long num = c.getLong(numCol);
- assertEquals(i, num);
- c.moveToNext();
- }
- c.close();
- }
-
- @MediumTest
- public void testStatementStringBinding() throws Exception {
- mDatabase.execSQL("CREATE TABLE test (num TEXT);");
- SQLiteStatement statement = mDatabase.compileStatement("INSERT INTO test (num) VALUES (?)");
-
- for (long i = 0; i < 10; i++) {
- statement.bindString(1, Long.toHexString(i));
- statement.execute();
- }
- statement.close();
-
- Cursor c = mDatabase.query("test", null, null, null, null, null, null);
- int numCol = c.getColumnIndexOrThrow("num");
- c.moveToFirst();
- for (long i = 0; i < 10; i++) {
- String num = c.getString(numCol);
- assertEquals(Long.toHexString(i), num);
- c.moveToNext();
- }
- c.close();
- }
-
- @MediumTest
- public void testStatementClearBindings() throws Exception {
- mDatabase.execSQL("CREATE TABLE test (num INTEGER);");
- SQLiteStatement statement = mDatabase.compileStatement("INSERT INTO test (num) VALUES (?)");
-
- for (long i = 0; i < 10; i++) {
- statement.bindLong(1, i);
- statement.clearBindings();
- statement.execute();
- }
- statement.close();
-
- Cursor c = mDatabase.query("test", null, null, null, null, null, "ROWID");
- int numCol = c.getColumnIndexOrThrow("num");
- assertTrue(c.moveToFirst());
- for (long i = 0; i < 10; i++) {
- assertTrue(c.isNull(numCol));
- c.moveToNext();
- }
- c.close();
- }
-
- @MediumTest
- public void testSimpleStringBinding() throws Exception {
- mDatabase.execSQL("CREATE TABLE test (num TEXT, value TEXT);");
- String statement = "INSERT INTO test (num, value) VALUES (?,?)";
-
- String[] args = new String[2];
- for (int i = 0; i < 2; i++) {
- args[i] = Integer.toHexString(i);
- }
-
- mDatabase.execSQL(statement, args);
-
- Cursor c = mDatabase.query("test", null, null, null, null, null, null);
- int numCol = c.getColumnIndexOrThrow("num");
- int valCol = c.getColumnIndexOrThrow("value");
- c.moveToFirst();
- String num = c.getString(numCol);
- assertEquals(Integer.toHexString(0), num);
-
- String val = c.getString(valCol);
- assertEquals(Integer.toHexString(1), val);
- c.close();
- }
-
- @MediumTest
- public void testStatementMultipleBindings() throws Exception {
- mDatabase.execSQL("CREATE TABLE test (num INTEGER, str TEXT);");
- SQLiteStatement statement =
- mDatabase.compileStatement("INSERT INTO test (num, str) VALUES (?, ?)");
-
- for (long i = 0; i < 10; i++) {
- statement.bindLong(1, i);
- statement.bindString(2, Long.toHexString(i));
- statement.execute();
- }
- statement.close();
-
- Cursor c = mDatabase.query("test", null, null, null, null, null, "ROWID");
- int numCol = c.getColumnIndexOrThrow("num");
- int strCol = c.getColumnIndexOrThrow("str");
- assertTrue(c.moveToFirst());
- for (long i = 0; i < 10; i++) {
- long num = c.getLong(numCol);
- String str = c.getString(strCol);
- assertEquals(i, num);
- assertEquals(Long.toHexString(i), str);
- c.moveToNext();
- }
- c.close();
- }
-
- private static class StatementTestThread extends Thread {
- private SQLiteDatabase mDatabase;
- private SQLiteStatement mStatement;
-
- public StatementTestThread(SQLiteDatabase db, SQLiteStatement statement) {
- super();
- mDatabase = db;
- mStatement = statement;
- }
-
- @Override
- public void run() {
- mDatabase.beginTransaction();
- for (long i = 0; i < 10; i++) {
- mStatement.bindLong(1, i);
- mStatement.bindString(2, Long.toHexString(i));
- mStatement.execute();
- }
- mDatabase.setTransactionSuccessful();
- mDatabase.endTransaction();
-
- Cursor c = mDatabase.query("test", null, null, null, null, null, "ROWID");
- int numCol = c.getColumnIndexOrThrow("num");
- int strCol = c.getColumnIndexOrThrow("str");
- assertTrue(c.moveToFirst());
- for (long i = 0; i < 10; i++) {
- long num = c.getLong(numCol);
- String str = c.getString(strCol);
- assertEquals(i, num);
- assertEquals(Long.toHexString(i), str);
- c.moveToNext();
- }
- c.close();
- }
- }
-
- @MediumTest
- public void testStatementMultiThreaded() throws Exception {
- mDatabase.execSQL("CREATE TABLE test (num INTEGER, str TEXT);");
- SQLiteStatement statement =
- mDatabase.compileStatement("INSERT INTO test (num, str) VALUES (?, ?)");
-
- StatementTestThread thread = new StatementTestThread(mDatabase, statement);
- thread.start();
- try {
- thread.join();
- } finally {
- statement.close();
- }
- }
-
- @MediumTest
- public void testStatementConstraint() throws Exception {
- mDatabase.execSQL("CREATE TABLE test (num INTEGER NOT NULL);");
- SQLiteStatement statement = mDatabase.compileStatement("INSERT INTO test (num) VALUES (?)");
-
- // Try to insert NULL, which violates the constraint
- try {
- statement.clearBindings();
- statement.execute();
- fail("expected exception not thrown");
- } catch (SQLiteConstraintException e) {
- // expected
- }
-
- // Make sure the statement can still be used
- statement.bindLong(1, 1);
- statement.execute();
- statement.close();
-
- Cursor c = mDatabase.query("test", null, null, null, null, null, null);
- int numCol = c.getColumnIndexOrThrow("num");
- c.moveToFirst();
- long num = c.getLong(numCol);
- assertEquals(1, num);
- c.close();
- }
-}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/DatabaseTests.java b/tests/AndroidTests/src/com/android/unit_tests/DatabaseTests.java
index a7a1400..a288c73 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/DatabaseTests.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/DatabaseTests.java
@@ -23,8 +23,6 @@
TestSuite suite = new TestSuite(DatabaseTests.class.getName());
suite.addTestSuite(DatabaseGeneralTest.class);
- suite.addTestSuite(DatabaseCursorTest.class);
- suite.addTestSuite(DatabaseStatementTest.class);
suite.addTestSuite(DatabaseLocaleTest.class);
suite.addTestSuite(CursorWindowTest.class);
suite.addTestSuite(DatabaseLockTest.class);
diff --git a/tests/AndroidTests/src/com/android/unit_tests/NeighboringCellInfoTest.java b/tests/AndroidTests/src/com/android/unit_tests/NeighboringCellInfoTest.java
deleted file mode 100644
index 7252aa9..0000000
--- a/tests/AndroidTests/src/com/android/unit_tests/NeighboringCellInfoTest.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.unit_tests;
-
-import android.os.Parcel;
-import android.test.AndroidTestCase;
-import android.telephony.NeighboringCellInfo;
-import android.test. suitebuilder.annotation.SmallTest;
-
-import static android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN;
-import static android.telephony.TelephonyManager.NETWORK_TYPE_EDGE;
-import static android.telephony.TelephonyManager.NETWORK_TYPE_GPRS;
-import static android.telephony.TelephonyManager.NETWORK_TYPE_UMTS;
-
-public class NeighboringCellInfoTest extends AndroidTestCase {
- @SmallTest
- public void testConstructor() {
- int rssi = 31;
- NeighboringCellInfo nc;
-
- nc = new NeighboringCellInfo(rssi, "FFFFFFF", NETWORK_TYPE_EDGE);
- assertEquals(NETWORK_TYPE_EDGE, nc.getNetworkType());
- assertEquals(rssi, nc.getRssi());
- assertEquals(0xfff, nc.getLac());
- assertEquals(0xffff, nc.getCid());
- assertEquals(NeighboringCellInfo.UNKNOWN_CID, nc.getPsc());
-
- nc = new NeighboringCellInfo(rssi, "1FF", NETWORK_TYPE_UMTS);
- assertEquals(NETWORK_TYPE_UMTS, nc.getNetworkType());
- assertEquals(rssi, nc.getRssi());
- assertEquals(NeighboringCellInfo.UNKNOWN_CID, nc.getCid());
- assertEquals(NeighboringCellInfo.UNKNOWN_CID, nc.getLac());
- assertEquals(0x1ff, nc.getPsc());
-
- nc = new NeighboringCellInfo(rssi, "1FF", NETWORK_TYPE_UNKNOWN);
- assertEquals(NETWORK_TYPE_UNKNOWN, nc.getNetworkType());
- assertEquals(rssi, nc.getRssi());
- assertEquals(NeighboringCellInfo.UNKNOWN_CID, nc.getCid());
- assertEquals(NeighboringCellInfo.UNKNOWN_CID, nc.getLac());
- assertEquals(NeighboringCellInfo.UNKNOWN_CID, nc.getPsc());
- }
-
- @SmallTest
- public void testParcel() {
- int rssi = 20;
-
- NeighboringCellInfo nc = new NeighboringCellInfo(rssi, "12345678", NETWORK_TYPE_GPRS);
- assertEquals(NETWORK_TYPE_GPRS, nc.getNetworkType());
- assertEquals(rssi, nc.getRssi());
- assertEquals(0x1234, nc.getLac());
- assertEquals(0x5678, nc.getCid());
- assertEquals(NeighboringCellInfo.UNKNOWN_CID, nc.getPsc());
-
- Parcel p = Parcel.obtain();
- p.setDataPosition(0);
- nc.writeToParcel(p, 0);
-
- p.setDataPosition(0);
- NeighboringCellInfo nw = new NeighboringCellInfo(p);
- assertEquals(NETWORK_TYPE_GPRS, nw.getNetworkType());
- assertEquals(rssi, nw.getRssi());
- assertEquals(0x1234, nw.getLac());
- assertEquals(0x5678, nw.getCid());
- assertEquals(NeighboringCellInfo.UNKNOWN_CID, nw.getPsc());
- }
-}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/content/ArrayTest.java b/tests/AndroidTests/src/com/android/unit_tests/content/ArrayTest.java
deleted file mode 100644
index 4d5b5e7..0000000
--- a/tests/AndroidTests/src/com/android/unit_tests/content/ArrayTest.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.unit_tests.content;
-
-import android.content.res.Resources;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.TypedValue;
-import com.android.unit_tests.R;
-
-public class ArrayTest extends AndroidTestCase {
- private Resources mResources;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mResources = mContext.getResources();
- }
-
- private void checkEntry(int resid, int index, Object res, Object expected) {
- assertEquals("in resource 0x" + Integer.toHexString(resid)
- + " at index " + index, expected, res);
- }
-
- private void checkStringArray(int resid, String[] expected) {
- String[] res = mResources.getStringArray(resid);
- assertEquals(res.length, expected.length);
- for (int i=0; i<expected.length; i++) {
- checkEntry(resid, i, res[i], expected[i]);
- }
- }
-
- private void checkTextArray(int resid, String[] expected) {
- CharSequence[] res = mResources.getTextArray(resid);
- assertEquals(res.length, expected.length);
- for (int i=0; i<expected.length; i++) {
- checkEntry(resid, i, res[i], expected[i]);
- }
- }
-
- private void checkIntArray(int resid, int[] expected) {
- int[] res = mResources.getIntArray(resid);
- assertEquals(res.length, expected.length);
- for (int i=0; i<expected.length; i++) {
- assertEquals("in resource 0x" + Integer.toHexString(resid)
- + " at index " + i, expected[i], res[i]);
- }
- }
-
- @SmallTest
- public void testStrings() throws Exception {
- checkStringArray(R.array.strings, new String[] {"zero", "1", "here"});
- checkTextArray(R.array.strings, new String[] {"zero", "1", "here"});
- checkStringArray(R.array.integers, new String[] {null, null, null});
- checkTextArray(R.array.integers, new String[] {null, null, null});
- }
-
- @SmallTest
- public void testIntegers() throws Exception {
- checkIntArray(R.array.strings, new int[] {0, 0, 0});
- checkIntArray(R.array.integers, new int[] {0, 1, 101});
- }
-}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/content/ConfigTest.java b/tests/AndroidTests/src/com/android/unit_tests/content/ConfigTest.java
deleted file mode 100644
index a065d70..0000000
--- a/tests/AndroidTests/src/com/android/unit_tests/content/ConfigTest.java
+++ /dev/null
@@ -1,540 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.unit_tests.content;
-
-import android.content.Context;
-import android.content.res.AssetManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.DisplayMetrics;
-import android.view.Display;
-import android.view.WindowManager;
-import com.android.unit_tests.R;
-
-import java.util.Locale;
-
-public class ConfigTest extends AndroidTestCase {
- enum properties {
- LANGUAGE,
- COUNTRY,
- MCC,
- MNC,
- TOUCHSCREEN,
- KEYBOARD,
- KEYBOARDHIDDEN,
- NAVIGATION,
- ORIENTATION,
- WIDTH,
- HEIGHT,
- DENSITY
- }
-
- private static void checkValue(Resources res, int resId, String expectedValue) {
- try {
- String actual = res.getString(resId);
- assertNotNull("Returned wrong configuration-based simple value: expected <nothing>, got '"
- + actual + "' from resource 0x"
- + Integer.toHexString(resId),
- expectedValue);
- assertEquals("Returned wrong configuration-based simple value: expected '"
- + expectedValue + "', got '" + actual + "' from resource 0x"
- + Integer.toHexString(resId),
- expectedValue, actual);
- } catch (Resources.NotFoundException e) {
- assertNull("Resource not found for configuration-based simple value: expecting \""
- + expectedValue + "\"",
- expectedValue);
- }
- }
-
- private static void checkValue(Resources res, int resId,
- int[] styleable, String[] expectedValues) {
- Resources.Theme theme = res.newTheme();
- TypedArray sa = theme.obtainStyledAttributes(resId, styleable);
- for (int i = 0; i < styleable.length; i++) {
- String actual = sa.getString(i);
- assertEquals("Returned wrong configuration-based style value: expected '"
- + expectedValues[i] + "', got '" + actual + "' from attr "
- + i + " of resource 0x" + Integer.toHexString(resId),
- actual, expectedValues[i]);
- }
- sa.recycle();
- }
-
- class TotalConfig {
- Configuration mConfig;
- DisplayMetrics mMetrics;
-
- TotalConfig() {
- mConfig = new Configuration();
- // don't rely on build settings - they may change
- mConfig.locale = new Locale("en", "US");
- mConfig.mcc = 310;
- mConfig.mnc = 001; // unused
- mConfig.touchscreen = Configuration.TOUCHSCREEN_FINGER;
- mConfig.keyboard = Configuration.KEYBOARD_QWERTY;
- mConfig.keyboardHidden = Configuration.KEYBOARDHIDDEN_YES;
- mConfig.navigation = Configuration.NAVIGATION_TRACKBALL;
- mConfig.orientation = Configuration.ORIENTATION_PORTRAIT;
-
- mMetrics = new DisplayMetrics();
- mMetrics.widthPixels = 200;
- mMetrics.heightPixels = 320;
- mMetrics.density = 1;
- }
-
- void setProperty(properties p, int value) {
- switch(p) {
- case MCC:
- mConfig.mcc = value;
- break;
- case MNC:
- mConfig.mnc = value;
- break;
- case TOUCHSCREEN:
- mConfig.touchscreen = value;
- break;
- case KEYBOARD:
- mConfig.keyboard = value;
- break;
- case KEYBOARDHIDDEN:
- mConfig.keyboardHidden = value;
- break;
- case NAVIGATION:
- mConfig.navigation = value;
- break;
- case ORIENTATION:
- mConfig.orientation = value;
- break;
- case WIDTH:
- mMetrics.widthPixels = value;
- break;
- case HEIGHT:
- mMetrics.heightPixels = value;
- break;
- case DENSITY:
- // this is the ratio from the standard
-
- mMetrics.density = (((float)value)/((float)DisplayMetrics.DENSITY_DEFAULT));
- break;
- default:
- assert(false);
- break;
- }
- }
-
- public void setProperty(properties p, String value) {
- switch(p) {
- case LANGUAGE:
- String oldCountry = mConfig.locale.getCountry();
- mConfig.locale = new Locale(value, oldCountry);
- break;
- case COUNTRY:
- String oldLanguage = mConfig.locale.getLanguage();
- mConfig.locale = new Locale(oldLanguage, value);
- break;
- default:
- assert(false);
- break;
- }
- }
-
- public Resources getResources() {
- AssetManager assmgr = new AssetManager();
- assmgr.addAssetPath(mContext.getPackageResourcePath());
- return new Resources(assmgr, mMetrics, mConfig);
- }
- }
-
- private static void checkPair(Resources res, int[] notResIds,
- int simpleRes, String simpleString,
- int bagRes, String bagString) {
- boolean willHave = true;
- if (notResIds != null) {
- for (int i : notResIds) {
- if (i == simpleRes) {
- willHave = false;
- break;
- }
- }
- }
- checkValue(res, simpleRes, willHave ? simpleString : null);
- checkValue(res, bagRes, R.styleable.TestConfig,
- new String[]{willHave ? bagString : null});
- }
-
- @SmallTest
- public void testAllConfigs() throws Exception {
- /**
- * Test a resource that contains a value for each possible single
- * configuration value.
- */
- TotalConfig config = new TotalConfig();
- Resources res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple default");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag default"});
-
- config = new TotalConfig();
- config.setProperty(properties.LANGUAGE, "xx");
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple xx");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag xx"});
-
- config = new TotalConfig();
- config.setProperty(properties.LANGUAGE, "xx");
- config.setProperty(properties.COUNTRY, "YY");
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple xx-rYY");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag xx-rYY"});
-
- config = new TotalConfig();
- config.setProperty(properties.MCC, 111);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple mcc111");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag mcc111"});
-
- config = new TotalConfig();
- config.setProperty(properties.MNC, 222);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple mnc222");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag mnc222"});
-
- config = new TotalConfig();
- config.setProperty(properties.TOUCHSCREEN, Configuration.TOUCHSCREEN_NOTOUCH);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple notouch");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag notouch"});
-
- config = new TotalConfig();
- config.setProperty(properties.TOUCHSCREEN, Configuration.TOUCHSCREEN_STYLUS);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple stylus");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag stylus"});
-
- config = new TotalConfig();
- config.setProperty(properties.KEYBOARD, Configuration.KEYBOARD_NOKEYS);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple nokeys");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag nokeys"});
-
- config = new TotalConfig();
- config.setProperty(properties.KEYBOARD, Configuration.KEYBOARD_12KEY);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple 12key");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag 12key"});
-
- config = new TotalConfig();
- config.setProperty(properties.KEYBOARDHIDDEN, Configuration.KEYBOARDHIDDEN_NO);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple keysexposed");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag keysexposed"});
-
- config = new TotalConfig();
- config.setProperty(properties.NAVIGATION, Configuration.NAVIGATION_NONAV);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple nonav");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag nonav"});
-
- config = new TotalConfig();
- config.setProperty(properties.NAVIGATION, Configuration.NAVIGATION_DPAD);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple dpad");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag dpad"});
-
- config = new TotalConfig();
- config.setProperty(properties.NAVIGATION, Configuration.NAVIGATION_WHEEL);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple wheel");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag wheel"});
-
- config = new TotalConfig();
- config.setProperty(properties.HEIGHT, 480);
- config.setProperty(properties.WIDTH, 320);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple 480x320");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag 480x320"});
-
- config = new TotalConfig();
- config.setProperty(properties.DENSITY, 240);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple 240dpi");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag 240dpi"});
-
- config = new TotalConfig();
- config.setProperty(properties.ORIENTATION, Configuration.ORIENTATION_LANDSCAPE);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple landscape");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag landscape"});
-
- config = new TotalConfig();
- config.setProperty(properties.ORIENTATION, Configuration.ORIENTATION_SQUARE);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple square");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag square"});
- }
-
- @MediumTest
- public void testDensity() throws Exception {
- // have 32, 240 and the default 160 content.
- // rule is that closest wins, with down scaling (larger content)
- // being twice as nice as upscaling.
- // transition at H/2 * (-1 +/- sqrt(1+8L/H))
- // SO, X < 49 goes to 32
- // 49 >= X < 182 goes to 160
- // X >= 182 goes to 240
- TotalConfig config = new TotalConfig();
- config.setProperty(properties.DENSITY, 2);
- Resources res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple 32dpi");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag 32dpi"});
-
- config = new TotalConfig();
- config.setProperty(properties.DENSITY, 32);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple 32dpi");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag 32dpi"});
-
- config = new TotalConfig();
- config.setProperty(properties.DENSITY, 48);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple 32dpi");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag 32dpi"});
-
- config = new TotalConfig();
- config.setProperty(properties.DENSITY, 49);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple default");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag default"});
-
- config = new TotalConfig();
- config.setProperty(properties.DENSITY, 150);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple default");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag default"});
-
- config = new TotalConfig();
- config.setProperty(properties.DENSITY, 181);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple default");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag default"});
-
- config = new TotalConfig();
- config.setProperty(properties.DENSITY, 182);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple 240dpi");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag 240dpi"});
-
- config = new TotalConfig();
- config.setProperty(properties.DENSITY, 239);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple 240dpi");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag 240dpi"});
-
- config = new TotalConfig();
- config.setProperty(properties.DENSITY, 490);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple 240dpi");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag 240dpi"});
- }
-
-// TODO - add tests for special cases - ie, other key params seem ignored if
-// nokeys is set
-
- @MediumTest
- public void testCombinations() throws Exception {
- /**
- * Verify that proper strings are found for multiple-selectivity case
- * (ie, a string set for locale and mcc is found only when both are
- * true).
- */
- TotalConfig config = new TotalConfig();
- config.setProperty(properties.LANGUAGE, "xx");
- config.setProperty(properties.COUNTRY, "YY");
- config.setProperty(properties.MCC, 111);
- Resources res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple mcc111 xx-rYY");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag mcc111 xx-rYY"});
-
- config = new TotalConfig();
- config.setProperty(properties.LANGUAGE, "xx");
- config.setProperty(properties.COUNTRY, "YY");
- config.setProperty(properties.MCC, 333);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple xx-rYY");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag xx-rYY"});
-
- config = new TotalConfig();
- config.setProperty(properties.MNC, 333);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple default");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag default"});
- }
-
- @MediumTest
- public void testPrecidence() throws Exception {
- /**
- * Verify that in cases of ties, the specific ordering is followed
- */
-
- /**
- * Precidence order: mcc, mnc, locale, orientation, density,
- * touchscreen, hidden, keyboard, navigation, width-height
- */
-
- /**
- * verify mcc trumps mnc. Have 110-xx, 220-xx but no 110-220
- * so with is selected? Should be mcc110-xx.
- */
- TotalConfig config = new TotalConfig();
- config.setProperty(properties.MCC, 110);
- config.setProperty(properties.MNC, 220);
- config.setProperty(properties.LANGUAGE, "xx");
- Resources res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple mcc110 xx");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag mcc110 xx"});
-
- /* full A + B + C doesn't exist. Do we get A + C or B + C?
- */
- config = new TotalConfig();
- config.setProperty(properties.MCC, 111);
- config.setProperty(properties.MNC, 222);
- config.setProperty(properties.LANGUAGE, "xx");
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple mcc111 mnc222");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag mcc111 mnc222"});
-
- config = new TotalConfig();
- config.setProperty(properties.MNC, 222);
- config.setProperty(properties.LANGUAGE, "xx");
- config.setProperty(properties.ORIENTATION,
- Configuration.ORIENTATION_SQUARE);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple mnc222 xx");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag mnc222 xx"});
-
- config = new TotalConfig();
- config.setProperty(properties.LANGUAGE, "xx");
- config.setProperty(properties.ORIENTATION,
- Configuration.ORIENTATION_SQUARE);
- config.setProperty(properties.DENSITY, 32);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple xx square");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag xx square"});
-
- config = new TotalConfig();
- config.setProperty(properties.ORIENTATION,
- Configuration.ORIENTATION_SQUARE);
- config.setProperty(properties.DENSITY, 32);
- config.setProperty(properties.TOUCHSCREEN,
- Configuration.TOUCHSCREEN_STYLUS);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple square 32dpi");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag square 32dpi"});
-
- config = new TotalConfig();
- config.setProperty(properties.DENSITY, 32);
- config.setProperty(properties.TOUCHSCREEN,
- Configuration.TOUCHSCREEN_STYLUS);
- config.setProperty(properties.KEYBOARDHIDDEN,
- Configuration.KEYBOARDHIDDEN_NO);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple 32dpi stylus");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag 32dpi stylus"});
-
- config = new TotalConfig();
- config.setProperty(properties.TOUCHSCREEN,
- Configuration.TOUCHSCREEN_STYLUS);
- config.setProperty(properties.KEYBOARDHIDDEN,
- Configuration.KEYBOARDHIDDEN_NO);
- config.setProperty(properties.KEYBOARD, Configuration.KEYBOARD_12KEY);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple stylus keysexposed");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag stylus keysexposed"});
-
- config = new TotalConfig();
- config.setProperty(properties.KEYBOARDHIDDEN,
- Configuration.KEYBOARDHIDDEN_NO);
- config.setProperty(properties.KEYBOARD, Configuration.KEYBOARD_12KEY);
- config.setProperty(properties.NAVIGATION,
- Configuration.NAVIGATION_DPAD);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple keysexposed 12key");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag keysexposed 12key"});
-
- config = new TotalConfig();
- config.setProperty(properties.KEYBOARD, Configuration.KEYBOARD_12KEY);
- config.setProperty(properties.NAVIGATION,
- Configuration.NAVIGATION_DPAD);
- config.setProperty(properties.HEIGHT, 63);
- config.setProperty(properties.WIDTH, 57);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple 12key dpad");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag 12key dpad"});
-
- config = new TotalConfig();
- config.setProperty(properties.NAVIGATION,
- Configuration.NAVIGATION_DPAD);
- config.setProperty(properties.HEIGHT, 640);
- config.setProperty(properties.WIDTH, 400);
- res = config.getResources();
- checkValue(res, R.configVarying.simple, "simple dpad");
- checkValue(res, R.configVarying.bag,
- R.styleable.TestConfig, new String[]{"bag dpad"});
- }
-}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/content/ContentTests.java b/tests/AndroidTests/src/com/android/unit_tests/content/ContentTests.java
index 80318dc..636660f 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/content/ContentTests.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/content/ContentTests.java
@@ -23,10 +23,6 @@
TestSuite suite = new TestSuite(ContentTests.class.getName());
suite.addTestSuite(AssetTest.class);
- suite.addTestSuite(IntentFilterTest.class);
- suite.addTest(ResourceTests.suite());
- suite.addTestSuite(PluralResourcesTest.class);
- suite.addTestSuite(ConfigTest.class);
return suite;
}
}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/content/FractionTest.java b/tests/AndroidTests/src/com/android/unit_tests/content/FractionTest.java
deleted file mode 100644
index 74a6b8d..0000000
--- a/tests/AndroidTests/src/com/android/unit_tests/content/FractionTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.unit_tests.content;
-
-import android.content.res.Resources;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.TypedValue;
-import com.android.unit_tests.R;
-
-public class FractionTest extends AndroidTestCase {
-
- private Resources mResources;
- private final TypedValue mValue = new TypedValue();
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mResources = mContext.getResources();
- }
-
- @SmallTest
- public void testFractions() throws Exception {
- tryFraction(R.dimen.frac100perc, 1, 1, 1);
- tryFraction(R.dimen.frac1perc, 1, 1, .01f);
- tryFraction(R.dimen.fracp1perc, 1, 1, .001f);
- tryFraction(R.dimen.fracp01perc, 1, 1, .0001f);
- tryFraction(R.dimen.frac0perc, 1, 1, 0);
- tryFraction(R.dimen.frac1p1perc, 1, 1, .011f);
- tryFraction(R.dimen.frac100p1perc, 1, 1, 1.001f);
- tryFraction(R.dimen.frac25510perc, 1, 1, 255.1f);
- tryFraction(R.dimen.frac25610perc, 1, 1, 256.1f);
- tryFraction(R.dimen.frac6553510perc, 1, 1, 65535.1f);
- tryFraction(R.dimen.frac6553610perc, 1, 1, 65536.1f);
-
- tryFraction(R.dimen.frac100perc, 100, 1, 100);
- tryFraction(R.dimen.frac1perc, 100, 1, .01f * 100);
- tryFraction(R.dimen.fracp1perc, 100, 1, .001f * 100);
- tryFraction(R.dimen.fracp01perc, 100, 1, .0001f * 100);
- tryFraction(R.dimen.frac0perc, 100, 1, 0);
- tryFraction(R.dimen.frac1p1perc, 100, 1, .011f * 100);
- tryFraction(R.dimen.frac100p1perc, 100, 1, 1.001f * 100);
- tryFraction(R.dimen.frac25510perc, 100, 1, 255.1f * 100);
- tryFraction(R.dimen.frac25610perc, 100, 1, 256.1f * 100);
- tryFraction(R.dimen.frac6553510perc, 100, 1, 65535.1f * 100);
- tryFraction(R.dimen.frac6553610perc, 100, 1, 65536.1f * 100);
-
- tryFraction(R.dimen.frac100pperc, 100, 2, 2);
- tryFraction(R.dimen.frac1pperc, 100, 2, .01f * 2);
- tryFraction(R.dimen.fracp1pperc, 100, 2, .001f * 2);
- tryFraction(R.dimen.fracp01pperc, 100, 2, .0001f * 2);
- tryFraction(R.dimen.frac0pperc, 100, 2, 0);
- tryFraction(R.dimen.frac1p1pperc, 100, 2, .011f * 2);
- tryFraction(R.dimen.frac100p1pperc, 100, 2, 1.001f * 2);
- tryFraction(R.dimen.frac25510pperc, 100, 2, 255.1f * 2);
- tryFraction(R.dimen.frac25610pperc, 100, 2, 256.1f * 2);
- tryFraction(R.dimen.frac6553510pperc, 100, 2, 65535.1f * 2);
- tryFraction(R.dimen.frac6553610pperc, 100, 2, 65536.1f * 2);
- }
-
- private void tryFraction(int resid, float base, float pbase, float expected) {
- mResources.getValue(resid, mValue, true);
- float res = mValue.getFraction(base, pbase);
- float diff = Math.abs(expected - res);
- float prec = expected * 1e-4f;
- if (prec < 1e-5f) {
- prec = 1e-5f;
- }
- //System.out.println(
- // "Res 0x" + Integer.toHexString(resid) + ": got=" + res
- // + ", expected=" + expected + ", diff=" + diff);
- assertFalse("Expecting value " + expected + " got " + res
- + ": in resource 0x" + Integer.toHexString(resid)
- + " " + mValue,
- diff > prec);
- }
-}
-
diff --git a/tests/AndroidTests/src/com/android/unit_tests/content/IntentFilterTest.java b/tests/AndroidTests/src/com/android/unit_tests/content/IntentFilterTest.java
deleted file mode 100644
index 0335b9d..0000000
--- a/tests/AndroidTests/src/com/android/unit_tests/content/IntentFilterTest.java
+++ /dev/null
@@ -1,570 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.unit_tests.content;
-
-import android.content.IntentFilter;
-import android.test.suitebuilder.annotation.SmallTest;
-import static android.os.PatternMatcher.PATTERN_LITERAL;
-import static android.os.PatternMatcher.PATTERN_PREFIX;
-import static android.os.PatternMatcher.PATTERN_SIMPLE_GLOB;
-import android.net.Uri;
-import android.util.StringBuilderPrinter;
-import junit.framework.TestCase;
-
-import java.util.HashSet;
-
-public class IntentFilterTest extends TestCase {
-
- public static class Match extends IntentFilter {
- Match(String[] actions, String[] categories, String[] mimeTypes,
- String[] schemes, String[] authorities, String[] ports) {
- if (actions != null) {
- for (int i = 0; i < actions.length; i++) {
- addAction(actions[i]);
- }
- }
- if (categories != null) {
- for (int i = 0; i < categories.length; i++) {
- addCategory(categories[i]);
- }
- }
- if (mimeTypes != null) {
- for (int i = 0; i < mimeTypes.length; i++) {
- try {
- addDataType(mimeTypes[i]);
- } catch (IntentFilter.MalformedMimeTypeException e) {
- throw new RuntimeException("Bad mime type", e);
- }
- }
- }
- if (schemes != null) {
- for (int i = 0; i < schemes.length; i++) {
- addDataScheme(schemes[i]);
- }
- }
- if (authorities != null) {
- for (int i = 0; i < authorities.length; i++) {
- addDataAuthority(authorities[i],
- ports != null ? ports[i] : null);
- }
- }
- }
-
- Match(String[] actions, String[] categories, String[] mimeTypes,
- String[] schemes, String[] authorities, String[] ports,
- String[] paths, int[] pathTypes) {
- this(actions, categories, mimeTypes, schemes, authorities, ports);
- if (paths != null) {
- for (int i = 0; i < paths.length; i++) {
- addDataPath(paths[i], pathTypes[i]);
- }
- }
- }
- }
-
- public static class MatchCondition {
- public final int result;
- public final String action;
- public final String mimeType;
- public final Uri data;
- public final String[] categories;
-
- public MatchCondition(int _result, String _action, String[] _categories,
- String _mimeType, String _data) {
- result = _result;
- action = _action;
- mimeType = _mimeType;
- data = _data != null ? Uri.parse(_data) : null;
- categories = _categories;
- }
- }
-
- public static void checkMatches(IntentFilter filter,
- MatchCondition[] results) {
- for (int i = 0; i < results.length; i++) {
- MatchCondition mc = results[i];
- HashSet<String> categories = null;
- if (mc.categories != null) {
- for (int j = 0; j < mc.categories.length; j++) {
- if (categories == null) {
- categories = new HashSet<String>();
- }
- categories.add(mc.categories[j]);
- }
- }
- int result = filter.match(mc.action, mc.mimeType,
- mc.data != null ? mc.data.getScheme() : null, mc.data,
- categories, "test");
- if ( (result & IntentFilter.MATCH_CATEGORY_MASK)
- != (mc.result & IntentFilter.MATCH_CATEGORY_MASK) ) {
- StringBuilder msg = new StringBuilder();
- msg.append("Error matching against IntentFilter:\n");
- filter.dump(new StringBuilderPrinter(msg), " ");
- msg.append("Match action: ");
- msg.append(mc.action);
- msg.append("\nMatch mimeType: ");
- msg.append(mc.mimeType);
- msg.append("\nMatch data: ");
- msg.append(mc.data);
- msg.append("\nMatch categories: ");
- if (mc.categories != null) {
- for (int j = 0; j < mc.categories.length; j++) {
- if (j > 0) msg.append(", ");
- msg.append(mc.categories[j]);
- }
- }
- msg.append("\nExpected result: 0x");
- msg.append(Integer.toHexString(mc.result));
- msg.append(", got result: 0x");
- msg.append(Integer.toHexString(result));
- throw new RuntimeException(msg.toString());
- }
- }
- }
-
- @SmallTest
- public void testActions() throws Exception {
- IntentFilter filter = new Match(
- new String[]{"action1"}, null, null, null, null, null);
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.MATCH_CATEGORY_EMPTY, null,
- null, null, null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_EMPTY, "action1",
- null, null, null),
- new MatchCondition(IntentFilter.NO_MATCH_ACTION, "action2",
- null, null, null),
- });
-
- filter = new Match(
- new String[]{"action1", "action2"},
- null, null, null, null, null);
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.MATCH_CATEGORY_EMPTY, null,
- null, null, null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_EMPTY, "action1",
- null, null, null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_EMPTY, "action2",
- null, null, null),
- new MatchCondition(IntentFilter.NO_MATCH_ACTION, "action3",
- null, null, null),
- });
- }
-
- @SmallTest
- public void testCategories() throws Exception {
- IntentFilter filter = new Match(
- null, new String[]{"category1"}, null, null, null, null);
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.MATCH_CATEGORY_EMPTY, null,
- null, null, null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_EMPTY, null,
- new String[]{"category1"}, null, null),
- new MatchCondition(IntentFilter.NO_MATCH_CATEGORY, null,
- new String[]{"category2"}, null, null),
- new MatchCondition(IntentFilter.NO_MATCH_CATEGORY, null,
- new String[]{"category1", "category2"}, null, null),
- });
-
- filter = new Match(
- null, new String[]{"category1", "category2"}, null, null,
- null, null);
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.MATCH_CATEGORY_EMPTY, null,
- null, null, null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_EMPTY, null,
- new String[]{"category1"}, null, null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_EMPTY, null,
- new String[]{"category2"}, null, null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_EMPTY, null,
- new String[]{"category1", "category2"}, null, null),
- new MatchCondition(IntentFilter.NO_MATCH_CATEGORY, null,
- new String[]{"category3"}, null, null),
- new MatchCondition(IntentFilter.NO_MATCH_CATEGORY, null,
- new String[]{"category1", "category2", "category3"},
- null, null),
- });
- }
-
- @SmallTest
- public void testMimeTypes() throws Exception {
- IntentFilter filter = new Match(
- null, null, new String[]{"which1/what1"}, null, null, null);
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.NO_MATCH_TYPE, null,
- null, null, null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null,
- "which1/what1", null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null,
- "which1/*", null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null,
- "*/*", null),
- new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null,
- "which2/what2", null),
- new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null,
- "which2/*", null),
- new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null,
- "which1/what2", null),
- });
-
- filter = new Match(null, null,
- new String[]{"which1/what1", "which2/what2"}, null, null,
- null);
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.NO_MATCH_TYPE, null,
- null, null, null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null,
- "which1/what1", null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null,
- "which1/*", null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null,
- "*/*", null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null,
- "which2/what2", null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null,
- "which2/*", null),
- new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null,
- "which1/what2", null),
- new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null,
- "which3/what3", null),
- });
-
- filter = new Match(null, null,
- new String[]{"which1/*"}, null, null, null);
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.NO_MATCH_TYPE, null,
- null, null, null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null,
- "which1/what1", null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null,
- "which1/*", null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null,
- "*/*", null),
- new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null,
- "which2/what2", null),
- new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null,
- "which2/*", null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null,
- "which1/what2", null),
- new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null,
- "which3/what3", null),
- });
-
- filter = new Match(null, null,
- new String[]{"*/*"}, null, null, null);
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.NO_MATCH_TYPE, null,
- null, null, null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null,
- "which1/what1", null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null,
- "which1/*", null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null,
- "*/*", null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null,
- "which2/what2", null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null,
- "which2/*", null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null,
- "which1/what2", null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null,
- "which3/what3", null),
- });
- }
-
- @SmallTest
- public void testSchemes() throws Exception {
- IntentFilter filter = new Match(null, null, null,
- new String[]{"scheme1"}, null, null);
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_SCHEME, null,
- null, null, "scheme1:foo"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme2:foo"),
- });
-
- filter = new Match(null, null, null,
- new String[]{"scheme1", "scheme2"}, null, null);
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_SCHEME, null,
- null, null, "scheme1:foo"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_SCHEME, null,
- null, null, "scheme2:foo"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme3:foo"),
- });
- }
-
- @SmallTest
- public void testAuthorities() throws Exception {
- IntentFilter filter = new Match(null, null, null,
- new String[]{"scheme1"},
- new String[]{"authority1"}, new String[]{null});
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, null),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme1:foo"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_HOST, null,
- null, null, "scheme1://authority1/"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme1://authority2/"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_HOST, null,
- null, null, "scheme1://authority1:100/"),
- });
-
- filter = new Match(null, null, null, new String[]{"scheme1"},
- new String[]{"authority1"}, new String[]{"100"});
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, null),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme1:foo"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme1://authority1/"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme1://authority2/"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PORT, null,
- null, null, "scheme1://authority1:100/"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme1://authority1:200/"),
- });
-
- filter = new Match(null, null, null, new String[]{"scheme1"},
- new String[]{"authority1", "authority2"},
- new String[]{"100", null});
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, null),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme1:foo"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme1://authority1/"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_HOST, null,
- null, null, "scheme1://authority2/"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PORT, null,
- null, null, "scheme1://authority1:100/"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme1://authority1:200/"),
- });
- }
-
- @SmallTest
- public void testPaths() throws Exception {
- IntentFilter filter = new Match(null, null, null,
- new String[]{"scheme"}, new String[]{"authority"}, null,
- new String[]{"/literal1", "/2literal"},
- new int[]{PATTERN_LITERAL, PATTERN_LITERAL});
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/literal1"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/2literal"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme://authority/literal"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme://authority/literal12"),
- });
- filter = new Match(null, null, null,
- new String[]{"scheme"}, new String[]{"authority"}, null,
- new String[]{"/literal1", "/2literal"},
- new int[]{PATTERN_PREFIX, PATTERN_PREFIX});
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/literal1"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/2literal"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme://authority/literal"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/literal12"),
- });
- filter = new Match(null, null, null,
- new String[]{"scheme"}, new String[]{"authority"}, null,
- new String[]{"/.*"},
- new int[]{PATTERN_SIMPLE_GLOB});
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/literal1"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme://authority"),
- });
- filter = new Match(null, null, null,
- new String[]{"scheme"}, new String[]{"authority"}, null,
- new String[]{".*"},
- new int[]{PATTERN_SIMPLE_GLOB});
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/literal1"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority"),
- });
- filter = new Match(null, null, null,
- new String[]{"scheme"}, new String[]{"authority"}, null,
- new String[]{"/a1*b"},
- new int[]{PATTERN_SIMPLE_GLOB});
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/ab"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/a1b"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/a11b"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme://authority/a2b"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme://authority/a1bc"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme://authority/"),
- });
- filter = new Match(null, null, null,
- new String[]{"scheme"}, new String[]{"authority"}, null,
- new String[]{"/a1*"},
- new int[]{PATTERN_SIMPLE_GLOB});
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/a1"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme://authority/ab"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/a11"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme://authority/a1b"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/a11"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme://authority/a2"),
- });
- filter = new Match(null, null, null,
- new String[]{"scheme"}, new String[]{"authority"}, null,
- new String[]{"/a\\.*b"},
- new int[]{PATTERN_SIMPLE_GLOB});
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/ab"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/a.b"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/a..b"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme://authority/a2b"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme://authority/a.bc"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme://authority/"),
- });
- filter = new Match(null, null, null,
- new String[]{"scheme"}, new String[]{"authority"}, null,
- new String[]{"/a.*b"},
- new int[]{PATTERN_SIMPLE_GLOB});
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/ab"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/a.b"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/a.1b"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/a2b"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme://authority/a.bc"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme://authority/"),
- });
- filter = new Match(null, null, null,
- new String[]{"scheme"}, new String[]{"authority"}, null,
- new String[]{"/a.*"},
- new int[]{PATTERN_SIMPLE_GLOB});
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, null),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/ab"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/a.b"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/a.1b"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/a2b"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/a.bc"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme://authority/"),
- });
- filter = new Match(null, null, null,
- new String[]{"scheme"}, new String[]{"authority"}, null,
- new String[]{"/a.\\*b"},
- new int[]{PATTERN_SIMPLE_GLOB});
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, null),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme://authority/ab"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/a.*b"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/a1*b"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme://authority/a2b"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme://authority/a.bc"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme://authority/"),
- });
- filter = new Match(null, null, null,
- new String[]{"scheme"}, new String[]{"authority"}, null,
- new String[]{"/a.\\*"},
- new int[]{PATTERN_SIMPLE_GLOB});
- checkMatches(filter, new MatchCondition[]{
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, null),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme://authority/ab"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/a.*"),
- new MatchCondition(IntentFilter.MATCH_CATEGORY_PATH, null,
- null, null, "scheme://authority/a1*"),
- new MatchCondition(IntentFilter.NO_MATCH_DATA, null,
- null, null, "scheme://authority/a1b"),
- });
- }
-
-}
-
diff --git a/tests/AndroidTests/src/com/android/unit_tests/content/PluralResourcesTest.java b/tests/AndroidTests/src/com/android/unit_tests/content/PluralResourcesTest.java
deleted file mode 100644
index c3d1478..0000000
--- a/tests/AndroidTests/src/com/android/unit_tests/content/PluralResourcesTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.unit_tests.content;
-
-import android.content.res.AssetManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.TypedValue;
-import android.util.Log;
-import com.android.unit_tests.R;
-
-import junit.framework.Assert;
-
-import java.util.Locale;
-
-public class PluralResourcesTest extends AndroidTestCase {
- private static final String TAG = "PluralResourcesTest";
-
- private Resources mResources;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mResources = mContext.getResources();
- }
-
- Resources resourcesForLanguage(String lang) {
- Configuration config = new Configuration();
- config.updateFrom(mResources.getConfiguration());
- config.locale = new Locale(lang);
- return new Resources(mResources.getAssets(), mResources.getDisplayMetrics(), config);
- }
-
- @SmallTest
- public void testPlurals() throws Exception {
- CharSequence cs;
- Resources res = resourcesForLanguage("en");
-
- cs = res.getQuantityText(R.plurals.plurals_test, 0);
- Log.d(TAG, "english 0 cs=" + cs);
- Assert.assertEquals(cs.toString(), "Some dogs");
-
- cs = res.getQuantityText(R.plurals.plurals_test, 1);
- Log.d(TAG, "english 1 cs=" + cs);
- Assert.assertEquals(cs.toString(), "A dog");
-
- cs = res.getQuantityText(R.plurals.plurals_test, 2);
- Assert.assertEquals(cs.toString(), "Some dogs");
-
- cs = res.getQuantityText(R.plurals.plurals_test, 5);
- Assert.assertEquals(cs.toString(), "Some dogs");
-
- cs = res.getQuantityText(R.plurals.plurals_test, 500);
- Assert.assertEquals(cs.toString(), "Some dogs");
- }
-
- @SmallTest
- public void testCzech() throws Exception {
- CharSequence cs;
- Resources res = resourcesForLanguage("cs");
-
- cs = res.getQuantityText(R.plurals.plurals_test, 0);
- Log.d(TAG, "czech 0 cs=" + cs);
- Assert.assertEquals(cs.toString(), "Some Czech dogs");
-
- cs = res.getQuantityText(R.plurals.plurals_test, 1);
- Log.d(TAG, "czech 1 cs=" + cs);
- Assert.assertEquals(cs.toString(), "A Czech dog");
-
- cs = res.getQuantityText(R.plurals.plurals_test, 2);
- Log.d(TAG, "czech 2 cs=" + cs);
- Assert.assertEquals(cs.toString(), "Few Czech dogs");
-
- cs = res.getQuantityText(R.plurals.plurals_test, 5);
- Assert.assertEquals(cs.toString(), "Some Czech dogs");
-
- cs = res.getQuantityText(R.plurals.plurals_test, 500);
- Assert.assertEquals(cs.toString(), "Some Czech dogs");
- }
-}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/content/PrimitiveTest.java b/tests/AndroidTests/src/com/android/unit_tests/content/PrimitiveTest.java
deleted file mode 100644
index 44098cc..0000000
--- a/tests/AndroidTests/src/com/android/unit_tests/content/PrimitiveTest.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.unit_tests.content;
-
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.TypedValue;
-import com.android.unit_tests.R;
-
-public class PrimitiveTest extends AndroidTestCase {
- private Resources mResources;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mResources = mContext.getResources();
- }
-
- private void tryEnum(int resid, int expected) {
- TypedArray sa =
- mContext.obtainStyledAttributes(resid, R.styleable.EnumStyle);
- int value = sa.getInt(R.styleable.EnumStyle_testEnum, -1);
- sa.recycle();
-
- assertEquals("Expecting value " + expected + " got " + value
- + ": in resource 0x" + Integer.toHexString(resid),
- expected, value);
- }
-
- @SmallTest
- public void testEnum() throws Exception {
- tryEnum(R.style.TestEnum1, 1);
- tryEnum(R.style.TestEnum2, 2);
- tryEnum(R.style.TestEnum10, 10);
- tryEnum(R.style.TestEnum1_EmptyInherit, 1);
- }
-
- private void tryFlag(int resid, int expected) {
- TypedArray sa =
- mContext.obtainStyledAttributes(resid, R.styleable.FlagStyle);
- int value = sa.getInt(R.styleable.FlagStyle_testFlags, -1);
- sa.recycle();
-
- assertEquals("Expecting value " + expected + " got " + value
- + ": in resource 0x" + Integer.toHexString(resid),
- expected, value);
- }
-
- @SmallTest
- public void testFlags() throws Exception {
- tryFlag(R.style.TestFlag1, 0x1);
- tryFlag(R.style.TestFlag2, 0x2);
- tryFlag(R.style.TestFlag31, 0x40000000);
- tryFlag(R.style.TestFlag1And2, 0x3);
- tryFlag(R.style.TestFlag1And2And31, 0x40000003);
- }
-
- private void tryBoolean(int resid, boolean expected) {
- TypedValue v = new TypedValue();
- mContext.getResources().getValue(resid, v, true);
- assertEquals(TypedValue.TYPE_INT_BOOLEAN, v.type);
- assertEquals("Expecting boolean value " + expected + " got " + v
- + " from TypedValue: in resource 0x" + Integer.toHexString(resid),
- expected, v.data != 0);
- assertEquals("Expecting boolean value " + expected + " got " + v
- + " from getBoolean(): in resource 0x" + Integer.toHexString(resid),
- expected, mContext.getResources().getBoolean(resid));
- }
-
- @SmallTest
- public void testBoolean() throws Exception {
- tryBoolean(R.bool.trueRes, true);
- tryBoolean(R.bool.falseRes, false);
- }
-
- private void tryString(int resid, String expected) {
- TypedValue v = new TypedValue();
- mContext.getResources().getValue(resid, v, true);
- assertEquals(TypedValue.TYPE_STRING, v.type);
- assertEquals("Expecting string value " + expected + " got " + v
- + ": in resource 0x" + Integer.toHexString(resid),
- expected, v.string);
- }
-
- @SmallTest
- public void testStringCoerce() throws Exception {
- tryString(R.string.coerceIntegerToString, "100");
- tryString(R.string.coerceBooleanToString, "true");
- tryString(R.string.coerceColorToString, "#fff");
- tryString(R.string.coerceFloatToString, "100.0");
- tryString(R.string.coerceDimensionToString, "100px");
- tryString(R.string.coerceFractionToString, "100%");
- }
-
- private static void checkString(int resid, String actual, String expected) {
- assertEquals("Expecting string value \"" + expected + "\" got \""
- + actual + "\" in resources 0x" + Integer.toHexString(resid),
- expected, actual);
- }
-
- @SmallTest
- public void testFormattedString() throws Exception {
- // Make sure the regular one doesn't format anything
- checkString(R.string.formattedStringNone,
- mResources.getString(R.string.formattedStringNone),
- "Format[]");
- checkString(R.string.formattedStringOne,
- mResources.getString(R.string.formattedStringOne),
- "Format[%d]");
- checkString(R.string.formattedStringTwo,
- mResources.getString(R.string.formattedStringTwo),
- "Format[%3$d,%2$s]");
- // Make sure the formatted one works
- checkString(R.string.formattedStringNone,
- mResources.getString(R.string.formattedStringNone),
- "Format[]");
- checkString(R.string.formattedStringOne,
- mResources.getString(R.string.formattedStringOne, 42),
- "Format[42]");
- checkString(R.string.formattedStringTwo,
- mResources.getString(R.string.formattedStringTwo, "unused", "hi", 43),
- "Format[43,hi]");
- }
-}
-
diff --git a/tests/AndroidTests/src/com/android/unit_tests/content/RawResourceTest.java b/tests/AndroidTests/src/com/android/unit_tests/content/RawResourceTest.java
deleted file mode 100644
index 1786dc4..0000000
--- a/tests/AndroidTests/src/com/android/unit_tests/content/RawResourceTest.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.unit_tests.content;
-
-import android.content.res.Resources;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import com.android.unit_tests.R;
-
-import java.io.InputStream;
-
-public class RawResourceTest extends AndroidTestCase {
- private Resources mResources;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mResources = mContext.getResources();
- }
-
- @SmallTest
- public void testReadToEnd() throws Exception {
- InputStream is = mResources.openRawResource(R.raw.text);
- AssetTest.verifyTextAsset(is);
- }
-}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/content/ResourceNameTest.java b/tests/AndroidTests/src/com/android/unit_tests/content/ResourceNameTest.java
deleted file mode 100644
index 2a56243..0000000
--- a/tests/AndroidTests/src/com/android/unit_tests/content/ResourceNameTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.unit_tests.content;
-
-import android.content.res.Resources;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.unit_tests.R;
-
-public class ResourceNameTest extends AndroidTestCase {
-
- @SmallTest
- public void testGetResourceName() {
- Resources res = mContext.getResources();
-
- String fullName = res.getResourceName(R.configVarying.simple);
- assertEquals("com.android.unit_tests:configVarying/simple", fullName);
-
- String packageName = res.getResourcePackageName(R.configVarying.simple);
- assertEquals("com.android.unit_tests", packageName);
-
- String typeName = res.getResourceTypeName(R.configVarying.simple);
- assertEquals("configVarying", typeName);
-
- String entryName = res.getResourceEntryName(R.configVarying.simple);
- assertEquals("simple", entryName);
- }
-
- @SmallTest
- public void testGetResourceIdentifier() {
- Resources res = mContext.getResources();
- int resid = res.getIdentifier(
- "com.android.unit_tests:configVarying/simple",
- null, null);
- assertEquals(R.configVarying.simple, resid);
-
- resid = res.getIdentifier("configVarying/simple", null,
- "com.android.unit_tests");
- assertEquals(R.configVarying.simple, resid);
-
- resid = res.getIdentifier("simple", "configVarying",
- "com.android.unit_tests");
- assertEquals(R.configVarying.simple, resid);
- }
-}
-
diff --git a/tests/AndroidTests/src/com/android/unit_tests/content/ResourceTests.java b/tests/AndroidTests/src/com/android/unit_tests/content/ResourceTests.java
deleted file mode 100644
index 943941e..0000000
--- a/tests/AndroidTests/src/com/android/unit_tests/content/ResourceTests.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.unit_tests.content;
-
-import junit.framework.TestSuite;
-
-public class ResourceTests {
- public static TestSuite suite() {
- TestSuite suite = new TestSuite(ResourceTests.class.getName());
-
- suite.addTestSuite(FractionTest.class);
- suite.addTestSuite(PrimitiveTest.class);
- suite.addTestSuite(ArrayTest.class);
- suite.addTestSuite(ConfigTest.class);
- suite.addTestSuite(RawResourceTest.class);
- suite.addTestSuite(ResourceNameTest.class);
- return suite;
- }
-}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTestSuite.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTestSuite.java
deleted file mode 100644
index f3d1c5e..0000000
--- a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTestSuite.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.unit_tests.vcard;
-
-import com.android.unit_tests.AndroidTests;
-
-import android.test.suitebuilder.TestSuiteBuilder;
-
-import junit.framework.TestSuite;
-
-public class VCardTestSuite extends TestSuite {
- public static TestSuite suite() {
- TestSuiteBuilder suiteBuilder = new TestSuiteBuilder(AndroidTests.class);
- suiteBuilder.includeAllPackagesUnderHere();
- return suiteBuilder.build();
- }
-}
\ No newline at end of file
diff --git a/tests/CoreTests/android/core/CoreTests.java b/tests/CoreTests/android/core/CoreTests.java
index e4f835c..442fe0f 100644
--- a/tests/CoreTests/android/core/CoreTests.java
+++ b/tests/CoreTests/android/core/CoreTests.java
@@ -59,7 +59,6 @@
suite.addTestSuite(LocationManagerProximityTest.class);
suite.addTestSuite(AndroidTestRunnerTest.class);
suite.addTestSuite(InstrumentationTestRunnerTest.class);
- suite.addTestSuite(CookieTest.class);
return suite;
}
diff --git a/tests/CoreTests/android/webkit/CookieTest.java b/tests/CoreTests/android/webkit/CookieTest.java
deleted file mode 100644
index 5c5a6a8..0000000
--- a/tests/CoreTests/android/webkit/CookieTest.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.webkit;
-
-import android.content.Context;
-import android.test.AndroidTestCase;
-import android.util.Log;
-import android.webkit.CookieManager;
-import android.webkit.CookieSyncManager;
-
-public class CookieTest extends AndroidTestCase {
-
- /**
- * To run these tests: $ mmm frameworks/base/tests/CoreTests/android && adb
- * remount && adb sync $ adb shell am instrument -w \ -e class
- * android.webkit.CookieTest \
- * android.core/android.test.InstrumentationTestRunner
- */
-
- private CookieManager mCookieManager;
-
- @Override
- public void setContext(Context context) {
- assertTrue(mContext == null);
- super.setContext(context);
- CookieSyncManager.createInstance(context);
- mCookieManager = CookieManager.getInstance();
- mCookieManager.removeAllCookie();
- }
-
- public void testParse() {
- mCookieManager.removeAllCookie();
- String url = "http://www.foo.com";
-
- // basic
- mCookieManager.setCookie(url, "a=b");
- String cookie = mCookieManager.getCookie(url);
- assertTrue(cookie.equals("a=b"));
-
- // quoted
- mCookieManager.setCookie(url, "c=\"d;\"");
- cookie = mCookieManager.getCookie(url);
- assertTrue(cookie.equals("a=b; c=\"d;\""));
-
- // empty
- mCookieManager.setCookie(url, "; path=/");
- cookie = mCookieManager.getCookie(url);
- assertTrue(cookie.equals("a=b; c=\"d;\""));
- }
-
- public void testDomain() {
- mCookieManager.removeAllCookie();
- String url = "http://www.foo.com";
-
- // basic
- mCookieManager.setCookie(url, "a=b");
- String cookie = mCookieManager.getCookie(url);
- assertTrue(cookie.equals("a=b"));
-
- // no cross domain cookie
- cookie = mCookieManager.getCookie("http://bar.com");
- assertTrue(cookie == null);
-
- // more than one cookie
- mCookieManager.setCookie(url, "c=d; domain=.foo.com");
- cookie = mCookieManager.getCookie(url);
- assertTrue(cookie.equals("a=b; c=d"));
-
- // host cookie should not be accessible from a sub-domain.
- cookie = mCookieManager.getCookie("http://bar.www.foo.com");
- assertTrue(cookie.equals("c=d"));
-
- // test setting a domain= that doesn't start w/ a dot, should
- // treat it as a domain cookie, as if there was a pre-pended dot.
- mCookieManager.setCookie(url, "e=f; domain=www.foo.com");
- cookie = mCookieManager.getCookie(url);
- assertTrue(cookie.equals("a=b; c=d; e=f"));
- cookie = mCookieManager.getCookie("http://sub.www.foo.com");
- assertTrue(cookie.equals("c=d; e=f"));
- cookie = mCookieManager.getCookie("http://foo.com");
- assertTrue(cookie.equals("c=d"));
- }
-
- public void testSubDomain() {
- mCookieManager.removeAllCookie();
- String url_abcd = "http://a.b.c.d.com";
- String url_bcd = "http://b.c.d.com";
- String url_cd = "http://c.d.com";
- String url_d = "http://d.com";
-
- mCookieManager.setCookie(url_abcd, "a=1; domain=.a.b.c.d.com");
- mCookieManager.setCookie(url_abcd, "b=2; domain=.b.c.d.com");
- mCookieManager.setCookie(url_abcd, "c=3; domain=.c.d.com");
- mCookieManager.setCookie(url_abcd, "d=4; domain=.d.com");
-
- String cookie = mCookieManager.getCookie(url_abcd);
- assertTrue(cookie.equals("a=1; b=2; c=3; d=4"));
- cookie = mCookieManager.getCookie(url_bcd);
- assertTrue(cookie.equals("b=2; c=3; d=4"));
- cookie = mCookieManager.getCookie(url_cd);
- assertTrue(cookie.equals("c=3; d=4"));
- cookie = mCookieManager.getCookie(url_d);
- assertTrue(cookie.equals("d=4"));
-
- // check that the same cookie can exist on different sub-domains.
- mCookieManager.setCookie(url_bcd, "x=bcd; domain=.b.c.d.com");
- mCookieManager.setCookie(url_bcd, "x=cd; domain=.c.d.com");
- cookie = mCookieManager.getCookie(url_bcd);
- assertTrue(cookie.equals("b=2; c=3; d=4; x=bcd; x=cd"));
- cookie = mCookieManager.getCookie(url_cd);
- assertTrue(cookie.equals("c=3; d=4; x=cd"));
- }
-
- public void testInvalidDomain() {
- mCookieManager.removeAllCookie();
- String url = "http://foo.bar.com";
-
- mCookieManager.setCookie(url, "a=1; domain=.yo.foo.bar.com");
- String cookie = mCookieManager.getCookie(url);
- assertTrue(cookie == null);
-
- mCookieManager.setCookie(url, "b=2; domain=.foo.com");
- cookie = mCookieManager.getCookie(url);
- assertTrue(cookie == null);
-
- mCookieManager.setCookie(url, "c=3; domain=.bar.foo.com");
- cookie = mCookieManager.getCookie(url);
- assertTrue(cookie == null);
-
- mCookieManager.setCookie(url, "d=4; domain=.foo.bar.com.net");
- cookie = mCookieManager.getCookie(url);
- assertTrue(cookie == null);
-
- mCookieManager.setCookie(url, "e=5; domain=.ar.com");
- cookie = mCookieManager.getCookie(url);
- assertTrue(cookie == null);
-
- mCookieManager.setCookie(url, "f=6; domain=.com");
- cookie = mCookieManager.getCookie(url);
- assertTrue(cookie == null);
-
- mCookieManager.setCookie(url, "g=7; domain=.co.uk");
- cookie = mCookieManager.getCookie(url);
- assertTrue(cookie == null);
-
- mCookieManager.setCookie(url, "h=8; domain=.foo.bar.com.com");
- cookie = mCookieManager.getCookie(url);
- assertTrue(cookie == null);
- }
-
- public void testPath() {
- mCookieManager.removeAllCookie();
- String url = "http://www.foo.com";
-
- mCookieManager.setCookie(url, "a=b; path=/wee");
- String cookie = mCookieManager.getCookie(url + "/wee");
- assertTrue(cookie.equals("a=b"));
- cookie = mCookieManager.getCookie(url + "/wee/");
- assertTrue(cookie.equals("a=b"));
- cookie = mCookieManager.getCookie(url + "/wee/hee");
- assertTrue(cookie.equals("a=b"));
- cookie = mCookieManager.getCookie(url + "/wee/hee/more");
- assertTrue(cookie.equals("a=b"));
- cookie = mCookieManager.getCookie(url + "/weehee");
- assertTrue(cookie == null);
- cookie = mCookieManager.getCookie(url);
- assertTrue(cookie == null);
-
- mCookieManager.setCookie(url, "a=c; path=");
- cookie = mCookieManager.getCookie(url + "/wee");
- assertTrue(cookie.equals("a=b; a=c"));
- cookie = mCookieManager.getCookie(url);
- assertTrue(cookie.equals("a=c"));
-
- mCookieManager.setCookie(url, "a=d");
- cookie = mCookieManager.getCookie(url + "/wee");
- assertTrue(cookie.equals("a=b; a=d"));
- }
-}
diff --git a/tests/FrameworkTest/tests/src/com/android/frameworktest/expandablelistview/ExpandableListWithHeadersTest.java b/tests/FrameworkTest/tests/src/com/android/frameworktest/expandablelistview/ExpandableListWithHeadersTest.java
deleted file mode 100644
index 49b5106..0000000
--- a/tests/FrameworkTest/tests/src/com/android/frameworktest/expandablelistview/ExpandableListWithHeadersTest.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.frameworktest.expandablelistview;
-
-import android.test.ActivityInstrumentationTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.view.KeyEvent;
-import android.widget.ExpandableListView;
-
-import com.android.frameworktest.expandablelistview.ExpandableListWithHeaders;
-import com.android.frameworktest.util.ListUtil;
-
-public class ExpandableListWithHeadersTest extends ActivityInstrumentationTestCase<ExpandableListWithHeaders> {
- private ExpandableListView mExpandableListView;
- private ListUtil mListUtil;
-
- public ExpandableListWithHeadersTest() {
- super("com.android.frameworktest",
- ExpandableListWithHeaders.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mExpandableListView = getActivity().getExpandableListView();
- mListUtil = new ListUtil(mExpandableListView, getInstrumentation());
- }
-
- @MediumTest
- public void testPreconditions() {
- assertNotNull(mExpandableListView);
- }
-
- @MediumTest
- public void testExpandOnFirstPosition() {
- // Should be a header, and hence the first group should NOT have expanded
- mListUtil.arrowScrollToSelectedPosition(0);
- sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
- getInstrumentation().waitForIdleSync();
- assertFalse(mExpandableListView.isGroupExpanded(0));
- }
-
- @LargeTest
- public void testExpandOnFirstGroup() {
- mListUtil.arrowScrollToSelectedPosition(getActivity().getNumOfHeadersAndFooters());
- sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
- getInstrumentation().waitForIdleSync();
- assertTrue(mExpandableListView.isGroupExpanded(0));
- }
-}
diff --git a/tests/FrameworkTest/tests/src/com/android/frameworktest/view/RemoteViewsActivityTest.java b/tests/FrameworkTest/tests/src/com/android/frameworktest/view/RemoteViewsActivityTest.java
deleted file mode 100644
index 3dcb252..0000000
--- a/tests/FrameworkTest/tests/src/com/android/frameworktest/view/RemoteViewsActivityTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.frameworktest.view;
-
-import android.os.Parcel;
-import android.test.ActivityInstrumentationTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.view.InflateException;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.RemoteViews;
-
-import com.android.frameworktest.R;
-import com.android.frameworktest.view.RemoteViewsActivity;
-
-public class RemoteViewsActivityTest extends ActivityInstrumentationTestCase<RemoteViewsActivity> {
- public RemoteViewsActivityTest() {
- super("com.android.frameworktest", RemoteViewsActivity.class);
- }
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
- }
-
- @MediumTest
- public void testGood() throws Exception {
- final RemoteViewsActivity activity = getActivity();
-
- RemoteViews orig = new RemoteViews("com.android.frameworktest",
- R.layout.remote_view_test_good);
- Parcel p = Parcel.obtain();
- orig.writeToParcel(p, 0);
- p.setDataPosition(0);
-
- RemoteViews r = RemoteViews.CREATOR.createFromParcel(p);
-
- ViewGroup parent = (ViewGroup) activity.findViewById(R.id.parent);
-
- View result = r.apply(activity, parent);
-
- p.recycle();
-
- assertTrue("LinearLayout not inflated", result.findViewById(R.id.linear) != null);
- assertTrue("TextView not inflated", result.findViewById(R.id.text) != null);
- assertTrue("ImageView not inflated", result.findViewById(R.id.image) != null);
- assertTrue("FrameLayout not inflated", result.findViewById(R.id.frame) != null);
- assertTrue("RelateiveLayout not inflated", result.findViewById(R.id.relative) != null);
- assertTrue("AbsoluteLayout not inflated", result.findViewById(R.id.absolute) != null);
- assertTrue("ProgressBar not inflated", result.findViewById(R.id.progress) != null);
- assertTrue("ImageButton not inflated", result.findViewById(R.id.image_button) != null);
- assertTrue("Button not inflated", result.findViewById(R.id.button) != null);
- }
-
- @MediumTest
- public void testDerivedClass() throws Exception {
- final RemoteViewsActivity activity = getActivity();
-
- RemoteViews orig = new RemoteViews("com.android.frameworktest",
- R.layout.remote_view_test_bad_1);
- Parcel p = Parcel.obtain();
- orig.writeToParcel(p, 0);
- p.setDataPosition(0);
-
- RemoteViews r = RemoteViews.CREATOR.createFromParcel(p);
-
- ViewGroup parent = (ViewGroup) activity.findViewById(R.id.parent);
-
- boolean exceptionThrown = false;
- View result = null;
-
- try {
- result = r.apply(activity, parent);
- } catch (InflateException e) {
- exceptionThrown = true;
- }
-
- p.recycle();
-
- assertTrue("Derived class (EditText) allowed to be inflated", exceptionThrown);
- assertNull("Derived class (EditText) allowed to be inflated", result);
- }
-
- @MediumTest
- public void testWebView() throws Exception {
- final RemoteViewsActivity activity = getActivity();
-
- RemoteViews orig = new RemoteViews("com.android.frameworktest",
- R.layout.remote_view_test_bad_2);
- Parcel p = Parcel.obtain();
- orig.writeToParcel(p, 0);
- p.setDataPosition(0);
-
- RemoteViews r = RemoteViews.CREATOR.createFromParcel(p);
-
- ViewGroup parent = (ViewGroup) activity.findViewById(R.id.parent);
-
- boolean exceptionThrown = false;
- View result = null;
-
- try {
- result = r.apply(activity, parent);
- } catch (InflateException e) {
- exceptionThrown = true;
- }
-
- p.recycle();
-
- assertTrue("WebView allowed to be inflated", exceptionThrown);
- assertNull("WebView allowed to be inflated", result);
- }
-}
diff --git a/tests/TransformTest/Android.mk b/tests/TransformTest/Android.mk
new file mode 100644
index 0000000..2d3637d
--- /dev/null
+++ b/tests/TransformTest/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := TransformTest
+
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/TransformTest/AndroidManifest.xml b/tests/TransformTest/AndroidManifest.xml
new file mode 100644
index 0000000..5c9995f
--- /dev/null
+++ b/tests/TransformTest/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.test.transform">
+ <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="7" />
+ <application android:label="TransformTest">
+ <activity android:name="TransformTestActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/TransformTest/res/drawable/logo.png b/tests/TransformTest/res/drawable/logo.png
new file mode 100644
index 0000000..4d717a8
--- /dev/null
+++ b/tests/TransformTest/res/drawable/logo.png
Binary files differ
diff --git a/tests/TransformTest/res/values/strings.xml b/tests/TransformTest/res/values/strings.xml
new file mode 100644
index 0000000..a0eb81f
--- /dev/null
+++ b/tests/TransformTest/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <string name="act_title">TransformTest</string>
+</resources>
diff --git a/tests/TransformTest/src/com/google/android/test/transform/TransformTestActivity.java b/tests/TransformTest/src/com/google/android/test/transform/TransformTestActivity.java
new file mode 100644
index 0000000..52286d1
--- /dev/null
+++ b/tests/TransformTest/src/com/google/android/test/transform/TransformTestActivity.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.test.transform;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.TransformGestureDetector;
+import android.view.View;
+import android.widget.LinearLayout;
+
+public class TransformTestActivity extends Activity {
+ public TransformTestActivity() {
+ super();
+ init(false);
+ }
+
+ public TransformTestActivity(boolean noCompat) {
+ super();
+ init(noCompat);
+ }
+
+ public void init(boolean noCompat) {
+
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final LayoutInflater li = (LayoutInflater)getSystemService(
+ LAYOUT_INFLATER_SERVICE);
+
+ this.setTitle(R.string.act_title);
+ LinearLayout root = new LinearLayout(this);
+ root.setOrientation(LinearLayout.VERTICAL);
+
+ TransformView view = new TransformView(getApplicationContext());
+ Drawable drawable = getResources().getDrawable(R.drawable.logo);
+ drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicWidth());
+ view.setDrawable(drawable);
+
+ root.addView(view);
+ setContentView(root);
+ }
+
+ private class TransformView extends View {
+ private Drawable mDrawable;
+ private float mPosX;
+ private float mPosY;
+ private float mScale = 1.f;
+ private Matrix mMatrix;
+ private TransformGestureDetector mDetector;
+
+ private class Listener implements TransformGestureDetector.OnTransformGestureListener {
+
+ public boolean onTransform(TransformGestureDetector detector) {
+ Log.d("ttest", "Translation: (" + detector.getTranslateX() +
+ ", " + detector.getTranslateY() + ")");
+ float scale = detector.getScaleFactor();
+ Log.d("ttest", "Scale: " + scale);
+ if (mScale * scale > 0.1f) {
+ if (mScale * scale < 10.f) {
+ mScale *= scale;
+ } else {
+ mScale = 10.f;
+ }
+ } else {
+ mScale = 0.1f;
+ }
+
+ mPosX += detector.getTranslateX();
+ mPosY += detector.getTranslateY();
+
+ Log.d("ttest", "mScale: " + mScale + " mPos: (" + mPosX + ", " + mPosY + ")");
+
+ float sizeX = mDrawable.getIntrinsicWidth()/2;
+ float sizeY = mDrawable.getIntrinsicHeight()/2;
+ float centerX = detector.getCenterX();
+ float centerY = detector.getCenterY();
+ float diffX = centerX - mPosX;
+ float diffY = centerY - mPosY;
+ diffX = diffX*scale - diffX;
+ diffY = diffY*scale - diffY;
+ mPosX -= diffX;
+ mPosY -= diffY;
+ mMatrix.reset();
+ mMatrix.postTranslate(-sizeX, -sizeY);
+ mMatrix.postScale(mScale, mScale);
+ mMatrix.postTranslate(mPosX, mPosY);
+
+ invalidate();
+
+ return true;
+ }
+
+ public boolean onTransformBegin(TransformGestureDetector detector) {
+ return true;
+ }
+
+ public boolean onTransformEnd(TransformGestureDetector detector) {
+ return true;
+ }
+
+ public boolean onTransformFling(TransformGestureDetector detector) {
+ return false;
+ }
+
+ }
+
+ public TransformView(Context context) {
+ super(context);
+ mMatrix = new Matrix();
+ mDetector = new TransformGestureDetector(context, new Listener());
+ DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+ mPosX = metrics.widthPixels/2;
+ mPosY = metrics.heightPixels/2;
+ }
+
+ public void setDrawable(Drawable d) {
+ mDrawable = d;
+
+ float sizeX = mDrawable.getIntrinsicWidth()/2;
+ float sizeY = mDrawable.getIntrinsicHeight()/2;
+ mMatrix.reset();
+ mMatrix.postTranslate(-sizeX, -sizeY);
+ mMatrix.postScale(mScale, mScale);
+ mMatrix.postTranslate(mPosX, mPosY);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ boolean handled = mDetector.onTouchEvent(event);
+
+ int pointerCount = event.getPointerCount();
+ Log.d("ttest", "pointerCount: " + pointerCount);
+
+ return handled;
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ int saveCount = canvas.getSaveCount();
+ canvas.save();
+ canvas.concat(mMatrix);
+ mDrawable.draw(canvas);
+ canvas.restoreToCount(saveCount);
+ }
+ }
+}
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index de6ff14..ae8f242 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1768,31 +1768,33 @@
fprintf(stderr, "ERROR: %s\n", error.string());
return -1;
}
- // asdf --> package.asdf
- // .asdf .a.b --> package.asdf package.a.b
- // asdf.adsf --> asdf.asdf
- String8 rule("-keep class ");
- const char* p = name.string();
- const char* q = strchr(p, '.');
- if (p == q) {
- rule += pkg;
- rule += name;
- } else if (q == NULL) {
- rule += pkg;
- rule += ".";
- rule += name;
- } else {
- rule += name;
+ if (name.length() > 0) {
+ // asdf --> package.asdf
+ // .asdf .a.b --> package.asdf package.a.b
+ // asdf.adsf --> asdf.asdf
+ String8 rule("-keep class ");
+ const char* p = name.string();
+ const char* q = strchr(p, '.');
+ if (p == q) {
+ rule += pkg;
+ rule += name;
+ } else if (q == NULL) {
+ rule += pkg;
+ rule += ".";
+ rule += name;
+ } else {
+ rule += name;
+ }
+
+ String8 location = tag;
+ location += " ";
+ location += assFile->getSourceFile();
+ char lineno[20];
+ sprintf(lineno, ":%d", tree.getLineNumber());
+ location += lineno;
+
+ keep->add(rule, location);
}
-
- String8 location = tag;
- location += " ";
- location += assFile->getSourceFile();
- char lineno[20];
- sprintf(lineno, ":%d", tree.getLineNumber());
- location += lineno;
-
- keep->add(rule, location);
}
}
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java
index 1e9f573..b670eee 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java
@@ -259,7 +259,18 @@
// resolve the defStyleAttr value into a IStyleResourceValue
IStyleResourceValue defStyleValues = null;
- if (defStyleAttr != 0) {
+
+ // look for a custom style.
+ String customStyle = parser.getAttributeValue(null /* namespace*/, "style");
+ if (customStyle != null) {
+ IResourceValue item = findResValue(customStyle);
+
+ if (item instanceof IStyleResourceValue) {
+ defStyleValues = (IStyleResourceValue)item;
+ }
+ }
+
+ if (defStyleValues == null && defStyleAttr != 0) {
// get the name from the int.
String defStyleName = searchAttr(defStyleAttr);
@@ -1104,7 +1115,7 @@
Bundle initialExtras) {
// TODO Auto-generated method stub
}
-
+
@Override
public void setTheme(int arg0) {
// TODO Auto-generated method stub
@@ -1137,7 +1148,7 @@
throws IntentSender.SendIntentException {
// TODO Auto-generated method stub
}
-
+
@Override
public boolean startInstrumentation(ComponentName arg0, String arg1,
Bundle arg2) {