Merge "Parcel::appendFrom({const } Parcel *parcel, size_t, size_t)"
diff --git a/Android.mk b/Android.mk
index 28340cf..cd9ae7d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -137,8 +137,8 @@
 	core/java/android/view/IWindowSession.aidl \
 	core/java/android/speech/IRecognitionListener.aidl \
 	core/java/android/speech/IRecognitionService.aidl \
-	core/java/android/speech/tts/ITts.aidl \
-	core/java/android/speech/tts/ITtsCallback.aidl \
+	core/java/android/speech/tts/ITextToSpeechCallback.aidl \
+	core/java/android/speech/tts/ITextToSpeechService.aidl \
 	core/java/com/android/internal/app/IBatteryStats.aidl \
 	core/java/com/android/internal/app/IUsageStats.aidl \
 	core/java/com/android/internal/app/IMediaContainerService.aidl \
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 023ce59..50292e4 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -96,6 +96,7 @@
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/RSTest_intermediates/)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/hardware/IUsbManager.java)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/nfc)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates)
 
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
diff --git a/api/current.txt b/api/current.txt
index 4d3f8c6..9d593d8 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -22579,6 +22579,7 @@
     method public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodList();
     method public java.util.List<android.view.inputmethod.InputMethodSubtype> getEnabledInputMethodSubtypeList(android.view.inputmethod.InputMethodInfo, boolean);
     method public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodList();
+    method public android.view.inputmethod.InputMethodSubtype getLastInputMethodSubtype();
     method public java.util.Map<android.view.inputmethod.InputMethodInfo, java.util.List<android.view.inputmethod.InputMethodSubtype>> getShortcutInputMethodsAndSubtypes();
     method public void hideSoftInputFromInputMethod(android.os.IBinder, int);
     method public boolean hideSoftInputFromWindow(android.os.IBinder, int);
diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp
index 0159edd..371268f 100644
--- a/cmds/app_process/app_main.cpp
+++ b/cmds/app_process/app_main.cpp
@@ -1,8 +1,8 @@
 /*
  * Main entry of app process.
- * 
+ *
  * Starts the interpreted runtime, then starts up the application.
- * 
+ *
  */
 
 #define LOG_TAG "appproc"
@@ -25,23 +25,13 @@
         "Usage: app_process [java-options] cmd-dir start-class-name [options]\n");
 }
 
-status_t app_init(const char* className, int argc, const char* const argv[])
-{
-    LOGV("Entered app_init()!\n");
-
-    AndroidRuntime* jr = AndroidRuntime::getRuntime();
-    jr->callMain(className, argc, argv);
-    
-    LOGV("Exiting app_init()!\n");
-    return NO_ERROR;
-}
-
 class AppRuntime : public AndroidRuntime
 {
 public:
     AppRuntime()
         : mParentDir(NULL)
         , mClassName(NULL)
+        , mClass(NULL)
         , mArgC(0)
         , mArgV(NULL)
     {
@@ -60,6 +50,35 @@
         return mClassName;
     }
 
+    virtual void onVmCreated(JNIEnv* env)
+    {
+        if (mClassName == NULL) {
+            return; // Zygote. Nothing to do here.
+        }
+
+        /*
+         * This is a little awkward because the JNI FindClass call uses the
+         * class loader associated with the native method we're executing in.
+         * If called in onStarted (from RuntimeInit.finishInit because we're
+         * launching "am", for example), FindClass would see that we're calling
+         * from a boot class' native method, and so wouldn't look for the class
+         * we're trying to look up in CLASSPATH. Unfortunately it needs to,
+         * because the "am" classes are not boot classes.
+         *
+         * The easiest fix is to call FindClass here, early on before we start
+         * executing boot class Java code and thereby deny ourselves access to
+         * non-boot classes.
+         */
+        char* slashClassName = toSlashClassName(mClassName);
+        mClass = env->FindClass(slashClassName);
+        if (mClass == NULL) {
+            LOGE("ERROR: could not find class '%s'\n", mClassName);
+        }
+        free(slashClassName);
+
+        mClass = reinterpret_cast<jclass>(env->NewGlobalRef(mClass));
+    }
+
     virtual void onStarted()
     {
         sp<ProcessState> proc = ProcessState::self();
@@ -67,8 +86,9 @@
             LOGV("App process: starting thread pool.\n");
             proc->startThreadPool();
         }
-        
-        app_init(mClassName, mArgC, mArgV);
+
+        AndroidRuntime* ar = AndroidRuntime::getRuntime();
+        ar->callMain(mClassName, mClass, mArgC, mArgV);
 
         if (ProcessState::self()->supportsProcesses()) {
             IPCThreadState::self()->stopProcess();
@@ -81,7 +101,7 @@
         if (proc->supportsProcesses()) {
             LOGV("App process: starting thread pool.\n");
             proc->startThreadPool();
-        }       
+        }
     }
 
     virtual void onExit(int code)
@@ -96,9 +116,10 @@
         AndroidRuntime::onExit(code);
     }
 
-    
+
     const char* mParentDir;
     const char* mClassName;
+    jclass mClass;
     int mArgC;
     const char* const* mArgV;
 };
@@ -120,7 +141,7 @@
     // These are global variables in ProcessState.cpp
     mArgC = argc;
     mArgV = argv;
-    
+
     mArgLen = 0;
     for (int i=0; i<argc; i++) {
         mArgLen += strlen(argv[i]) + 1;
@@ -139,7 +160,7 @@
     argv++;
 
     // Everything up to '--' or first non '-' arg goes to the vm
-    
+
     int i = runtime.addVmArguments(argc, argv);
 
     // Next arg is parent directory
@@ -151,7 +172,7 @@
     if (i < argc) {
         arg = argv[i++];
         if (0 == strcmp("--zygote", arg)) {
-            bool startSystemServer = (i < argc) ? 
+            bool startSystemServer = (i < argc) ?
                     strcmp(argv[i], "--start-system-server") == 0 : false;
             setArgv0(argv0, "zygote");
             set_process_name("zygote");
diff --git a/cmds/runtime/main_runtime.cpp b/cmds/runtime/main_runtime.cpp
index 83cb533..785e4cc 100644
--- a/cmds/runtime/main_runtime.cpp
+++ b/cmds/runtime/main_runtime.cpp
@@ -12,7 +12,7 @@
 
 #include <binder/IPCThreadState.h>
 #include <binder/ProcessState.h>
-#include <utils/Log.h>  
+#include <utils/Log.h>
 #include <cutils/zygote.h>
 
 #include <cutils/properties.h>
@@ -41,7 +41,7 @@
 #undef LOG_TAG
 #define LOG_TAG "runtime"
 
-static const char* ZYGOTE_ARGV[] = { 
+static const char* ZYGOTE_ARGV[] = {
     "--setuid=1000",
     "--setgid=1000",
     "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,3001,3002,3003",
@@ -68,7 +68,6 @@
 
 namespace android {
 
-extern status_t app_init(const char* className);
 extern void set_finish_init_func(void (*func)());
 
 
@@ -76,7 +75,7 @@
  * This class is used to kill this process (runtime) when the system_server dies.
  */
 class GrimReaper : public IBinder::DeathRecipient {
-public: 
+public:
     GrimReaper() { }
 
     virtual void binderDied(const wp<IBinder>& who)
@@ -170,7 +169,7 @@
 
 /*
  * Post-system-process initialization.
- * 
+ *
  * This function continues initialization after the system process
  * has been initialized.  It needs to be separate because the system
  * initialization needs to care of starting the Android runtime if it is not
@@ -210,17 +209,17 @@
 static void boot_init()
 {
     LOGI("Entered boot_init()!\n");
-    
+
     sp<ProcessState> proc(ProcessState::self());
     LOGD("ProcessState: %p\n", proc.get());
     proc->becomeContextManager(contextChecker, NULL);
-    
+
     if (proc->supportsProcesses()) {
         LOGI("Binder driver opened.  Multiprocess enabled.\n");
     } else {
         LOGI("Binder driver not found.  Processes not supported.\n");
     }
-    
+
     sp<BServiceManager> sm = new BServiceManager;
     proc->setContextObject(sm);
 }
@@ -258,7 +257,7 @@
     int res;
     time_t min_time = 1167652800; // jan 1 2007, type 'date -ud "1/1 12:00" +%s' to get value for current year
     struct timespec ts;
-    
+
     fd = open("/dev/alarm", O_RDWR);
     if(fd < 0) {
         LOGW("Unable to open alarm driver: %s\n", strerror(errno));
@@ -346,14 +345,14 @@
     int ic;
     int result = 1;
     pid_t systemPid;
-    
+
     sp<ProcessState> proc;
 
 #ifndef HAVE_ANDROID_OS
     /* Set stdout/stderr to unbuffered for MinGW/MSYS. */
     //setvbuf(stdout, NULL, _IONBF, 0);
     //setvbuf(stderr, NULL, _IONBF, 0);
-    
+
     LOGI("commandline args:\n");
     for (int i = 0; i < argc; i++)
         LOGI("  %2d: '%s'\n", i, argv[i]);
@@ -455,7 +454,7 @@
 
 #if 0
     // Hack to keep libc from beating the filesystem to death.  It's
-    // hitting /etc/localtime frequently, 
+    // hitting /etc/localtime frequently,
     //
     // This statement locks us into Pacific time.  We could do better,
     // but there's not much point until we're sure that the library
@@ -467,15 +466,15 @@
 
     /* track our progress through the boot sequence */
     const int LOG_BOOT_PROGRESS_START = 3000;
-    LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START, 
+    LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START,
         ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
 
     validateTime();
 
     proc = ProcessState::self();
-    
+
     boot_init();
-    
+
     /* If we are in multiprocess mode, have zygote spawn the system
      * server process and call system_init(). If we are running in
      * single process mode just call system_init() directly.
@@ -488,8 +487,8 @@
         property_get("log.redirect-stdio", propBuf, "");
         logStdio = (strcmp(propBuf, "true") == 0);
 
-        zygote_run_oneshot((int)(!logStdio), 
-                sizeof(ZYGOTE_ARGV) / sizeof(ZYGOTE_ARGV[0]), 
+        zygote_run_oneshot((int)(!logStdio),
+                sizeof(ZYGOTE_ARGV) / sizeof(ZYGOTE_ARGV[0]),
                 ZYGOTE_ARGV);
 
         //start_process("/system/bin/mediaserver");
@@ -497,7 +496,7 @@
     } else {
 #ifndef HAVE_ANDROID_OS
         QuickRuntime* runt = new QuickRuntime();
-        runt->start("com/android/server/SystemServer", 
+        runt->start("com/android/server/SystemServer",
                     false /* spontaneously fork system server from zygote */);
 #endif
     }
@@ -506,11 +505,11 @@
 
     finish_system_init(proc);
     run(proc);
-    
+
 bail:
     if (proc != NULL) {
         proc->setContextObject(NULL);
     }
-    
+
     return 0;
 }
diff --git a/cmds/stagefright/stream.cpp b/cmds/stagefright/stream.cpp
index bb84bd1..be443d0 100644
--- a/cmds/stagefright/stream.cpp
+++ b/cmds/stagefright/stream.cpp
@@ -107,7 +107,7 @@
         : mEOS(false) {
     }
 
-    virtual void notify(int msg, int ext1, int ext2) {
+    virtual void notify(int msg, int ext1, int ext2, const Parcel *obj) {
         Mutex::Autolock autoLock(mLock);
 
         if (msg == MEDIA_ERROR || msg == MEDIA_PLAYBACK_COMPLETE) {
diff --git a/cmds/system_server/library/system_init.cpp b/cmds/system_server/library/system_init.cpp
index a29ba73..b615764 100644
--- a/cmds/system_server/library/system_init.cpp
+++ b/cmds/system_server/library/system_init.cpp
@@ -37,7 +37,7 @@
  * This class is used to kill this process when the runtime dies.
  */
 class GrimReaper : public IBinder::DeathRecipient {
-public: 
+public:
     GrimReaper() { }
 
     virtual void binderDied(const wp<IBinder>& who)
@@ -54,15 +54,15 @@
 extern "C" status_t system_init()
 {
     LOGI("Entered system_init()");
-    
+
     sp<ProcessState> proc(ProcessState::self());
-    
+
     sp<IServiceManager> sm = defaultServiceManager();
     LOGI("ServiceManager: %p\n", sm.get());
-    
+
     sp<GrimReaper> grim = new GrimReaper();
     sm->asBinder()->linkToDeath(grim, grim.get(), 0);
-    
+
     char propBuf[PROPERTY_VALUE_MAX];
     property_get("system_init.startsurfaceflinger", propBuf, "1");
     if (strcmp(propBuf, "1") == 0) {
@@ -97,12 +97,23 @@
     // the beginning of their processes's main(), before calling
     // the init function.
     LOGI("System server: starting Android runtime.\n");
-    
     AndroidRuntime* runtime = AndroidRuntime::getRuntime();
 
     LOGI("System server: starting Android services.\n");
-    runtime->callStatic("com/android/server/SystemServer", "init2");
-        
+    JNIEnv* env = runtime->getJNIEnv();
+    if (env == NULL) {
+        return UNKNOWN_ERROR;
+    }
+    jclass clazz = env->FindClass("com/android/server/SystemServer");
+    if (clazz == NULL) {
+        return UNKNOWN_ERROR;
+    }
+    jmethodID methodId = env->GetStaticMethodID(clazz, "init2", "()V");
+    if (methodId == NULL) {
+        return UNKNOWN_ERROR;
+    }
+    env->CallStaticVoidMethod(clazz, methodId);
+
     // If running in our own process, just go into the thread
     // pool.  Otherwise, call the initialization finished
     // func to let this process continue its initilization.
@@ -114,4 +125,3 @@
     }
     return NO_ERROR;
 }
-
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 6c6a72d..328a55b 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -1086,7 +1086,7 @@
     /**
      * Area class for focus.
      *
-     * @see #setFocusAreas(List<Area>)
+     * @see #setFocusAreas(List)
      * @see #getFocusAreas()
      * @hide
      */
@@ -2573,8 +2573,8 @@
 
         /**
          * Gets the maximum number of focus areas supported. This is the maximum
-         * length of the list in {@link #setFocusArea(List<Area>)} and
-         * {@link #getFocusArea()}.
+         * length of the list in {@link #setFocusAreas(List)} and
+         * {@link #getFocusAreas()}.
          *
          * @return the maximum number of focus areas supported by the camera.
          * @see #getFocusAreas()
@@ -2588,8 +2588,8 @@
          * Gets the current focus areas. Camera driver uses the areas to decide
          * focus.
          *
-         * Before using this API or {@link #setFocusAreas(List<int>)}, apps
-         * should call {@link #getMaxNumFocusArea()} to know the maximum number of
+         * Before using this API or {@link #setFocusAreas(List)}, apps should
+         * call {@link #getMaxNumFocusAreas()} to know the maximum number of
          * focus areas first. If the value is 0, focus area is not supported.
          *
          * Each focus area is a rectangle with specified weight. The direction
@@ -2618,7 +2618,7 @@
          *
          * Focus area only has effect if the current focus mode is
          * {@link #FOCUS_MODE_AUTO}, {@link #FOCUS_MODE_MACRO}, or
-         * {@link #FOCUS_MODE_CONTINOUS_VIDEO}.
+         * {@link #FOCUS_MODE_CONTINUOUS_VIDEO}.
          *
          * @return a list of current focus areas
          * @hide
@@ -2630,7 +2630,7 @@
         /**
          * Sets focus areas. See {@link #getFocusAreas()} for documentation.
          *
-         * @param focusArea the focus areas
+         * @param focusAreas the focus areas
          * @see #getFocusAreas()
          * @hide
          */
@@ -2640,8 +2640,8 @@
 
         /**
          * Gets the maximum number of metering areas supported. This is the
-         * maximum length of the list in {@link #setMeteringArea(List<Area>)}
-         * and {@link #getMeteringArea()}.
+         * maximum length of the list in {@link #setMeteringAreas(List)} and
+         * {@link #getMeteringAreas()}.
          *
          * @return the maximum number of metering areas supported by the camera.
          * @see #getMeteringAreas()
@@ -2655,10 +2655,10 @@
          * Gets the current metering areas. Camera driver uses these areas to
          * decide exposure.
          *
-         * Before using this API or {@link #setMeteringAreas(List<int>)}, apps
-         * should call {@link #getMaxNumMeteringArea()} to know the maximum
-         * number of metering areas first. If the value is 0, metering area is
-         * not supported.
+         * Before using this API or {@link #setMeteringAreas(List)}, apps should
+         * call {@link #getMaxNumMeteringAreas()} to know the maximum number of
+         * metering areas first. If the value is 0, metering area is not
+         * supported.
          *
          * Each metering area is a rectangle with specified weight. The
          * direction is relative to the sensor orientation, that is, what the
@@ -2685,7 +2685,7 @@
          * even when using zoom.
          *
          * No matter what metering areas are, the final exposure are compensated
-         * by {@link setExposureCompensation(int)}.
+         * by {@link #setExposureCompensation(int)}.
          *
          * @return a list of current metering areas
          * @hide
@@ -2698,7 +2698,7 @@
          * Sets metering areas. See {@link #getMeteringAreas()} for
          * documentation.
          *
-         * @param meteringArea the metering areas
+         * @param meteringAreas the metering areas
          * @see #getMeteringAreas()
          * @hide
          */
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 90e2e79..b0f3ac3 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -26,6 +26,7 @@
 import android.telephony.SignalStrength;
 import android.util.Log;
 import android.util.Printer;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
 
@@ -408,15 +409,19 @@
     }
 
     public final static class HistoryItem implements Parcelable {
+        static final String TAG = "HistoryItem";
+        static final boolean DEBUG = false;
+        
         public HistoryItem next;
         
         public long time;
         
-        public static final byte CMD_UPDATE = 0;
-        public static final byte CMD_START = 1;
-        public static final byte CMD_OVERFLOW = 2;
+        public static final byte CMD_NULL = 0;
+        public static final byte CMD_UPDATE = 1;
+        public static final byte CMD_START = 2;
+        public static final byte CMD_OVERFLOW = 3;
         
-        public byte cmd;
+        public byte cmd = CMD_NULL;
         
         public byte batteryLevel;
         public byte batteryStatus;
@@ -427,33 +432,38 @@
         public char batteryVoltage;
         
         // Constants from SCREEN_BRIGHTNESS_*
-        public static final int STATE_BRIGHTNESS_MASK = 0x000000f;
+        public static final int STATE_BRIGHTNESS_MASK = 0x0000000f;
         public static final int STATE_BRIGHTNESS_SHIFT = 0;
         // Constants from SIGNAL_STRENGTH_*
-        public static final int STATE_SIGNAL_STRENGTH_MASK = 0x00000f0;
+        public static final int STATE_SIGNAL_STRENGTH_MASK = 0x000000f0;
         public static final int STATE_SIGNAL_STRENGTH_SHIFT = 4;
         // Constants from ServiceState.STATE_*
-        public static final int STATE_PHONE_STATE_MASK = 0x0000f00;
+        public static final int STATE_PHONE_STATE_MASK = 0x00000f00;
         public static final int STATE_PHONE_STATE_SHIFT = 8;
         // Constants from DATA_CONNECTION_*
-        public static final int STATE_DATA_CONNECTION_MASK = 0x000f000;
+        public static final int STATE_DATA_CONNECTION_MASK = 0x0000f000;
         public static final int STATE_DATA_CONNECTION_SHIFT = 12;
         
-        public static final int STATE_BATTERY_PLUGGED_FLAG = 1<<30;
-        public static final int STATE_SCREEN_ON_FLAG = 1<<29;
+        // These states always appear directly in the first int token
+        // of a delta change; they should be ones that change relatively
+        // frequently.
+        public static final int STATE_WAKE_LOCK_FLAG = 1<<30;
+        public static final int STATE_SENSOR_ON_FLAG = 1<<29;
         public static final int STATE_GPS_ON_FLAG = 1<<28;
-        public static final int STATE_PHONE_IN_CALL_FLAG = 1<<27;
-        public static final int STATE_PHONE_SCANNING_FLAG = 1<<26;
-        public static final int STATE_WIFI_ON_FLAG = 1<<25;
-        public static final int STATE_WIFI_RUNNING_FLAG = 1<<24;
-        public static final int STATE_WIFI_FULL_LOCK_FLAG = 1<<23;
-        public static final int STATE_WIFI_SCAN_LOCK_FLAG = 1<<22;
-        public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<21;
-        public static final int STATE_BLUETOOTH_ON_FLAG = 1<<20;
-        public static final int STATE_AUDIO_ON_FLAG = 1<<19;
-        public static final int STATE_VIDEO_ON_FLAG = 1<<18;
-        public static final int STATE_WAKE_LOCK_FLAG = 1<<17;
-        public static final int STATE_SENSOR_ON_FLAG = 1<<16;
+        public static final int STATE_PHONE_SCANNING_FLAG = 1<<27;
+        public static final int STATE_WIFI_RUNNING_FLAG = 1<<26;
+        public static final int STATE_WIFI_FULL_LOCK_FLAG = 1<<25;
+        public static final int STATE_WIFI_SCAN_LOCK_FLAG = 1<<24;
+        public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<23;
+        // These are on the lower bits used for the command; if they change
+        // we need to write another int of data.
+        public static final int STATE_AUDIO_ON_FLAG = 1<<22;
+        public static final int STATE_VIDEO_ON_FLAG = 1<<21;
+        public static final int STATE_SCREEN_ON_FLAG = 1<<20;
+        public static final int STATE_BATTERY_PLUGGED_FLAG = 1<<19;
+        public static final int STATE_PHONE_IN_CALL_FLAG = 1<<18;
+        public static final int STATE_WIFI_ON_FLAG = 1<<17;
+        public static final int STATE_BLUETOOTH_ON_FLAG = 1<<16;
         
         public static final int MOST_INTERESTING_STATES =
             STATE_BATTERY_PLUGGED_FLAG | STATE_SCREEN_ON_FLAG
@@ -466,16 +476,7 @@
         
         public HistoryItem(long time, Parcel src) {
             this.time = time;
-            int bat = src.readInt();
-            cmd = (byte)(bat&0xff);
-            batteryLevel = (byte)((bat>>8)&0xff);
-            batteryStatus = (byte)((bat>>16)&0xf);
-            batteryHealth = (byte)((bat>>20)&0xf);
-            batteryPlugType = (byte)((bat>>24)&0xf);
-            bat = src.readInt();
-            batteryTemperature = (char)(bat&0xffff);
-            batteryVoltage = (char)((bat>>16)&0xffff);
-            states = src.readInt();
+            readFromParcel(src);
         }
         
         public int describeContents() {
@@ -495,6 +496,174 @@
             dest.writeInt(bat);
             dest.writeInt(states);
         }
+
+        private void readFromParcel(Parcel src) {
+            int bat = src.readInt();
+            cmd = (byte)(bat&0xff);
+            batteryLevel = (byte)((bat>>8)&0xff);
+            batteryStatus = (byte)((bat>>16)&0xf);
+            batteryHealth = (byte)((bat>>20)&0xf);
+            batteryPlugType = (byte)((bat>>24)&0xf);
+            bat = src.readInt();
+            batteryTemperature = (char)(bat&0xffff);
+            batteryVoltage = (char)((bat>>16)&0xffff);
+            states = src.readInt();
+        }
+
+        // Part of initial delta int that specifies the time delta.
+        static final int DELTA_TIME_MASK = 0x3ffff;
+        static final int DELTA_TIME_ABS = 0x3fffd;    // Following is an entire abs update.
+        static final int DELTA_TIME_INT = 0x3fffe;    // The delta is a following int
+        static final int DELTA_TIME_LONG = 0x3ffff;   // The delta is a following long
+        // Part of initial delta int holding the command code.
+        static final int DELTA_CMD_MASK = 0x3;
+        static final int DELTA_CMD_SHIFT = 18;
+        // Flag in delta int: a new battery level int follows.
+        static final int DELTA_BATTERY_LEVEL_FLAG = 1<<20;
+        // Flag in delta int: a new full state and battery status int follows.
+        static final int DELTA_STATE_FLAG = 1<<21;
+        static final int DELTA_STATE_MASK = 0xffc00000;
+        
+        public void writeDelta(Parcel dest, HistoryItem last) {
+            if (last == null || last.cmd != CMD_UPDATE) {
+                dest.writeInt(DELTA_TIME_ABS);
+                writeToParcel(dest, 0);
+                return;
+            }
+            
+            final long deltaTime = time - last.time;
+            final int lastBatteryLevelInt = last.buildBatteryLevelInt();
+            final int lastStateInt = last.buildStateInt();
+            
+            int deltaTimeToken;
+            if (deltaTime < 0 || deltaTime > Integer.MAX_VALUE) {
+                deltaTimeToken = DELTA_TIME_LONG;
+            } else if (deltaTime >= DELTA_TIME_ABS) {
+                deltaTimeToken = DELTA_TIME_INT;
+            } else {
+                deltaTimeToken = (int)deltaTime;
+            }
+            int firstToken = deltaTimeToken
+                    | (cmd<<DELTA_CMD_SHIFT)
+                    | (states&DELTA_STATE_MASK);
+            final int batteryLevelInt = buildBatteryLevelInt();
+            final boolean batteryLevelIntChanged = batteryLevelInt != lastBatteryLevelInt;
+            if (batteryLevelIntChanged) {
+                firstToken |= DELTA_BATTERY_LEVEL_FLAG;
+            }
+            final int stateInt = buildStateInt();
+            final boolean stateIntChanged = stateInt != lastStateInt;
+            if (stateIntChanged) {
+                firstToken |= DELTA_STATE_FLAG;
+            }
+            dest.writeInt(firstToken);
+            if (DEBUG) Slog.i(TAG, "WRITE DELTA: firstToken=0x" + Integer.toHexString(firstToken)
+                    + " deltaTime=" + deltaTime);
+            
+            if (deltaTimeToken >= DELTA_TIME_INT) {
+                if (deltaTimeToken == DELTA_TIME_INT) {
+                    if (DEBUG) Slog.i(TAG, "WRITE DELTA: int deltaTime=" + (int)deltaTime);
+                    dest.writeInt((int)deltaTime);
+                } else {
+                    if (DEBUG) Slog.i(TAG, "WRITE DELTA: long deltaTime=" + deltaTime);
+                    dest.writeLong(deltaTime);
+                }
+            }
+            if (batteryLevelIntChanged) {
+                dest.writeInt(batteryLevelInt);
+                if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryToken=0x"
+                        + Integer.toHexString(batteryLevelInt)
+                        + " batteryLevel=" + batteryLevel
+                        + " batteryTemp=" + (int)batteryTemperature
+                        + " batteryVolt=" + (int)batteryVoltage);
+            }
+            if (stateIntChanged) {
+                dest.writeInt(stateInt);
+                if (DEBUG) Slog.i(TAG, "WRITE DELTA: stateToken=0x"
+                        + Integer.toHexString(stateInt)
+                        + " batteryStatus=" + batteryStatus
+                        + " batteryHealth=" + batteryHealth
+                        + " batteryPlugType=" + batteryPlugType
+                        + " states=0x" + Integer.toHexString(states));
+            }
+        }
+        
+        private int buildBatteryLevelInt() {
+            return ((((int)batteryLevel)<<24)&0xff000000)
+                    | ((((int)batteryTemperature)<<14)&0x00ffc000)
+                    | (((int)batteryVoltage)&0x00003fff);
+        }
+        
+        private int buildStateInt() {
+            return ((((int)batteryStatus)<<28)&0xf0000000)
+                    | ((((int)batteryHealth)<<24)&0x0f000000)
+                    | ((((int)batteryPlugType)<<22)&0x00c00000)
+                    | (states&(~DELTA_STATE_MASK));
+        }
+        
+        public void readDelta(Parcel src) {
+            int firstToken = src.readInt();
+            int deltaTimeToken = firstToken&DELTA_TIME_MASK;
+            cmd = (byte)((firstToken>>DELTA_CMD_SHIFT)&DELTA_CMD_MASK);
+            if (DEBUG) Slog.i(TAG, "READ DELTA: firstToken=0x" + Integer.toHexString(firstToken)
+                    + " deltaTimeToken=" + deltaTimeToken);
+            
+            if (deltaTimeToken < DELTA_TIME_ABS) {
+                time += deltaTimeToken;
+            } else if (deltaTimeToken == DELTA_TIME_ABS) {
+                time = src.readLong();
+                readFromParcel(src);
+                return;
+            } else if (deltaTimeToken == DELTA_TIME_INT) {
+                int delta = src.readInt();
+                time += delta;
+                if (DEBUG) Slog.i(TAG, "READ DELTA: time delta=" + delta + " new time=" + time);
+            } else {
+                long delta = src.readLong();
+                if (DEBUG) Slog.i(TAG, "READ DELTA: time delta=" + delta + " new time=" + time);
+                time += delta;
+            }
+            
+            if ((firstToken&DELTA_BATTERY_LEVEL_FLAG) != 0) {
+                int batteryLevelInt = src.readInt();
+                batteryLevel = (byte)((batteryLevelInt>>24)&0xff);
+                batteryTemperature = (char)((batteryLevelInt>>14)&0x3ff);
+                batteryVoltage = (char)(batteryLevelInt&0x3fff);
+                if (DEBUG) Slog.i(TAG, "READ DELTA: batteryToken=0x"
+                        + Integer.toHexString(batteryLevelInt)
+                        + " batteryLevel=" + batteryLevel
+                        + " batteryTemp=" + (int)batteryTemperature
+                        + " batteryVolt=" + (int)batteryVoltage);
+            }
+            
+            if ((firstToken&DELTA_STATE_FLAG) != 0) {
+                int stateInt = src.readInt();
+                states = (firstToken&DELTA_STATE_MASK) | (stateInt&(~DELTA_STATE_MASK));
+                batteryStatus = (byte)((stateInt>>28)&0xf);
+                batteryHealth = (byte)((stateInt>>24)&0xf);
+                batteryPlugType = (byte)((stateInt>>22)&0x3);
+                if (DEBUG) Slog.i(TAG, "READ DELTA: stateToken=0x"
+                        + Integer.toHexString(stateInt)
+                        + " batteryStatus=" + batteryStatus
+                        + " batteryHealth=" + batteryHealth
+                        + " batteryPlugType=" + batteryPlugType
+                        + " states=0x" + Integer.toHexString(states));
+            } else {
+                states = (firstToken&DELTA_STATE_MASK) | (states&(~DELTA_STATE_MASK));
+            }
+        }
+
+        public void clear() {
+            time = 0;
+            cmd = CMD_NULL;
+            batteryLevel = 0;
+            batteryStatus = 0;
+            batteryHealth = 0;
+            batteryPlugType = 0;
+            batteryTemperature = 0;
+            batteryVoltage = 0;
+            states = 0;
+        }
         
         public void setTo(HistoryItem o) {
             time = o.time;
@@ -556,11 +725,14 @@
 
     public abstract boolean getNextHistoryLocked(HistoryItem out);
 
-    /**
-     * Return the current history of battery state changes.
-     */
-    public abstract HistoryItem getHistory();
-    
+    public abstract void finishIteratingHistoryLocked();
+
+    public abstract boolean startIteratingOldHistoryLocked();
+
+    public abstract boolean getNextOldHistoryLocked(HistoryItem out);
+
+    public abstract void finishIteratingOldHistoryLocked();
+
     /**
      * Return the base time offset for the battery history.
      */
@@ -1729,7 +1901,7 @@
         }
     }
 
-    void printBitDescriptions(PrintWriter pw, int oldval, int newval, BitDescription[] descriptions) {
+    static void printBitDescriptions(PrintWriter pw, int oldval, int newval, BitDescription[] descriptions) {
         int diff = oldval ^ newval;
         if (diff == 0) return;
         for (int i=0; i<descriptions.length; i++) {
@@ -1753,6 +1925,125 @@
         }
     }
     
+    public void prepareForDumpLocked() {
+    }
+
+    public static class HistoryPrinter {
+        int oldState = 0;
+        int oldStatus = -1;
+        int oldHealth = -1;
+        int oldPlug = -1;
+        int oldTemp = -1;
+        int oldVolt = -1;
+
+        public void printNextItem(PrintWriter pw, HistoryItem rec, long now) {
+            pw.print("  ");
+            TimeUtils.formatDuration(rec.time-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN);
+            pw.print(" ");
+            if (rec.cmd == HistoryItem.CMD_START) {
+                pw.println(" START");
+            } else if (rec.cmd == HistoryItem.CMD_OVERFLOW) {
+                pw.println(" *OVERFLOW*");
+            } else {
+                if (rec.batteryLevel < 10) pw.print("00");
+                else if (rec.batteryLevel < 100) pw.print("0");
+                pw.print(rec.batteryLevel);
+                pw.print(" ");
+                if (rec.states < 0x10) pw.print("0000000");
+                else if (rec.states < 0x100) pw.print("000000");
+                else if (rec.states < 0x1000) pw.print("00000");
+                else if (rec.states < 0x10000) pw.print("0000");
+                else if (rec.states < 0x100000) pw.print("000");
+                else if (rec.states < 0x1000000) pw.print("00");
+                else if (rec.states < 0x10000000) pw.print("0");
+                pw.print(Integer.toHexString(rec.states));
+                if (oldStatus != rec.batteryStatus) {
+                    oldStatus = rec.batteryStatus;
+                    pw.print(" status=");
+                    switch (oldStatus) {
+                        case BatteryManager.BATTERY_STATUS_UNKNOWN:
+                            pw.print("unknown");
+                            break;
+                        case BatteryManager.BATTERY_STATUS_CHARGING:
+                            pw.print("charging");
+                            break;
+                        case BatteryManager.BATTERY_STATUS_DISCHARGING:
+                            pw.print("discharging");
+                            break;
+                        case BatteryManager.BATTERY_STATUS_NOT_CHARGING:
+                            pw.print("not-charging");
+                            break;
+                        case BatteryManager.BATTERY_STATUS_FULL:
+                            pw.print("full");
+                            break;
+                        default:
+                            pw.print(oldStatus);
+                            break;
+                    }
+                }
+                if (oldHealth != rec.batteryHealth) {
+                    oldHealth = rec.batteryHealth;
+                    pw.print(" health=");
+                    switch (oldHealth) {
+                        case BatteryManager.BATTERY_HEALTH_UNKNOWN:
+                            pw.print("unknown");
+                            break;
+                        case BatteryManager.BATTERY_HEALTH_GOOD:
+                            pw.print("good");
+                            break;
+                        case BatteryManager.BATTERY_HEALTH_OVERHEAT:
+                            pw.print("overheat");
+                            break;
+                        case BatteryManager.BATTERY_HEALTH_DEAD:
+                            pw.print("dead");
+                            break;
+                        case BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE:
+                            pw.print("over-voltage");
+                            break;
+                        case BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE:
+                            pw.print("failure");
+                            break;
+                        default:
+                            pw.print(oldHealth);
+                            break;
+                    }
+                }
+                if (oldPlug != rec.batteryPlugType) {
+                    oldPlug = rec.batteryPlugType;
+                    pw.print(" plug=");
+                    switch (oldPlug) {
+                        case 0:
+                            pw.print("none");
+                            break;
+                        case BatteryManager.BATTERY_PLUGGED_AC:
+                            pw.print("ac");
+                            break;
+                        case BatteryManager.BATTERY_PLUGGED_USB:
+                            pw.print("usb");
+                            break;
+                        default:
+                            pw.print(oldPlug);
+                            break;
+                    }
+                }
+                if (oldTemp != rec.batteryTemperature) {
+                    oldTemp = rec.batteryTemperature;
+                    pw.print(" temp=");
+                    pw.print(oldTemp);
+                }
+                if (oldVolt != rec.batteryVoltage) {
+                    oldVolt = rec.batteryVoltage;
+                    pw.print(" volt=");
+                    pw.print(oldVolt);
+                }
+                printBitDescriptions(pw, oldState, rec.states,
+                        HISTORY_STATE_DESCRIPTIONS);
+                pw.println();
+            }
+            oldState = rec.states;
+        }
+    }
+
     /**
      * Dumps a human-readable summary of the battery statistics to the given PrintWriter.
      *
@@ -1760,122 +2051,28 @@
      */
     @SuppressWarnings("unused")
     public void dumpLocked(PrintWriter pw) {
+        prepareForDumpLocked();
+
+        long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
+
         final HistoryItem rec = new HistoryItem();
         if (startIteratingHistoryLocked()) {
             pw.println("Battery History:");
-            long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
-            int oldState = 0;
-            int oldStatus = -1;
-            int oldHealth = -1;
-            int oldPlug = -1;
-            int oldTemp = -1;
-            int oldVolt = -1;
+            HistoryPrinter hprinter = new HistoryPrinter();
             while (getNextHistoryLocked(rec)) {
-                pw.print("  ");
-                TimeUtils.formatDuration(rec.time-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN);
-                pw.print(" ");
-                if (rec.cmd == HistoryItem.CMD_START) {
-                    pw.println(" START");
-                } else if (rec.cmd == HistoryItem.CMD_OVERFLOW) {
-                    pw.println(" *OVERFLOW*");
-                } else {
-                    if (rec.batteryLevel < 10) pw.print("00");
-                    else if (rec.batteryLevel < 100) pw.print("0");
-                    pw.print(rec.batteryLevel);
-                    pw.print(" ");
-                    if (rec.states < 0x10) pw.print("0000000");
-                    else if (rec.states < 0x100) pw.print("000000");
-                    else if (rec.states < 0x1000) pw.print("00000");
-                    else if (rec.states < 0x10000) pw.print("0000");
-                    else if (rec.states < 0x100000) pw.print("000");
-                    else if (rec.states < 0x1000000) pw.print("00");
-                    else if (rec.states < 0x10000000) pw.print("0");
-                    pw.print(Integer.toHexString(rec.states));
-                    if (oldStatus != rec.batteryStatus) {
-                        oldStatus = rec.batteryStatus;
-                        pw.print(" status=");
-                        switch (oldStatus) {
-                            case BatteryManager.BATTERY_STATUS_UNKNOWN:
-                                pw.print("unknown");
-                                break;
-                            case BatteryManager.BATTERY_STATUS_CHARGING:
-                                pw.print("charging");
-                                break;
-                            case BatteryManager.BATTERY_STATUS_DISCHARGING:
-                                pw.print("discharging");
-                                break;
-                            case BatteryManager.BATTERY_STATUS_NOT_CHARGING:
-                                pw.print("not-charging");
-                                break;
-                            case BatteryManager.BATTERY_STATUS_FULL:
-                                pw.print("full");
-                                break;
-                            default:
-                                pw.print(oldStatus);
-                                break;
-                        }
-                    }
-                    if (oldHealth != rec.batteryHealth) {
-                        oldHealth = rec.batteryHealth;
-                        pw.print(" health=");
-                        switch (oldHealth) {
-                            case BatteryManager.BATTERY_HEALTH_UNKNOWN:
-                                pw.print("unknown");
-                                break;
-                            case BatteryManager.BATTERY_HEALTH_GOOD:
-                                pw.print("good");
-                                break;
-                            case BatteryManager.BATTERY_HEALTH_OVERHEAT:
-                                pw.print("overheat");
-                                break;
-                            case BatteryManager.BATTERY_HEALTH_DEAD:
-                                pw.print("dead");
-                                break;
-                            case BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE:
-                                pw.print("over-voltage");
-                                break;
-                            case BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE:
-                                pw.print("failure");
-                                break;
-                            default:
-                                pw.print(oldHealth);
-                                break;
-                        }
-                    }
-                    if (oldPlug != rec.batteryPlugType) {
-                        oldPlug = rec.batteryPlugType;
-                        pw.print(" plug=");
-                        switch (oldPlug) {
-                            case 0:
-                                pw.print("none");
-                                break;
-                            case BatteryManager.BATTERY_PLUGGED_AC:
-                                pw.print("ac");
-                                break;
-                            case BatteryManager.BATTERY_PLUGGED_USB:
-                                pw.print("usb");
-                                break;
-                            default:
-                                pw.print(oldPlug);
-                                break;
-                        }
-                    }
-                    if (oldTemp != rec.batteryTemperature) {
-                        oldTemp = rec.batteryTemperature;
-                        pw.print(" temp=");
-                        pw.print(oldTemp);
-                    }
-                    if (oldVolt != rec.batteryVoltage) {
-                        oldVolt = rec.batteryVoltage;
-                        pw.print(" volt=");
-                        pw.print(oldVolt);
-                    }
-                    printBitDescriptions(pw, oldState, rec.states,
-                            HISTORY_STATE_DESCRIPTIONS);
-                    pw.println();
-                }
-                oldState = rec.states;
+                hprinter.printNextItem(pw, rec, now);
             }
+            finishIteratingHistoryLocked();
+            pw.println("");
+        }
+
+        if (startIteratingOldHistoryLocked()) {
+            pw.println("Old battery History:");
+            HistoryPrinter hprinter = new HistoryPrinter();
+            while (getNextOldHistoryLocked(rec)) {
+                hprinter.printNextItem(pw, rec, now);
+            }
+            finishIteratingOldHistoryLocked();
             pw.println("");
         }
         
@@ -1918,6 +2115,8 @@
     
     @SuppressWarnings("unused")
     public void dumpCheckinLocked(PrintWriter pw, String[] args, List<ApplicationInfo> apps) {
+        prepareForDumpLocked();
+
         boolean isUnpluggedOnly = false;
         
         for (String arg : args) {
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index aa959b4..f7661b6 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -35,64 +35,64 @@
     //consider ParcelFileDescriptor A(fileDescriptor fd),  ParcelFileDescriptor B(A)
     //in this particular case fd.close might be invoked twice.
     private final ParcelFileDescriptor mParcelDescriptor;
-    
+
     /**
      * For use with {@link #open}: if {@link #MODE_CREATE} has been supplied
      * and this file doesn't already exist, then create the file with
      * permissions such that any application can read it.
      */
     public static final int MODE_WORLD_READABLE = 0x00000001;
-    
+
     /**
      * For use with {@link #open}: if {@link #MODE_CREATE} has been supplied
      * and this file doesn't already exist, then create the file with
      * permissions such that any application can write it.
      */
     public static final int MODE_WORLD_WRITEABLE = 0x00000002;
-    
+
     /**
      * For use with {@link #open}: open the file with read-only access.
      */
     public static final int MODE_READ_ONLY = 0x10000000;
-    
+
     /**
      * For use with {@link #open}: open the file with write-only access.
      */
     public static final int MODE_WRITE_ONLY = 0x20000000;
-    
+
     /**
      * For use with {@link #open}: open the file with read and write access.
      */
     public static final int MODE_READ_WRITE = 0x30000000;
-    
+
     /**
      * For use with {@link #open}: create the file if it doesn't already exist.
      */
     public static final int MODE_CREATE = 0x08000000;
-    
+
     /**
      * For use with {@link #open}: erase contents of file when opening.
      */
     public static final int MODE_TRUNCATE = 0x04000000;
-    
+
     /**
      * For use with {@link #open}: append to end of file while writing.
      */
     public static final int MODE_APPEND = 0x02000000;
-    
+
     /**
      * Create a new ParcelFileDescriptor accessing a given file.
-     * 
+     *
      * @param file The file to be opened.
      * @param mode The desired access mode, must be one of
      * {@link #MODE_READ_ONLY}, {@link #MODE_WRITE_ONLY}, or
      * {@link #MODE_READ_WRITE}; may also be any combination of
      * {@link #MODE_CREATE}, {@link #MODE_TRUNCATE},
      * {@link #MODE_WORLD_READABLE}, and {@link #MODE_WORLD_WRITEABLE}.
-     * 
+     *
      * @return Returns a new ParcelFileDescriptor pointing to the given
      * file.
-     * 
+     *
      * @throws FileNotFoundException Throws FileNotFoundException if the given
      * file does not exist or can not be opened with the requested mode.
      */
@@ -106,12 +106,12 @@
                 security.checkWrite(path);
             }
         }
-        
+
         if ((mode&MODE_READ_WRITE) == 0) {
             throw new IllegalArgumentException(
                     "Must specify MODE_READ_ONLY, MODE_WRITE_ONLY, or MODE_READ_WRITE");
         }
-        
+
         FileDescriptor fd = Parcel.openFileDescriptor(path, mode);
         return fd != null ? new ParcelFileDescriptor(fd) : null;
     }
@@ -137,13 +137,10 @@
      *         specified Socket.
      */
     public static ParcelFileDescriptor fromSocket(Socket socket) {
-        FileDescriptor fd = getFileDescriptorFromSocket(socket);
+        FileDescriptor fd = socket.getFileDescriptor$();
         return fd != null ? new ParcelFileDescriptor(fd) : null;
     }
 
-    // Extracts the file descriptor from the specified socket and returns it untouched
-    private static native FileDescriptor getFileDescriptorFromSocket(Socket socket);
-
     /**
      * Create two ParcelFileDescriptors structured as a data pipe.  The first
      * ParcelFileDescriptor in the returned array is the read side; the second
@@ -187,26 +184,26 @@
 
     /**
      * Retrieve the actual FileDescriptor associated with this object.
-     * 
+     *
      * @return Returns the FileDescriptor associated with this object.
      */
     public FileDescriptor getFileDescriptor() {
         return mFileDescriptor;
     }
-    
+
     /**
      * Return the total size of the file representing this fd, as determined
      * by stat().  Returns -1 if the fd is not a file.
      */
     public native long getStatSize();
-    
+
     /**
      * This is needed for implementing AssetFileDescriptor.AutoCloseOutputStream,
      * and I really don't think we want it to be public.
      * @hide
      */
     public native long seekTo(long pos);
-    
+
     /**
      * Return the native fd int for this ParcelFileDescriptor.  The
      * ParcelFileDescriptor still owns the fd, and it still must be closed
@@ -218,9 +215,9 @@
         }
         return getFdNative();
     }
-    
+
     private native int getFdNative();
-    
+
     /**
      * Return the native fd int for this ParcelFileDescriptor and detach it
      * from the object here.  You are now responsible for closing the fd in
@@ -240,11 +237,11 @@
         Parcel.clearFileDescriptor(mFileDescriptor);
         return fd;
     }
-    
+
     /**
      * Close the ParcelFileDescriptor. This implementation closes the underlying
      * OS resources allocated to represent this stream.
-     * 
+     *
      * @throws IOException
      *             If an error occurs attempting to close this ParcelFileDescriptor.
      */
@@ -261,7 +258,7 @@
             Parcel.closeFileDescriptor(mFileDescriptor);
         }
     }
-    
+
     /**
      * An InputStream you can create on a ParcelFileDescriptor, which will
      * take care of calling {@link ParcelFileDescriptor#close
@@ -269,7 +266,7 @@
      */
     public static class AutoCloseInputStream extends FileInputStream {
         private final ParcelFileDescriptor mFd;
-        
+
         public AutoCloseInputStream(ParcelFileDescriptor fd) {
             super(fd.getFileDescriptor());
             mFd = fd;
@@ -284,7 +281,7 @@
             }
         }
     }
-    
+
     /**
      * An OutputStream you can create on a ParcelFileDescriptor, which will
      * take care of calling {@link ParcelFileDescriptor#close
@@ -292,7 +289,7 @@
      */
     public static class AutoCloseOutputStream extends FileOutputStream {
         private final ParcelFileDescriptor mFd;
-        
+
         public AutoCloseOutputStream(ParcelFileDescriptor fd) {
             super(fd.getFileDescriptor());
             mFd = fd;
@@ -307,12 +304,12 @@
             }
         }
     }
-    
+
     @Override
     public String toString() {
         return "{ParcelFileDescriptor: " + mFileDescriptor + "}";
     }
-    
+
     @Override
     protected void finalize() throws Throwable {
         try {
@@ -323,13 +320,13 @@
             super.finalize();
         }
     }
-    
+
     public ParcelFileDescriptor(ParcelFileDescriptor descriptor) {
         super();
         mParcelDescriptor = descriptor;
         mFileDescriptor = mParcelDescriptor.mFileDescriptor;
     }
-    
+
     /*package */ParcelFileDescriptor(FileDescriptor descriptor) {
         super();
         if (descriptor == null) {
@@ -338,7 +335,7 @@
         mFileDescriptor = descriptor;
         mParcelDescriptor = null;
     }
-    
+
     /* Parcelable interface */
     public int describeContents() {
         return Parcelable.CONTENTS_FILE_DESCRIPTOR;
diff --git a/core/java/android/speech/tts/BlockingMediaPlayer.java b/core/java/android/speech/tts/BlockingMediaPlayer.java
new file mode 100644
index 0000000..3cf60dd
--- /dev/null
+++ b/core/java/android/speech/tts/BlockingMediaPlayer.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2011 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.speech.tts;
+
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.util.Log;
+
+/**
+ * A media player that allows blocking to wait for it to finish.
+ */
+class BlockingMediaPlayer {
+
+    private static final String TAG = "BlockMediaPlayer";
+
+    private static final String MEDIA_PLAYER_THREAD_NAME = "TTS-MediaPlayer";
+
+    private final Context mContext;
+    private final Uri mUri;
+    private final int mStreamType;
+    private final ConditionVariable mDone;
+    // Only accessed on the Handler thread
+    private MediaPlayer mPlayer;
+    private volatile boolean mFinished;
+
+    /**
+     * Creates a new blocking media player.
+     * Creating a blocking media player is a cheap operation.
+     *
+     * @param context
+     * @param uri
+     * @param streamType
+     */
+    public BlockingMediaPlayer(Context context, Uri uri, int streamType) {
+        mContext = context;
+        mUri = uri;
+        mStreamType = streamType;
+        mDone = new ConditionVariable();
+
+    }
+
+    /**
+     * Starts playback and waits for it to finish.
+     * Can be called from any thread.
+     *
+     * @return {@code true} if the playback finished normally, {@code false} if the playback
+     *         failed or {@link #stop} was called before the playback finished.
+     */
+    public boolean startAndWait() {
+        HandlerThread thread = new HandlerThread(MEDIA_PLAYER_THREAD_NAME);
+        thread.start();
+        Handler handler = new Handler(thread.getLooper());
+        mFinished = false;
+        handler.post(new Runnable() {
+            @Override
+            public void run() {
+                startPlaying();
+            }
+        });
+        mDone.block();
+        handler.post(new Runnable() {
+            @Override
+            public void run() {
+                finish();
+                // No new messages should get posted to the handler thread after this
+                Looper.myLooper().quit();
+            }
+        });
+        return mFinished;
+    }
+
+    /**
+     * Stops playback. Can be called multiple times.
+     * Can be called from any thread.
+     */
+    public void stop() {
+        mDone.open();
+    }
+
+    /**
+     * Starts playback.
+     * Called on the handler thread.
+     */
+    private void startPlaying() {
+        mPlayer = MediaPlayer.create(mContext, mUri);
+        if (mPlayer == null) {
+            Log.w(TAG, "Failed to play " + mUri);
+            mDone.open();
+            return;
+        }
+        try {
+            mPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+                @Override
+                public boolean onError(MediaPlayer mp, int what, int extra) {
+                    Log.w(TAG, "Audio playback error: " + what + ", " + extra);
+                    mDone.open();
+                    return true;
+                }
+            });
+            mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+                @Override
+                public void onCompletion(MediaPlayer mp) {
+                    mFinished = true;
+                    mDone.open();
+                }
+            });
+            mPlayer.setAudioStreamType(mStreamType);
+            mPlayer.start();
+        } catch (IllegalArgumentException ex) {
+            Log.w(TAG, "MediaPlayer failed", ex);
+            mDone.open();
+        }
+    }
+
+    /**
+     * Stops playback and release the media player.
+     * Called on the handler thread.
+     */
+    private void finish() {
+        try {
+            mPlayer.stop();
+        } catch (IllegalStateException ex) {
+            // Do nothing, the player is already stopped
+        }
+        mPlayer.release();
+    }
+
+}
\ No newline at end of file
diff --git a/core/java/android/speech/tts/FileSynthesisRequest.java b/core/java/android/speech/tts/FileSynthesisRequest.java
new file mode 100644
index 0000000..6a9b2dc
--- /dev/null
+++ b/core/java/android/speech/tts/FileSynthesisRequest.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2011 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.speech.tts;
+
+import android.media.AudioFormat;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Speech synthesis request that writes the audio to a WAV file.
+ */
+class FileSynthesisRequest extends SynthesisRequest {
+
+    private static final String TAG = "FileSynthesisRequest";
+    private static final boolean DBG = false;
+
+    private static final int MAX_AUDIO_BUFFER_SIZE = 8192;
+
+    private static final int WAV_HEADER_LENGTH = 44;
+    private static final short WAV_FORMAT_PCM = 0x0001;
+
+    private final Object mStateLock = new Object();
+    private final File mFileName;
+    private int mSampleRateInHz;
+    private int mAudioFormat;
+    private int mChannelCount;
+    private RandomAccessFile mFile;
+    private boolean mStopped = false;
+
+    FileSynthesisRequest(String text, File fileName) {
+        super(text);
+        mFileName = fileName;
+    }
+
+    @Override
+    void stop() {
+        synchronized (mStateLock) {
+            mStopped = true;
+            cleanUp();
+        }
+    }
+
+    /**
+     * Must be called while holding the monitor on {@link #mStateLock}.
+     */
+    private void cleanUp() {
+        closeFile();
+        if (mFile != null) {
+            mFileName.delete();
+        }
+    }
+
+    /**
+     * Must be called while holding the monitor on {@link #mStateLock}.
+     */
+    private void closeFile() {
+        try {
+            if (mFile != null) {
+                mFile.close();
+                mFile = null;
+            }
+        } catch (IOException ex) {
+            Log.e(TAG, "Failed to close " + mFileName + ": " + ex);
+        }
+    }
+
+    @Override
+    public int getMaxBufferSize() {
+        return MAX_AUDIO_BUFFER_SIZE;
+    }
+
+    @Override
+    public int start(int sampleRateInHz, int audioFormat, int channelCount) {
+        if (DBG) {
+            Log.d(TAG, "FileSynthesisRequest.start(" + sampleRateInHz + "," + audioFormat
+                    + "," + channelCount + ")");
+        }
+        synchronized (mStateLock) {
+            if (mStopped) {
+                if (DBG) Log.d(TAG, "Request has been aborted.");
+                return TextToSpeech.ERROR;
+            }
+            if (mFile != null) {
+                cleanUp();
+                throw new IllegalArgumentException("FileSynthesisRequest.start() called twice");
+            }
+            mSampleRateInHz = sampleRateInHz;
+            mAudioFormat = audioFormat;
+            mChannelCount = channelCount;
+            try {
+                mFile = new RandomAccessFile(mFileName, "rw");
+                // Reserve space for WAV header
+                mFile.write(new byte[WAV_HEADER_LENGTH]);
+                return TextToSpeech.SUCCESS;
+            } catch (IOException ex) {
+                Log.e(TAG, "Failed to open " + mFileName + ": " + ex);
+                cleanUp();
+                return TextToSpeech.ERROR;
+            }
+        }
+    }
+
+    @Override
+    public int audioAvailable(byte[] buffer, int offset, int length) {
+        if (DBG) {
+            Log.d(TAG, "FileSynthesisRequest.audioAvailable(" + buffer + "," + offset
+                    + "," + length + ")");
+        }
+        synchronized (mStateLock) {
+            if (mStopped) {
+                if (DBG) Log.d(TAG, "Request has been aborted.");
+                return TextToSpeech.ERROR;
+            }
+            if (mFile == null) {
+                Log.e(TAG, "File not open");
+                return TextToSpeech.ERROR;
+            }
+            try {
+                mFile.write(buffer, offset, length);
+                return TextToSpeech.SUCCESS;
+            } catch (IOException ex) {
+                Log.e(TAG, "Failed to write to " + mFileName + ": " + ex);
+                cleanUp();
+                return TextToSpeech.ERROR;
+            }
+        }
+    }
+
+    @Override
+    public int done() {
+        if (DBG) Log.d(TAG, "FileSynthesisRequest.done()");
+        synchronized (mStateLock) {
+            if (mStopped) {
+                if (DBG) Log.d(TAG, "Request has been aborted.");
+                return TextToSpeech.ERROR;
+            }
+            if (mFile == null) {
+                Log.e(TAG, "File not open");
+                return TextToSpeech.ERROR;
+            }
+            try {
+                // Write WAV header at start of file
+                mFile.seek(0);
+                int dataLength = (int) (mFile.length() - WAV_HEADER_LENGTH);
+                mFile.write(
+                        makeWavHeader(mSampleRateInHz, mAudioFormat, mChannelCount, dataLength));
+                closeFile();
+                return TextToSpeech.SUCCESS;
+            } catch (IOException ex) {
+                Log.e(TAG, "Failed to write to " + mFileName + ": " + ex);
+                cleanUp();
+                return TextToSpeech.ERROR;
+            }
+        }
+    }
+
+    @Override
+    public int completeAudioAvailable(int sampleRateInHz, int audioFormat, int channelCount,
+            byte[] buffer, int offset, int length) {
+        synchronized (mStateLock) {
+            if (mStopped) {
+                if (DBG) Log.d(TAG, "Request has been aborted.");
+                return TextToSpeech.ERROR;
+            }
+        }
+        FileOutputStream out = null;
+        try {
+            out = new FileOutputStream(mFileName);
+            out.write(makeWavHeader(sampleRateInHz, audioFormat, channelCount, length));
+            out.write(buffer, offset, length);
+            return TextToSpeech.SUCCESS;
+        } catch (IOException ex) {
+            Log.e(TAG, "Failed to write to " + mFileName + ": " + ex);
+            return TextToSpeech.ERROR;
+        } finally {
+            try {
+                if (out != null) {
+                    out.close();
+                }
+            } catch (IOException ex) {
+                Log.e(TAG, "Failed to close " + mFileName + ": " + ex);
+            }
+        }
+    }
+
+    private byte[] makeWavHeader(int sampleRateInHz, int audioFormat, int channelCount,
+            int dataLength) {
+        // TODO: is AudioFormat.ENCODING_DEFAULT always the same as ENCODING_PCM_16BIT?
+        int sampleSizeInBytes = (audioFormat == AudioFormat.ENCODING_PCM_8BIT ? 1 : 2);
+        int byteRate = sampleRateInHz * sampleSizeInBytes * channelCount;
+        short blockAlign = (short) (sampleSizeInBytes * channelCount);
+        short bitsPerSample = (short) (sampleSizeInBytes * 8);
+
+        byte[] headerBuf = new byte[WAV_HEADER_LENGTH];
+        ByteBuffer header = ByteBuffer.wrap(headerBuf);
+        header.order(ByteOrder.LITTLE_ENDIAN);
+
+        header.put(new byte[]{ 'R', 'I', 'F', 'F' });
+        header.putInt(dataLength + WAV_HEADER_LENGTH - 8);  // RIFF chunk size
+        header.put(new byte[]{ 'W', 'A', 'V', 'E' });
+        header.put(new byte[]{ 'f', 'm', 't', ' ' });
+        header.putInt(16);  // size of fmt chunk
+        header.putShort(WAV_FORMAT_PCM);
+        header.putShort((short) channelCount);
+        header.putInt(sampleRateInHz);
+        header.putInt(byteRate);
+        header.putShort(blockAlign);
+        header.putShort(bitsPerSample);
+        header.put(new byte[]{ 'd', 'a', 't', 'a' });
+        header.putInt(dataLength);
+
+        return headerBuf;
+    }
+
+}
diff --git a/core/java/android/speech/tts/ITtsCallback.aidl b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
similarity index 78%
rename from core/java/android/speech/tts/ITtsCallback.aidl
rename to core/java/android/speech/tts/ITextToSpeechCallback.aidl
index c9898eb..40902ae 100755
--- a/core/java/android/speech/tts/ITtsCallback.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2011 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,15 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package android.speech.tts;
 
 /**
- * AIDL for the callback from the TTS Service
- * ITtsCallback.java is autogenerated from this.
+ * Interface for callbacks from TextToSpeechService
  *
  * {@hide}
  */
-oneway interface ITtsCallback {
+oneway interface ITextToSpeechCallback {
     void utteranceCompleted(String utteranceId);
 }
diff --git a/core/java/android/speech/tts/ITextToSpeechService.aidl b/core/java/android/speech/tts/ITextToSpeechService.aidl
new file mode 100644
index 0000000..ff3fa11
--- /dev/null
+++ b/core/java/android/speech/tts/ITextToSpeechService.aidl
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2011 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.speech.tts;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.speech.tts.ITextToSpeechCallback;
+
+/**
+ * Interface for TextToSpeech to talk to TextToSpeechService.
+ *
+ * {@hide}
+ */
+interface ITextToSpeechService {
+
+    /**
+     * Tells the engine to synthesize some speech and play it back.
+     *
+     * @param callingApp The package name of the calling app. Used to connect requests
+     *         callbacks and to clear requests when the calling app is stopping.
+     * @param text The text to synthesize.
+     * @param queueMode Determines what to do to requests already in the queue.
+     * @param param Request parameters.
+     */
+    int speak(in String callingApp, in String text, in int queueMode, in Bundle params);
+
+    /**
+     * Tells the engine to synthesize some speech and write it to a file.
+     *
+     * @param callingApp The package name of the calling app. Used to connect requests
+     *         callbacks and to clear requests when the calling app is stopping.
+     * @param text The text to synthesize.
+     * @param filename The file to write the synthesized audio to.
+     * @param param Request parameters.
+     */
+    int synthesizeToFile(in String callingApp, in String text,
+        in String filename, in Bundle params);
+
+    /**
+     * Plays an existing audio resource.
+     *
+     * @param callingApp The package name of the calling app. Used to connect requests
+     *         callbacks and to clear requests when the calling app is stopping.
+     * @param audioUri URI for the audio resource (a file or android.resource URI)
+     * @param queueMode Determines what to do to requests already in the queue.
+     * @param param Request parameters.
+     */
+    int playAudio(in String callingApp, in Uri audioUri, in int queueMode, in Bundle params);
+
+    /**
+     * Plays silence.
+     *
+     * @param callingApp The package name of the calling app. Used to connect requests
+     *         callbacks and to clear requests when the calling app is stopping.
+     * @param duration Number of milliseconds of silence to play.
+     * @param queueMode Determines what to do to requests already in the queue.
+     * @param param Request parameters.
+     */
+    int playSilence(in String callingApp, in long duration, in int queueMode, in Bundle params);
+
+    /**
+     * Checks whether the service is currently playing some audio.
+     */
+    boolean isSpeaking();
+
+    /**
+     * Interrupts the current utterance (if from the given app) and removes any utterances
+     * in the queue that are from the given app.
+     *
+     * @param callingApp Package name of the app whose utterances
+     *        should be interrupted and cleared.
+     */
+    int stop(in String callingApp);
+
+    /**
+     * Returns the language, country and variant currently being used by the TTS engine.
+     *
+     * Can be called from multiple threads.
+     *
+     * @return A 3-element array, containing language (ISO 3-letter code),
+     *         country (ISO 3-letter code) and variant used by the engine.
+     *         The country and variant may be {@code ""}. If country is empty, then variant must
+     *         be empty too.
+     */
+    String[] getLanguage();
+
+    /**
+     * Checks whether the engine supports a given language.
+     *
+     * @param lang ISO-3 language code.
+     * @param country ISO-3 country code. May be empty or null.
+     * @param variant Language variant. May be empty or null.
+     * @return Code indicating the support status for the locale.
+     *         One of {@link TextToSpeech#LANG_AVAILABLE},
+     *         {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
+     *         {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
+     *         {@link TextToSpeech#LANG_MISSING_DATA}
+     *         {@link TextToSpeech#LANG_NOT_SUPPORTED}.
+     */
+    int isLanguageAvailable(in String lang, in String country, in String variant);
+
+    /**
+     * Notifies the engine that it should load a speech synthesis language.
+     *
+     * @param lang ISO-3 language code.
+     * @param country ISO-3 country code. May be empty or null.
+     * @param variant Language variant. May be empty or null.
+     * @return Code indicating the support status for the locale.
+     *         One of {@link TextToSpeech#LANG_AVAILABLE},
+     *         {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
+     *         {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
+     *         {@link TextToSpeech#LANG_MISSING_DATA}
+     *         {@link TextToSpeech#LANG_NOT_SUPPORTED}.
+     */
+    int loadLanguage(in String lang, in String country, in String variant);
+
+    /**
+     * Sets the callback that will be notified when playback of utterance from the
+     * given app are completed.
+     *
+     * @param callingApp Package name for the app whose utterance the callback will handle.
+     * @param cb The callback.
+     */
+    void setCallback(in String callingApp, ITextToSpeechCallback cb);
+
+}
diff --git a/core/java/android/speech/tts/ITts.aidl b/core/java/android/speech/tts/ITts.aidl
deleted file mode 100755
index c1051c4..0000000
--- a/core/java/android/speech/tts/ITts.aidl
+++ /dev/null
@@ -1,69 +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 android.speech.tts;

-

-import android.speech.tts.ITtsCallback;

-

-import android.content.Intent;

-

-/**

- * AIDL for the TTS Service

- * ITts.java is autogenerated from this.

- *

- * {@hide}

- */

-interface ITts {

-    int setSpeechRate(in String callingApp, in int speechRate);

-

-    int setPitch(in String callingApp, in int pitch);

-

-    int speak(in String callingApp, in String text, in int queueMode, in String[] params);

-

-    boolean isSpeaking();

-

-    int stop(in String callingApp);

-

-    void addSpeech(in String callingApp, in String text, in String packageName, in int resId);

-

-    void addSpeechFile(in String callingApp, in String text, in String filename);

-

-    String[] getLanguage();

-

-    int isLanguageAvailable(in String language, in String country, in String variant, in String[] params);

-

-    int setLanguage(in String callingApp, in String language, in String country, in String variant);

-

-    boolean synthesizeToFile(in String callingApp, in String text, in String[] params, in String outputDirectory);

-

-    int playEarcon(in String callingApp, in String earcon, in int queueMode, in String[] params);

-

-    void addEarcon(in String callingApp, in String earcon, in String packageName, in int resId);

-

-    void addEarconFile(in String callingApp, in String earcon, in String filename);

-

-    int registerCallback(in String callingApp, ITtsCallback cb);

-

-    int unregisterCallback(in String callingApp, ITtsCallback cb);

-

-    int playSilence(in String callingApp, in long duration, in int queueMode, in String[] params);
-

-    int setEngineByPackageName(in String enginePackageName);
-
-    String getDefaultEngine();
-
-    boolean areDefaultsEnforced();

-}

diff --git a/core/java/android/speech/tts/PlaybackSynthesisRequest.java b/core/java/android/speech/tts/PlaybackSynthesisRequest.java
new file mode 100644
index 0000000..2267015
--- /dev/null
+++ b/core/java/android/speech/tts/PlaybackSynthesisRequest.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2011 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.speech.tts;
+
+import android.media.AudioFormat;
+import android.media.AudioTrack;
+import android.util.Log;
+
+/**
+ * Speech synthesis request that plays the audio as it is received.
+ */
+class PlaybackSynthesisRequest extends SynthesisRequest {
+
+    private static final String TAG = "PlaybackSynthesisRequest";
+    private static final boolean DBG = false;
+
+    private static final int MIN_AUDIO_BUFFER_SIZE = 8192;
+
+    /**
+     * Audio stream type. Must be one of the STREAM_ contants defined in
+     * {@link android.media.AudioManager}.
+     */
+    private final int mStreamType;
+
+    /**
+     * Volume, in the range [0.0f, 1.0f]. The default value is
+     * {@link TextToSpeech.Engine#DEFAULT_VOLUME} (1.0f).
+     */
+    private final float mVolume;
+
+    /**
+     * Left/right position of the audio, in the range [-1.0f, 1.0f].
+     * The default value is {@link TextToSpeech.Engine#DEFAULT_PAN} (0.0f).
+     */
+    private final float mPan;
+
+    private final Object mStateLock = new Object();
+    private AudioTrack mAudioTrack = null;
+    private boolean mStopped = false;
+
+    PlaybackSynthesisRequest(String text, int streamType, float volume, float pan) {
+        super(text);
+        mStreamType = streamType;
+        mVolume = volume;
+        mPan = pan;
+    }
+
+    @Override
+    void stop() {
+        if (DBG) Log.d(TAG, "stop()");
+        synchronized (mStateLock) {
+            mStopped = true;
+            cleanUp();
+        }
+    }
+
+    private void cleanUp() {
+        if (DBG) Log.d(TAG, "cleanUp()");
+        if (mAudioTrack != null) {
+            mAudioTrack.flush();
+            mAudioTrack.stop();
+            // TODO: do we need to wait for playback to finish before releasing?
+            mAudioTrack.release();
+            mAudioTrack = null;
+        }
+    }
+
+    @Override
+    public int getMaxBufferSize() {
+        // The AudioTrack buffer will be at least MIN_AUDIO_BUFFER_SIZE, so that should always be
+        // a safe buffer size to pass in.
+        return MIN_AUDIO_BUFFER_SIZE;
+    }
+
+    // TODO: add a thread that writes to the AudioTrack?
+    @Override
+    public int start(int sampleRateInHz, int audioFormat, int channelCount) {
+        if (DBG) {
+            Log.d(TAG, "start(" + sampleRateInHz + "," + audioFormat
+                    + "," + channelCount + ")");
+        }
+
+        synchronized (mStateLock) {
+            if (mStopped) {
+                if (DBG) Log.d(TAG, "Request has been aborted.");
+                return TextToSpeech.ERROR;
+            }
+            if (mAudioTrack != null) {
+                Log.e(TAG, "start() called twice");
+                cleanUp();
+                return TextToSpeech.ERROR;
+            }
+
+            mAudioTrack = createAudioTrack(sampleRateInHz, audioFormat, channelCount,
+                    AudioTrack.MODE_STREAM);
+            if (mAudioTrack == null) {
+                return TextToSpeech.ERROR;
+            }
+        }
+
+        return TextToSpeech.SUCCESS;
+    }
+
+    private void setupVolume(AudioTrack audioTrack, float volume, float pan) {
+        float vol = clip(volume, 0.0f, 1.0f);
+        float panning = clip(pan, -1.0f, 1.0f);
+        float volLeft = vol;
+        float volRight = vol;
+        if (panning > 0.0f) {
+            volLeft *= (1.0f - panning);
+        } else if (panning < 0.0f) {
+            volRight *= (1.0f + panning);
+        }
+        if (DBG) Log.d(TAG, "volLeft=" + volLeft + ",volRight=" + volRight);
+        if (audioTrack.setStereoVolume(volLeft, volRight) != AudioTrack.SUCCESS) {
+            Log.e(TAG, "Failed to set volume");
+        }
+    }
+
+    private float clip(float value, float min, float max) {
+        return value > max ? max : (value < min ? min : value);
+    }
+
+    @Override
+    public int audioAvailable(byte[] buffer, int offset, int length) {
+        if (DBG) {
+            Log.d(TAG, "audioAvailable(byte[" + buffer.length + "],"
+                    + offset + "," + length + ")");
+        }
+        if (length > getMaxBufferSize()) {
+            throw new IllegalArgumentException("buffer is too large (" + length + " bytes)");
+        }
+        synchronized (mStateLock) {
+            if (mStopped) {
+                if (DBG) Log.d(TAG, "Request has been aborted.");
+                return TextToSpeech.ERROR;
+            }
+            if (mAudioTrack == null) {
+                Log.e(TAG, "audioAvailable(): Not started");
+                return TextToSpeech.ERROR;
+            }
+            int playState = mAudioTrack.getPlayState();
+            if (playState == AudioTrack.PLAYSTATE_STOPPED) {
+                if (DBG) Log.d(TAG, "AudioTrack stopped, restarting");
+                mAudioTrack.play();
+            }
+            // TODO: loop until all data is written?
+            if (DBG) Log.d(TAG, "AudioTrack.write()");
+            int count = mAudioTrack.write(buffer, offset, length);
+            if (DBG) Log.d(TAG, "AudioTrack.write() returned " + count);
+            if (count < 0) {
+                Log.e(TAG, "Writing to AudioTrack failed: " + count);
+                cleanUp();
+                return TextToSpeech.ERROR;
+            } else {
+                return TextToSpeech.SUCCESS;
+            }
+        }
+    }
+
+    @Override
+    public int done() {
+        if (DBG) Log.d(TAG, "done()");
+        synchronized (mStateLock) {
+            if (mStopped) {
+                if (DBG) Log.d(TAG, "Request has been aborted.");
+                return TextToSpeech.ERROR;
+            }
+            if (mAudioTrack == null) {
+                Log.e(TAG, "done(): Not started");
+                return TextToSpeech.ERROR;
+            }
+            cleanUp();
+        }
+        return TextToSpeech.SUCCESS;
+    }
+
+    @Override
+    public int completeAudioAvailable(int sampleRateInHz, int audioFormat, int channelCount,
+            byte[] buffer, int offset, int length) {
+        if (DBG) {
+            Log.d(TAG, "completeAudioAvailable(" + sampleRateInHz + "," + audioFormat
+                    + "," + channelCount + "byte[" + buffer.length + "],"
+                    + offset + "," + length + ")");
+        }
+
+        synchronized (mStateLock) {
+            if (mStopped) {
+                if (DBG) Log.d(TAG, "Request has been aborted.");
+                return TextToSpeech.ERROR;
+            }
+            if (mAudioTrack != null) {
+                Log.e(TAG, "start() called before completeAudioAvailable()");
+                cleanUp();
+                return TextToSpeech.ERROR;
+            }
+
+            mAudioTrack = createAudioTrack(sampleRateInHz, audioFormat, channelCount,
+                    AudioTrack.MODE_STATIC);
+            if (mAudioTrack == null) {
+                return TextToSpeech.ERROR;
+            }
+
+            try {
+                mAudioTrack.write(buffer, offset, length);
+                mAudioTrack.play();
+            } catch (IllegalStateException ex) {
+                Log.e(TAG, "Playback error", ex);
+                return TextToSpeech.ERROR;
+            } finally {
+                cleanUp();
+            }
+        }
+
+        return TextToSpeech.SUCCESS;
+    }
+
+    private AudioTrack createAudioTrack(int sampleRateInHz, int audioFormat, int channelCount,
+            int mode) {
+        int channelConfig;
+        if (channelCount == 1) {
+            channelConfig = AudioFormat.CHANNEL_OUT_MONO;
+        } else if (channelCount == 2){
+            channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
+        } else {
+            Log.e(TAG, "Unsupported number of channels: " + channelCount);
+            return null;
+        }
+
+        int minBufferSizeInBytes
+                = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
+        int bufferSizeInBytes = Math.max(MIN_AUDIO_BUFFER_SIZE, minBufferSizeInBytes);
+        AudioTrack audioTrack = new AudioTrack(mStreamType, sampleRateInHz, channelConfig,
+                audioFormat, bufferSizeInBytes, mode);
+        if (audioTrack == null) {
+            return null;
+        }
+        if (audioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
+            audioTrack.release();
+            return null;
+        }
+        setupVolume(audioTrack, mVolume, mPan);
+        return audioTrack;
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/speech/tts/SynthesisRequest.java b/core/java/android/speech/tts/SynthesisRequest.java
new file mode 100644
index 0000000..f4bb852
--- /dev/null
+++ b/core/java/android/speech/tts/SynthesisRequest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2011 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.speech.tts;
+
+/**
+ * A request for speech synthesis given to a TTS engine for processing.
+ *
+ * The engine can provide streaming audio by calling
+ * {@link #start}, then {@link #audioAvailable} until all audio has been provided, then finally
+ * {@link #done}.
+ *
+ * Alternatively, the engine can provide all the audio at once, by using
+ * {@link #completeAudioAvailable}.
+ *
+ * @hide Pending approval
+ */
+public abstract class SynthesisRequest {
+
+    private final String mText;
+    private String mLanguage;
+    private String mCountry;
+    private String mVariant;
+    private int mSpeechRate;
+    private int mPitch;
+
+    public SynthesisRequest(String text) {
+        mText = text;
+    }
+
+    /**
+     * Sets the locale for the request.
+     */
+    void setLanguage(String language, String country, String variant) {
+        mLanguage = language;
+        mCountry = country;
+        mVariant = variant;
+    }
+
+    /**
+     * Sets the speech rate.
+     */
+    void setSpeechRate(int speechRate) {
+        mSpeechRate = speechRate;
+    }
+
+    /**
+     * Sets the pitch.
+     */
+    void setPitch(int pitch) {
+        mPitch = pitch;
+    }
+
+    /**
+     * Gets the text which should be synthesized.
+     */
+    public String getText() {
+        return mText;
+    }
+
+    /**
+     * Gets the ISO 3-letter language code for the language to use.
+     */
+    public String getLanguage() {
+        return mLanguage;
+    }
+
+    /**
+     * Gets the ISO 3-letter country code for the language to use.
+     */
+    public String getCountry() {
+        return mCountry;
+    }
+
+    /**
+     * Gets the language variant to use.
+     */
+    public String getVariant() {
+        return mVariant;
+    }
+
+    /**
+     * Gets the speech rate to use. {@link TextToSpeech.Engine#DEFAULT_RATE} (100)
+     * is the normal rate.
+     */
+    public int getSpeechRate() {
+        return mSpeechRate;
+    }
+
+    /**
+     * Gets the pitch to use. {@link TextToSpeech.Engine#DEFAULT_PITCH} (100)
+     * is the normal pitch.
+     */
+    public int getPitch() {
+        return mPitch;
+    }
+
+    /**
+     * Gets the maximum number of bytes that the TTS engine can pass in a single call of
+     * {@link #audioAvailable}. This does not apply to {@link #completeAudioAvailable}.
+     */
+    public abstract int getMaxBufferSize();
+
+    /**
+     * Aborts the speech request.
+     *
+     * Can be called from multiple threads.
+     */
+    abstract void stop();
+
+    /**
+     * The service should call this when it starts to synthesize audio for this
+     * request.
+     *
+     * This method should only be called on the synthesis thread,
+     * while in {@link TextToSpeechService#onSynthesizeText}.
+     *
+     * @param sampleRateInHz Sample rate in HZ of the generated audio.
+     * @param audioFormat Audio format of the generated audio. Must be one of
+     *         the ENCODING_ constants defined in {@link android.media.AudioFormat}.
+     * @param channelCount The number of channels. Must be {@code 1} or {@code 2}.
+     * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+     */
+    public abstract int start(int sampleRateInHz, int audioFormat, int channelCount);
+
+    /**
+     * The service should call this method when synthesized audio is ready for consumption.
+     *
+     * This method should only be called on the synthesis thread,
+     * while in {@link TextToSpeechService#onSynthesizeText}.
+     *
+     * @param buffer The generated audio data. This method will not hold on to {@code buffer},
+     *         so the caller is free to modify it after this method returns.
+     * @param offset The offset into {@code buffer} where the audio data starts.
+     * @param length The number of bytes of audio data in {@code buffer}. This must be
+     *         less than or equal to the return value of {@link #getMaxBufferSize}.
+     * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+     */
+    public abstract int audioAvailable(byte[] buffer, int offset, int length);
+
+    /**
+     * The service should call this method when all the synthesized audio for a request has
+     * been passed to {@link #audioAvailable}.
+     *
+     * This method should only be called on the synthesis thread,
+     * while in {@link TextToSpeechService#onSynthesizeText}.
+     *
+     * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+     */
+    public abstract int done();
+
+    /**
+     * The service can call this method instead of using {@link #start}, {@link #audioAvailable}
+     * and {@link #done} if all the audio data is available in a single buffer.
+     *
+     * @param sampleRateInHz Sample rate in HZ of the generated audio.
+     * @param audioFormat Audio format of the generated audio. Must be one of
+     *         the ENCODING_ constants defined in {@link android.media.AudioFormat}.
+     * @param channelCount The number of channels. Must be {@code 1} or {@code 2}.
+     * @param buffer The generated audio data. This method will not hold on to {@code buffer},
+     *         so the caller is free to modify it after this method returns.
+     * @param offset The offset into {@code buffer} where the audio data starts.
+     * @param length The number of bytes of audio data in {@code buffer}.
+     * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+     */
+    public abstract int completeAudioAvailable(int sampleRateInHz, int audioFormat,
+            int channelCount, byte[] buffer, int offset, int length);
+}
\ No newline at end of file
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 95830ec..2add7b5 100755
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 Google Inc.
+ * 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
@@ -15,22 +15,31 @@
  */
 package android.speech.tts;
 
-import android.speech.tts.ITts;
-import android.speech.tts.ITtsCallback;
-
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
 import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.Log;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 
 /**
  *
@@ -49,11 +58,11 @@
     /**
      * Denotes a successful operation.
      */
-    public static final int SUCCESS                = 0;
+    public static final int SUCCESS = 0;
     /**
      * Denotes a generic operation failure.
      */
-    public static final int ERROR                  = -1;
+    public static final int ERROR = -1;
 
     /**
      * Queue mode where all entries in the playback queue (media to be played
@@ -65,20 +74,17 @@
      */
     public static final int QUEUE_ADD = 1;
 
-
     /**
      * Denotes the language is available exactly as specified by the locale.
      */
     public static final int LANG_COUNTRY_VAR_AVAILABLE = 2;
 
-
     /**
      * Denotes the language is available for the language and country specified 
      * by the locale, but not the variant.
      */
     public static final int LANG_COUNTRY_AVAILABLE = 1;
 
-
     /**
      * Denotes the language is available for the language by the locale, 
      * but not the country and variant.
@@ -95,7 +101,6 @@
      */
     public static final int LANG_NOT_SUPPORTED = -2;
 
-
     /**
      * Broadcast Action: The TextToSpeech synthesizer has completed processing
      * of all the text in the speech queue.
@@ -104,7 +109,6 @@
     public static final String ACTION_TTS_QUEUE_PROCESSING_COMPLETED =
             "android.speech.tts.TTS_QUEUE_PROCESSING_COMPLETED";
 
-
     /**
      * Interface definition of a callback to be invoked indicating the completion of the
      * TextToSpeech engine initialization.
@@ -112,103 +116,117 @@
     public interface OnInitListener {
         /**
          * Called to signal the completion of the TextToSpeech engine initialization.
+         *
          * @param status {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
          */
         public void onInit(int status);
     }
 
     /**
-     * Interface definition of a callback to be invoked indicating the TextToSpeech engine has
-     * completed synthesizing an utterance with an utterance ID set.
-     *
+     * Listener that will be called when the TTS service has
+     * completed synthesizing an utterance. This is only called if the utterance
+     * has an utterance ID (see {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}).
      */
     public interface OnUtteranceCompletedListener {
         /**
-         * Called to signal the completion of the synthesis of the utterance that was identified
-         * with the string parameter. This identifier is the one originally passed in the
-         * parameter hashmap of the synthesis request in
-         * {@link TextToSpeech#speak(String, int, HashMap)} or
-         * {@link TextToSpeech#synthesizeToFile(String, HashMap, String)} with the
-         * {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID} key.
+         * Called when an utterance has been synthesized.
+         *
          * @param utteranceId the identifier of the utterance.
          */
         public void onUtteranceCompleted(String utteranceId);
     }
 
-
     /**
-     * Internal constants for the TextToSpeech functionality
-     *
+     * Constants and parameter names for controlling text-to-speech.
      */
     public class Engine {
-        // default values for a TTS engine when settings are not found in the provider
+
         /**
-         * {@hide}
+         * Default speech rate.
+         * @hide
          */
-        public static final int DEFAULT_RATE = 100; // 1x
+        public static final int DEFAULT_RATE = 100;
+
         /**
-         * {@hide}
+         * Default pitch.
+         * @hide
          */
-        public static final int DEFAULT_PITCH = 100;// 1x
+        public static final int DEFAULT_PITCH = 100;
+
         /**
-         * {@hide}
+         * Default volume.
+         * @hide
          */
         public static final float DEFAULT_VOLUME = 1.0f;
+
         /**
-         * {@hide}
-         */
-        protected static final String DEFAULT_VOLUME_STRING = "1.0";
-        /**
-         * {@hide}
+         * Default pan (centered).
+         * @hide
          */
         public static final float DEFAULT_PAN = 0.0f;
-        /**
-         * {@hide}
-         */
-        protected static final String DEFAULT_PAN_STRING = "0.0";
 
         /**
-         * {@hide}
+         * Default value for {@link Settings.Secure#TTS_USE_DEFAULTS}.
+         * @hide
          */
         public static final int USE_DEFAULTS = 0; // false
-        /**
-         * {@hide}
-         */
-        public static final String DEFAULT_SYNTH = "com.svox.pico";
 
-        // default values for rendering
+        /**
+         * Package name of the default TTS engine.
+         *
+         * TODO: This should come from a system property
+         *
+         * @hide
+         */
+        public static final String DEFAULT_ENGINE = "com.svox.pico";
+
         /**
          * Default audio stream used when playing synthesized speech.
          */
         public static final int DEFAULT_STREAM = AudioManager.STREAM_MUSIC;
 
-        // return codes for a TTS engine's check data activity
         /**
          * Indicates success when checking the installation status of the resources used by the
          * TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
          */
         public static final int CHECK_VOICE_DATA_PASS = 1;
+
         /**
          * Indicates failure when checking the installation status of the resources used by the
          * TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
          */
         public static final int CHECK_VOICE_DATA_FAIL = 0;
+
         /**
          * Indicates erroneous data when checking the installation status of the resources used by
          * the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
          */
         public static final int CHECK_VOICE_DATA_BAD_DATA = -1;
+
         /**
          * Indicates missing resources when checking the installation status of the resources used
          * by the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
          */
         public static final int CHECK_VOICE_DATA_MISSING_DATA = -2;
+
         /**
          * Indicates missing storage volume when checking the installation status of the resources
          * used by the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
          */
         public static final int CHECK_VOICE_DATA_MISSING_VOLUME = -3;
 
+        /**
+         * Intent for starting a TTS service. Services that handle this intent must
+         * extend {@link TextToSpeechService}. Normal applications should not use this intent
+         * directly, instead they should talk to the TTS service using the the methods in this
+         * class.
+         *
+         * @hide Pending API council approval
+         */
+        @SdkConstant(SdkConstantType.SERVICE_ACTION)
+        public static final String INTENT_ACTION_TTS_SERVICE =
+                "android.intent.action.TTS_SERVICE";
+
         // intents to ask engine to install data or check its data
         /**
          * Activity Action: Triggers the platform TextToSpeech engine to
@@ -231,6 +249,7 @@
         @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
         public static final String ACTION_TTS_DATA_INSTALLED =
                 "android.speech.tts.engine.TTS_DATA_INSTALLED";
+
         /**
          * Activity Action: Starts the activity from the platform TextToSpeech
          * engine to verify the proper installation and availability of the
@@ -258,23 +277,36 @@
         public static final String ACTION_CHECK_TTS_DATA =
                 "android.speech.tts.engine.CHECK_TTS_DATA";
 
+        /**
+         * Activity intent for getting some sample text to use for demonstrating TTS.
+         *
+         * @hide This intent was used by engines written against the old API.
+         * Not sure if it should be exposed.
+         */
+        @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+        public static final String ACTION_GET_SAMPLE_TEXT =
+                "android.speech.tts.engine.GET_SAMPLE_TEXT";
+
         // extras for a TTS engine's check data activity
         /**
          * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
          * the TextToSpeech engine specifies the path to its resources.
          */
         public static final String EXTRA_VOICE_DATA_ROOT_DIRECTORY = "dataRoot";
+
         /**
          * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
          * the TextToSpeech engine specifies the file names of its resources under the
          * resource path.
          */
         public static final String EXTRA_VOICE_DATA_FILES = "dataFiles";
+
         /**
          * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
          * the TextToSpeech engine specifies the locale associated with each resource file.
          */
         public static final String EXTRA_VOICE_DATA_FILES_INFO = "dataFilesInfo";
+
         /**
          * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
          * the TextToSpeech engine returns an ArrayList<String> of all the available voices.
@@ -282,6 +314,7 @@
          * optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
          */
         public static final String EXTRA_AVAILABLE_VOICES = "availableVoices";
+
         /**
          * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
          * the TextToSpeech engine returns an ArrayList<String> of all the unavailable voices.
@@ -289,6 +322,7 @@
          * optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
          */
         public static final String EXTRA_UNAVAILABLE_VOICES = "unavailableVoices";
+
         /**
          * Extra information sent with the {@link #ACTION_CHECK_TTS_DATA} intent where the
          * caller indicates to the TextToSpeech engine which specific sets of voice data to
@@ -311,134 +345,87 @@
         // keys for the parameters passed with speak commands. Hidden keys are used internally
         // to maintain engine state for each TextToSpeech instance.
         /**
-         * {@hide}
+         * @hide
          */
         public static final String KEY_PARAM_RATE = "rate";
+
         /**
-         * {@hide}
+         * @hide
          */
         public static final String KEY_PARAM_LANGUAGE = "language";
+
         /**
-         * {@hide}
+         * @hide
          */
         public static final String KEY_PARAM_COUNTRY = "country";
+
         /**
-         * {@hide}
+         * @hide
          */
         public static final String KEY_PARAM_VARIANT = "variant";
+
         /**
-         * {@hide}
+         * @hide
          */
         public static final String KEY_PARAM_ENGINE = "engine";
+
         /**
-         * {@hide}
+         * @hide
          */
         public static final String KEY_PARAM_PITCH = "pitch";
+
         /**
          * Parameter key to specify the audio stream type to be used when speaking text
-         * or playing back a file.
+         * or playing back a file. The value should be one of the STREAM_ constants
+         * defined in {@link AudioManager}.
+         *
          * @see TextToSpeech#speak(String, int, HashMap)
          * @see TextToSpeech#playEarcon(String, int, HashMap)
          */
         public static final String KEY_PARAM_STREAM = "streamType";
+
         /**
          * Parameter key to identify an utterance in the
          * {@link TextToSpeech.OnUtteranceCompletedListener} after text has been
          * spoken, a file has been played back or a silence duration has elapsed.
+         *
          * @see TextToSpeech#speak(String, int, HashMap)
          * @see TextToSpeech#playEarcon(String, int, HashMap)
          * @see TextToSpeech#synthesizeToFile(String, HashMap, String)
          */
         public static final String KEY_PARAM_UTTERANCE_ID = "utteranceId";
+
         /**
          * Parameter key to specify the speech volume relative to the current stream type
          * volume used when speaking text. Volume is specified as a float ranging from 0 to 1
          * where 0 is silence, and 1 is the maximum volume (the default behavior).
+         *
          * @see TextToSpeech#speak(String, int, HashMap)
          * @see TextToSpeech#playEarcon(String, int, HashMap)
          */
         public static final String KEY_PARAM_VOLUME = "volume";
+
         /**
          * Parameter key to specify how the speech is panned from left to right when speaking text.
          * Pan is specified as a float ranging from -1 to +1 where -1 maps to a hard-left pan,
          * 0 to center (the default behavior), and +1 to hard-right.
+         *
          * @see TextToSpeech#speak(String, int, HashMap)
          * @see TextToSpeech#playEarcon(String, int, HashMap)
          */
         public static final String KEY_PARAM_PAN = "pan";
 
-        // key positions in the array of cached parameters
-        /**
-         * {@hide}
-         */
-        protected static final int PARAM_POSITION_RATE = 0;
-        /**
-         * {@hide}
-         */
-        protected static final int PARAM_POSITION_LANGUAGE = 2;
-        /**
-         * {@hide}
-         */
-        protected static final int PARAM_POSITION_COUNTRY = 4;
-        /**
-         * {@hide}
-         */
-        protected static final int PARAM_POSITION_VARIANT = 6;
-        /**
-         * {@hide}
-         */
-        protected static final int PARAM_POSITION_STREAM = 8;
-        /**
-         * {@hide}
-         */
-        protected static final int PARAM_POSITION_UTTERANCE_ID = 10;
-
-        /**
-         * {@hide}
-         */
-        protected static final int PARAM_POSITION_ENGINE = 12;
-
-        /**
-         * {@hide}
-         */
-        protected static final int PARAM_POSITION_PITCH = 14;
-
-        /**
-         * {@hide}
-         */
-        protected static final int PARAM_POSITION_VOLUME = 16;
-
-        /**
-         * {@hide}
-         */
-        protected static final int PARAM_POSITION_PAN = 18;
-
-
-        /**
-         * {@hide}
-         * Total number of cached speech parameters.
-         * This number should be equal to (max param position/2) + 1.
-         */
-        protected static final int NB_CACHED_PARAMS = 10;
     }
 
-    /**
-     * Connection needed for the TTS.
-     */
-    private ServiceConnection mServiceConnection;
-
-    private ITts mITts = null;
-    private ITtsCallback mITtscallback = null;
-    private Context mContext = null;
-    private String mPackageName = "";
-    private OnInitListener mInitListener = null;
-    private boolean mStarted = false;
+    private final Context mContext;
+    private Connection mServiceConnection;
+    private OnInitListener mInitListener;
     private final Object mStartLock = new Object();
-    /**
-     * Used to store the cached parameters sent along with each synthesis request to the
-     * TTS service.
-     */
-    private String[] mCachedParams;
+
+    private String mRequestedEngine;
+    private final Map<String, Uri> mEarcons;
+    private final Map<String, Uri> mUtterances;
+    private final Bundle mParams = new Bundle();
 
     /**
      * The constructor for the TextToSpeech class.
@@ -451,84 +438,98 @@
      *            TextToSpeech engine has initialized.
      */
     public TextToSpeech(Context context, OnInitListener listener) {
+        this(context, listener, null);
+    }
+
+    /**
+     * @hide pending approval
+     */
+    public TextToSpeech(Context context, OnInitListener listener, String engine) {
         mContext = context;
-        mPackageName = mContext.getPackageName();
         mInitListener = listener;
+        mRequestedEngine = engine;
 
-        mCachedParams = new String[2*Engine.NB_CACHED_PARAMS]; // store key and value
-        mCachedParams[Engine.PARAM_POSITION_RATE] = Engine.KEY_PARAM_RATE;
-        mCachedParams[Engine.PARAM_POSITION_LANGUAGE] = Engine.KEY_PARAM_LANGUAGE;
-        mCachedParams[Engine.PARAM_POSITION_COUNTRY] = Engine.KEY_PARAM_COUNTRY;
-        mCachedParams[Engine.PARAM_POSITION_VARIANT] = Engine.KEY_PARAM_VARIANT;
-        mCachedParams[Engine.PARAM_POSITION_STREAM] = Engine.KEY_PARAM_STREAM;
-        mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID] = Engine.KEY_PARAM_UTTERANCE_ID;
-        mCachedParams[Engine.PARAM_POSITION_ENGINE] = Engine.KEY_PARAM_ENGINE;
-        mCachedParams[Engine.PARAM_POSITION_PITCH] = Engine.KEY_PARAM_PITCH;
-        mCachedParams[Engine.PARAM_POSITION_VOLUME] = Engine.KEY_PARAM_VOLUME;
-        mCachedParams[Engine.PARAM_POSITION_PAN] = Engine.KEY_PARAM_PAN;
-
-        // Leave all defaults that are shown in Settings uninitialized/at the default
-        // so that the values set in Settings will take effect if the application does
-        // not try to change these settings itself.
-        mCachedParams[Engine.PARAM_POSITION_RATE + 1] = "";
-        mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1] = "";
-        mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1] = "";
-        mCachedParams[Engine.PARAM_POSITION_VARIANT + 1] = "";
-        mCachedParams[Engine.PARAM_POSITION_STREAM + 1] =
-                String.valueOf(Engine.DEFAULT_STREAM);
-        mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID + 1] = "";
-        mCachedParams[Engine.PARAM_POSITION_ENGINE + 1] = "";
-        mCachedParams[Engine.PARAM_POSITION_PITCH + 1] = "100";
-        mCachedParams[Engine.PARAM_POSITION_VOLUME + 1] = Engine.DEFAULT_VOLUME_STRING;
-        mCachedParams[Engine.PARAM_POSITION_PAN + 1] = Engine.DEFAULT_PAN_STRING;
+        mEarcons = new HashMap<String, Uri>();
+        mUtterances = new HashMap<String, Uri>();
 
         initTts();
     }
 
-
-    private void initTts() {
-        mStarted = false;
-
-        // Initialize the TTS, run the callback after the binding is successful
-        mServiceConnection = new ServiceConnection() {
-            public void onServiceConnected(ComponentName name, IBinder service) {
-                synchronized(mStartLock) {
-                    mITts = ITts.Stub.asInterface(service);
-                    mStarted = true;
-                    // Cache the default engine and current language
-                    setEngineByPackageName(getDefaultEngine());
-                    setLanguage(getLanguage());
-                    if (mInitListener != null) {
-                        // TODO manage failures and missing resources
-                        mInitListener.onInit(SUCCESS);
-                    }
-                }
-            }
-
-            public void onServiceDisconnected(ComponentName name) {
-                synchronized(mStartLock) {
-                    mITts = null;
-                    mInitListener = null;
-                    mStarted = false;
-                }
-            }
-        };
-
-        Intent intent = new Intent("android.intent.action.START_TTS_SERVICE");
-        intent.addCategory("android.intent.category.TTS");
-        boolean bound = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
-        if (!bound) {
-            Log.e("TextToSpeech.java", "initTts() failed to bind to service");
-            if (mInitListener != null) {
-                mInitListener.onInit(ERROR);
-            }
-        } else {
-            // initialization listener will be called inside ServiceConnection
-            Log.i("TextToSpeech.java", "initTts() successfully bound to service");
-        }
-        // TODO handle plugin failures
+    private String getPackageName() {
+        return mContext.getPackageName();
     }
 
+    private <R> R runActionNoReconnect(Action<R> action, R errorResult, String method) {
+        return runAction(action, errorResult, method, false);
+    }
+
+    private <R> R runAction(Action<R> action, R errorResult, String method) {
+        return runAction(action, errorResult, method, true);
+    }
+
+    private <R> R runAction(Action<R> action, R errorResult, String method, boolean reconnect) {
+        synchronized (mStartLock) {
+            if (mServiceConnection == null) {
+                Log.w(TAG, method + " failed: not bound to TTS engine");
+                return errorResult;
+            }
+            return mServiceConnection.runAction(action, errorResult, method, reconnect);
+        }
+    }
+
+    private int initTts() {
+        String defaultEngine = getDefaultEngine();
+        String engine = defaultEngine;
+        if (!areDefaultsEnforced() && !TextUtils.isEmpty(mRequestedEngine)
+                && isEngineEnabled(engine)) {
+            engine = mRequestedEngine;
+        }
+
+        // Try requested engine
+        if (connectToEngine(engine)) {
+            return SUCCESS;
+        }
+
+        // Fall back to user's default engine if different from the already tested one
+        if (!engine.equals(defaultEngine)) {
+            if (connectToEngine(defaultEngine)) {
+                return SUCCESS;
+            }
+        }
+
+        // Fall back to the hardcoded default if different from the two above
+        if (!defaultEngine.equals(Engine.DEFAULT_ENGINE)
+                && !engine.equals(Engine.DEFAULT_ENGINE)) {
+            if (connectToEngine(Engine.DEFAULT_ENGINE)) {
+                return SUCCESS;
+            }
+        }
+
+        return ERROR;
+    }
+
+    private boolean connectToEngine(String engine) {
+        Connection connection = new Connection();
+        Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
+        intent.setPackage(engine);
+        boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
+        if (!bound) {
+            Log.e(TAG, "Failed to bind to " + engine);
+            dispatchOnInit(ERROR);
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    private void dispatchOnInit(int result) {
+        synchronized (mStartLock) {
+            if (mInitListener != null) {
+                mInitListener.onInit(result);
+                mInitListener = null;
+            }
+        }
+    }
 
     /**
      * Releases the resources used by the TextToSpeech engine.
@@ -536,15 +537,17 @@
      * so the TextToSpeech engine can be cleanly stopped.
      */
     public void shutdown() {
-        try {
-            mContext.unbindService(mServiceConnection);
-        } catch (IllegalArgumentException e) {
-            // Do nothing and fail silently since an error here indicates that
-            // binding never succeeded in the first place.
-        }
+        runActionNoReconnect(new Action<Void>() {
+            @Override
+            public Void run(ITextToSpeechService service) throws RemoteException {
+                service.setCallback(getPackageName(), null);
+                service.stop(getPackageName());
+                mServiceConnection.disconnect();
+                return null;
+            }
+        }, null, "shutdown");
     }
 
-
     /**
      * Adds a mapping between a string of text and a sound resource in a
      * package. After a call to this method, subsequent calls to
@@ -573,21 +576,9 @@
      * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
      */
     public int addSpeech(String text, String packagename, int resourceId) {
-        synchronized(mStartLock) {
-            if (!mStarted) {
-                return ERROR;
-            }
-            try {
-                mITts.addSpeech(mPackageName, text, packagename, resourceId);
-                return SUCCESS;
-            } catch (RemoteException e) {
-                restart("addSpeech", e);
-            } catch (NullPointerException e) {
-                restart("addSpeech", e);
-            } catch (IllegalStateException e) {
-                restart("addSpeech", e);
-            }
-            return ERROR;
+        synchronized (mStartLock) {
+            mUtterances.put(text, makeResourceUri(packagename, resourceId));
+            return SUCCESS;
         }
     }
 
@@ -608,20 +599,8 @@
      */
     public int addSpeech(String text, String filename) {
         synchronized (mStartLock) {
-            if (!mStarted) {
-                return ERROR;
-            }
-            try {
-                mITts.addSpeechFile(mPackageName, text, filename);
-                return SUCCESS;
-            } catch (RemoteException e) {
-                restart("addSpeech", e);
-            } catch (NullPointerException e) {
-                restart("addSpeech", e);
-            } catch (IllegalStateException e) {
-                restart("addSpeech", e);
-            }
-            return ERROR;
+            mUtterances.put(text, Uri.parse(filename));
+            return SUCCESS;
         }
     }
 
@@ -653,24 +632,11 @@
      */
     public int addEarcon(String earcon, String packagename, int resourceId) {
         synchronized(mStartLock) {
-            if (!mStarted) {
-                return ERROR;
-            }
-            try {
-                mITts.addEarcon(mPackageName, earcon, packagename, resourceId);
-                return SUCCESS;
-            } catch (RemoteException e) {
-                restart("addEarcon", e);
-            } catch (NullPointerException e) {
-                restart("addEarcon", e);
-            } catch (IllegalStateException e) {
-                restart("addEarcon", e);
-            }
-            return ERROR;
+            mEarcons.put(earcon, makeResourceUri(packagename, resourceId));
+            return SUCCESS;
         }
     }
 
-
     /**
      * Adds a mapping between a string of text and a sound file.
      * Use this to add custom earcons.
@@ -687,312 +653,199 @@
      * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
      */
     public int addEarcon(String earcon, String filename) {
-        synchronized (mStartLock) {
-            if (!mStarted) {
-                return ERROR;
-            }
-            try {
-                mITts.addEarconFile(mPackageName, earcon, filename);
-                return SUCCESS;
-            } catch (RemoteException e) {
-                restart("addEarcon", e);
-            } catch (NullPointerException e) {
-                restart("addEarcon", e);
-            } catch (IllegalStateException e) {
-                restart("addEarcon", e);
-            }
-            return ERROR;
+        synchronized(mStartLock) {
+            mEarcons.put(earcon, Uri.parse(filename));
+            return SUCCESS;
         }
     }
 
+    private Uri makeResourceUri(String packageName, int resourceId) {
+        return new Uri.Builder()
+                .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+                .encodedAuthority(packageName)
+                .appendEncodedPath(String.valueOf(resourceId))
+                .build();
+    }
 
     /**
      * Speaks the string using the specified queuing strategy and speech
      * parameters.
      *
-     * @param text
-     *            The string of text to be spoken.
-     * @param queueMode
-     *            The queuing strategy to use.
-     *            {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
-     * @param params
-     *            The list of parameters to be used. Can be null if no parameters are given.
-     *            They are specified using a (key, value) pair, where the key can be
-     *            {@link Engine#KEY_PARAM_STREAM} or
-     *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
+     * @param text The string of text to be spoken.
+     * @param queueMode The queuing strategy to use, {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
+     * @param params Parameters for the request. Can be null.
+     *            Supported parameter names:
+     *            {@link Engine#KEY_PARAM_STREAM},
+     *            {@link Engine#KEY_PARAM_UTTERANCE_ID},
+     *            {@link Engine#KEY_PARAM_VOLUME},
+     *            {@link Engine#KEY_PARAM_PAN}.
      *
-     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+     * @return {@link #ERROR} or {@link #SUCCESS}.
      */
-    public int speak(String text, int queueMode, HashMap<String,String> params)
-    {
-        synchronized (mStartLock) {
-            int result = ERROR;
-            Log.i("TextToSpeech.java - speak", "speak text of length " + text.length());
-            if (!mStarted) {
-                Log.e("TextToSpeech.java - speak", "service isn't started");
-                return result;
-            }
-            try {
-                if ((params != null) && (!params.isEmpty())) {
-                    setCachedParam(params, Engine.KEY_PARAM_STREAM, Engine.PARAM_POSITION_STREAM);
-                    setCachedParam(params, Engine.KEY_PARAM_UTTERANCE_ID,
-                            Engine.PARAM_POSITION_UTTERANCE_ID);
-                    setCachedParam(params, Engine.KEY_PARAM_ENGINE, Engine.PARAM_POSITION_ENGINE);
-                    setCachedParam(params, Engine.KEY_PARAM_VOLUME, Engine.PARAM_POSITION_VOLUME);
-                    setCachedParam(params, Engine.KEY_PARAM_PAN, Engine.PARAM_POSITION_PAN);
+    public int speak(final String text, final int queueMode, final HashMap<String, String> params) {
+        return runAction(new Action<Integer>() {
+            @Override
+            public Integer run(ITextToSpeechService service) throws RemoteException {
+                Uri utteranceUri = mUtterances.get(text);
+                if (utteranceUri != null) {
+                    return service.playAudio(getPackageName(), utteranceUri, queueMode,
+                            getParams(params));
+                } else {
+                    return service.speak(getPackageName(), text, queueMode, getParams(params));
                 }
-                result = mITts.speak(mPackageName, text, queueMode, mCachedParams);
-            } catch (RemoteException e) {
-                restart("speak", e);
-            } catch (NullPointerException e) {
-                restart("speak", e);
-            } catch (IllegalStateException e) {
-                restart("speak", e);
-            } finally {
-                resetCachedParams();
             }
-            return result;
-        }
+        }, ERROR, "speak");
     }
 
-
     /**
      * Plays the earcon using the specified queueing mode and parameters.
+     * The earcon must already have been added with {@link #addEarcon(String, String)} or
+     * {@link #addEarcon(String, String, int)}.
      *
-     * @param earcon
-     *            The earcon that should be played
-     * @param queueMode
-     *            {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
-     * @param params
-     *            The list of parameters to be used. Can be null if no parameters are given.
-     *            They are specified using a (key, value) pair, where the key can be
-     *            {@link Engine#KEY_PARAM_STREAM} or
+     * @param earcon The earcon that should be played
+     * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
+     * @param params Parameters for the request. Can be null.
+     *            Supported parameter names:
+     *            {@link Engine#KEY_PARAM_STREAM},
      *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
      *
-     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+     * @return {@link #ERROR} or {@link #SUCCESS}.
      */
-    public int playEarcon(String earcon, int queueMode,
-            HashMap<String,String> params) {
-        synchronized (mStartLock) {
-            int result = ERROR;
-            if (!mStarted) {
-                return result;
-            }
-            try {
-                if ((params != null) && (!params.isEmpty())) {
-                    String extra = params.get(Engine.KEY_PARAM_STREAM);
-                    if (extra != null) {
-                        mCachedParams[Engine.PARAM_POSITION_STREAM + 1] = extra;
-                    }
-                    setCachedParam(params, Engine.KEY_PARAM_STREAM, Engine.PARAM_POSITION_STREAM);
-                    setCachedParam(params, Engine.KEY_PARAM_UTTERANCE_ID,
-                            Engine.PARAM_POSITION_UTTERANCE_ID);
+    public int playEarcon(final String earcon, final int queueMode,
+            final HashMap<String, String> params) {
+        return runAction(new Action<Integer>() {
+            @Override
+            public Integer run(ITextToSpeechService service) throws RemoteException {
+                Uri earconUri = mEarcons.get(earcon);
+                if (earconUri == null) {
+                    return ERROR;
                 }
-                result = mITts.playEarcon(mPackageName, earcon, queueMode, null);
-            } catch (RemoteException e) {
-                restart("playEarcon", e);
-            } catch (NullPointerException e) {
-                restart("playEarcon", e);
-            } catch (IllegalStateException e) {
-                restart("playEarcon", e);
-            } finally {
-                resetCachedParams();
+                return service.playAudio(getPackageName(), earconUri, queueMode,
+                        getParams(params));
             }
-            return result;
-        }
+        }, ERROR, "playEarcon");
     }
 
     /**
      * Plays silence for the specified amount of time using the specified
      * queue mode.
      *
-     * @param durationInMs
-     *            A long that indicates how long the silence should last.
-     * @param queueMode
-     *            {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
-     * @param params
-     *            The list of parameters to be used. Can be null if no parameters are given.
-     *            They are specified using a (key, value) pair, where the key can be
+     * @param durationInMs The duration of the silence.
+     * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
+     * @param params Parameters for the request. Can be null.
+     *            Supported parameter names:
      *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
      *
-     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+     * @return {@link #ERROR} or {@link #SUCCESS}.
      */
-    public int playSilence(long durationInMs, int queueMode, HashMap<String,String> params) {
-        synchronized (mStartLock) {
-            int result = ERROR;
-            if (!mStarted) {
-                return result;
+    public int playSilence(final long durationInMs, final int queueMode,
+            final HashMap<String, String> params) {
+        return runAction(new Action<Integer>() {
+            @Override
+            public Integer run(ITextToSpeechService service) throws RemoteException {
+                return service.playSilence(getPackageName(), durationInMs, queueMode,
+                        getParams(params));
             }
-            try {
-                if ((params != null) && (!params.isEmpty())) {
-                    setCachedParam(params, Engine.KEY_PARAM_UTTERANCE_ID,
-                            Engine.PARAM_POSITION_UTTERANCE_ID);
-                }
-                result = mITts.playSilence(mPackageName, durationInMs, queueMode, mCachedParams);
-            } catch (RemoteException e) {
-                restart("playSilence", e);
-            } catch (NullPointerException e) {
-                restart("playSilence", e);
-            } catch (IllegalStateException e) {
-                restart("playSilence", e);
-            } finally {
-                resetCachedParams();
-            }
-            return result;
-        }
+        }, ERROR, "playSilence");
     }
 
-
     /**
-     * Returns whether or not the TextToSpeech engine is busy speaking.
+     * Checks whether the TTS engine is busy speaking.
      *
-     * @return Whether or not the TextToSpeech engine is busy speaking.
+     * @return {@code true} if the TTS engine is speaking.
      */
     public boolean isSpeaking() {
-        synchronized (mStartLock) {
-            if (!mStarted) {
-                return false;
+        return runAction(new Action<Boolean>() {
+            @Override
+            public Boolean run(ITextToSpeechService service) throws RemoteException {
+                return service.isSpeaking();
             }
-            try {
-                return mITts.isSpeaking();
-            } catch (RemoteException e) {
-                restart("isSpeaking", e);
-            } catch (NullPointerException e) {
-                restart("isSpeaking", e);
-            } catch (IllegalStateException e) {
-                restart("isSpeaking", e);
-            }
-            return false;
-        }
+        }, false, "isSpeaking");
     }
 
-
     /**
      * Interrupts the current utterance (whether played or rendered to file) and discards other
      * utterances in the queue.
      *
-     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+     * @return {@link #ERROR} or {@link #SUCCESS}.
      */
     public int stop() {
-        synchronized (mStartLock) {
-            int result = ERROR;
-            if (!mStarted) {
-                return result;
+        return runAction(new Action<Integer>() {
+            @Override
+            public Integer run(ITextToSpeechService service) throws RemoteException {
+                return service.stop(getPackageName());
             }
-            try {
-                result = mITts.stop(mPackageName);
-            } catch (RemoteException e) {
-                restart("stop", e);
-            } catch (NullPointerException e) {
-                restart("stop", e);
-            } catch (IllegalStateException e) {
-                restart("stop", e);
-            }
-            return result;
-        }
+        }, ERROR, "stop");
     }
 
-
     /**
-     * Sets the speech rate for the TextToSpeech engine.
+     * Sets the speech rate.
      *
      * This has no effect on any pre-recorded speech.
      *
-     * @param speechRate
-     *            The speech rate for the TextToSpeech engine. 1 is the normal speed,
-     *            lower values slow down the speech (0.5 is half the normal speech rate),
-     *            greater values accelerate it (2 is twice the normal speech rate).
+     * @param speechRate Speech rate. {@code 1.0} is the normal speech rate,
+     *            lower values slow down the speech ({@code 0.5} is half the normal speech rate),
+     *            greater values accelerate it ({@code 2.0} is twice the normal speech rate).
      *
-     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+     * @return {@link #ERROR} or {@link #SUCCESS}.
      */
     public int setSpeechRate(float speechRate) {
-        synchronized (mStartLock) {
-            int result = ERROR;
-            if (!mStarted) {
-                return result;
-            }
-            try {
-                if (speechRate > 0) {
-                    int rate = (int)(speechRate*100);
-                    mCachedParams[Engine.PARAM_POSITION_RATE + 1] = String.valueOf(rate);
-                    // the rate is not set here, instead it is cached so it will be associated
-                    // with all upcoming utterances.
-                    if (speechRate > 0.0f) {
-                        result = SUCCESS;
-                    } else {
-                        result = ERROR;
-                    }
+        if (speechRate > 0.0f) {
+            int intRate = (int)(speechRate * 100);
+            if (intRate > 0) {
+                synchronized (mStartLock) {
+                    mParams.putInt(Engine.KEY_PARAM_RATE, intRate);
                 }
-            } catch (NullPointerException e) {
-                restart("setSpeechRate", e);
-            } catch (IllegalStateException e) {
-                restart("setSpeechRate", e);
+                return SUCCESS;
             }
-            return result;
         }
+        return ERROR;
     }
 
-
     /**
      * Sets the speech pitch for the TextToSpeech engine.
      *
      * This has no effect on any pre-recorded speech.
      *
-     * @param pitch
-     *            The pitch for the TextToSpeech engine. 1 is the normal pitch,
+     * @param pitch Speech pitch. {@code 1.0} is the normal pitch,
      *            lower values lower the tone of the synthesized voice,
      *            greater values increase it.
      *
-     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+     * @return {@link #ERROR} or {@link #SUCCESS}.
      */
     public int setPitch(float pitch) {
-        synchronized (mStartLock) {
-            int result = ERROR;
-            if (!mStarted) {
-                return result;
-            }
-            try {
-                // the pitch is not set here, instead it is cached so it will be associated
-                // with all upcoming utterances.
-                if (pitch > 0) {
-                    int p = (int)(pitch*100);
-                    mCachedParams[Engine.PARAM_POSITION_PITCH + 1] = String.valueOf(p);
-                    result = SUCCESS;
+        if (pitch > 0.0f) {
+            int intPitch = (int)(pitch * 100);
+            if (intPitch > 0) {
+                synchronized (mStartLock) {
+                    mParams.putInt(Engine.KEY_PARAM_PITCH, intPitch);
                 }
-            } catch (NullPointerException e) {
-                restart("setPitch", e);
-            } catch (IllegalStateException e) {
-                restart("setPitch", e);
+                return SUCCESS;
             }
-            return result;
         }
+        return ERROR;
     }
 
-
     /**
-     * Sets the language for the TextToSpeech engine.
-     * The TextToSpeech engine will try to use the closest match to the specified
+     * Sets the text-to-speech language.
+     * The TTS engine will try to use the closest match to the specified
      * language as represented by the Locale, but there is no guarantee that the exact same Locale
      * will be used. Use {@link #isLanguageAvailable(Locale)} to check the level of support
      * before choosing the language to use for the next utterances.
      *
-     * @param loc
-     *            The locale describing the language to be used.
+     * @param loc The locale describing the language to be used.
      *
-     * @return code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
+     * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
      *         {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
      *         {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
      */
-    public int setLanguage(Locale loc) {
-        synchronized (mStartLock) {
-            int result = LANG_NOT_SUPPORTED;
-            if (!mStarted) {
-                return result;
-            }
-            if (loc == null) {
-                return result;
-            }
-            try {
+    public int setLanguage(final Locale loc) {
+        return runAction(new Action<Integer>() {
+            @Override
+            public Integer run(ITextToSpeechService service) throws RemoteException {
+                if (loc == null) {
+                    return LANG_NOT_SUPPORTED;
+                }
                 String language = loc.getISO3Language();
                 String country = loc.getISO3Country();
                 String variant = loc.getVariant();
@@ -1000,294 +853,316 @@
                 // the available parts.
                 // Note that the language is not actually set here, instead it is cached so it
                 // will be associated with all upcoming utterances.
-                result = mITts.isLanguageAvailable(language, country, variant, mCachedParams);
+                int result = service.loadLanguage(language, country, variant);
                 if (result >= LANG_AVAILABLE){
-                    mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1] = language;
-                    if (result >= LANG_COUNTRY_AVAILABLE){
-                        mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1] = country;
-                    } else {
-                        mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1] = "";
+                    if (result < LANG_COUNTRY_VAR_AVAILABLE) {
+                        variant = "";
+                        if (result < LANG_COUNTRY_AVAILABLE) {
+                            country = "";
+                        }
                     }
-                    if (result >= LANG_COUNTRY_VAR_AVAILABLE){
-                        mCachedParams[Engine.PARAM_POSITION_VARIANT + 1] = variant;
-                    } else {
-                        mCachedParams[Engine.PARAM_POSITION_VARIANT + 1] = "";
-                    }
+                    mParams.putString(Engine.KEY_PARAM_LANGUAGE, language);
+                    mParams.putString(Engine.KEY_PARAM_COUNTRY, country);
+                    mParams.putString(Engine.KEY_PARAM_VARIANT, variant);
                 }
-            } catch (RemoteException e) {
-                restart("setLanguage", e);
-            } catch (NullPointerException e) {
-                restart("setLanguage", e);
-            } catch (IllegalStateException e) {
-                restart("setLanguage", e);
+                return result;
             }
-            return result;
-        }
+        }, LANG_NOT_SUPPORTED, "setLanguage");
     }
 
-
     /**
      * Returns a Locale instance describing the language currently being used by the TextToSpeech
      * engine.
+     *
      * @return language, country (if any) and variant (if any) used by the engine stored in a Locale
-     *     instance, or null is the TextToSpeech engine has failed.
+     *     instance, or {@code null} on error.
      */
     public Locale getLanguage() {
-        synchronized (mStartLock) {
-            if (!mStarted) {
+        return runAction(new Action<Locale>() {
+            @Override
+            public Locale run(ITextToSpeechService service) throws RemoteException {
+                String[] locStrings = service.getLanguage();
+                if (locStrings != null && locStrings.length == 3) {
+                    return new Locale(locStrings[0], locStrings[1], locStrings[2]);
+                }
                 return null;
             }
-            try {
-                // Only do a call to the native synth if there is nothing in the cached params
-                if (mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1].length() < 1){
-                    String[] locStrings = mITts.getLanguage();
-                    if ((locStrings != null) && (locStrings.length == 3)) {
-                        return new Locale(locStrings[0], locStrings[1], locStrings[2]);
-                    } else {
-                        return null;
-                    }
-                } else {
-                    return new Locale(mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1],
-                            mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1],
-                            mCachedParams[Engine.PARAM_POSITION_VARIANT + 1]);
-                }
-            } catch (RemoteException e) {
-                restart("getLanguage", e);
-            } catch (NullPointerException e) {
-                restart("getLanguage", e);
-            } catch (IllegalStateException e) {
-                restart("getLanguage", e);
-            }
-            return null;
-        }
+        }, null, "getLanguage");
     }
 
     /**
      * Checks if the specified language as represented by the Locale is available and supported.
      *
-     * @param loc
-     *            The Locale describing the language to be used.
+     * @param loc The Locale describing the language to be used.
      *
-     * @return code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
+     * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
      *         {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
      *         {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
      */
-    public int isLanguageAvailable(Locale loc) {
-        synchronized (mStartLock) {
-            int result = LANG_NOT_SUPPORTED;
-            if (!mStarted) {
-                return result;
+    public int isLanguageAvailable(final Locale loc) {
+        return runAction(new Action<Integer>() {
+            @Override
+            public Integer run(ITextToSpeechService service) throws RemoteException {
+                return service.isLanguageAvailable(loc.getISO3Language(),
+                        loc.getISO3Country(), loc.getVariant());
             }
-            try {
-                result = mITts.isLanguageAvailable(loc.getISO3Language(),
-                        loc.getISO3Country(), loc.getVariant(), mCachedParams);
-            } catch (RemoteException e) {
-                restart("isLanguageAvailable", e);
-            } catch (NullPointerException e) {
-                restart("isLanguageAvailable", e);
-            } catch (IllegalStateException e) {
-                restart("isLanguageAvailable", e);
-            }
-            return result;
-        }
+        }, LANG_NOT_SUPPORTED, "isLanguageAvailable");
     }
 
-
     /**
      * Synthesizes the given text to a file using the specified parameters.
      *
-     * @param text
-     *            The String of text that should be synthesized
-     * @param params
-     *            The list of parameters to be used. Can be null if no parameters are given.
-     *            They are specified using a (key, value) pair, where the key can be
+     * @param text Thetext that should be synthesized
+     * @param params Parameters for the request. Can be null.
+     *            Supported parameter names:
      *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
-     * @param filename
-     *            The string that gives the full output filename; it should be
+     * @param filename Absolute file filename to write the generated audio data to.It should be
      *            something like "/sdcard/myappsounds/mysound.wav".
      *
-     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+     * @return {@link #ERROR} or {@link #SUCCESS}.
      */
-    public int synthesizeToFile(String text, HashMap<String,String> params,
-            String filename) {
-        Log.i("TextToSpeech.java", "synthesizeToFile()");
-        synchronized (mStartLock) {
-            int result = ERROR;
-            Log.i("TextToSpeech.java - synthesizeToFile", "synthesizeToFile text of length "
-                    + text.length());
-            if (!mStarted) {
-                Log.e("TextToSpeech.java - synthesizeToFile", "service isn't started");
-                return result;
+    public int synthesizeToFile(final String text, final HashMap<String, String> params,
+            final String filename) {
+        return runAction(new Action<Integer>() {
+            @Override
+            public Integer run(ITextToSpeechService service) throws RemoteException {
+                return service.synthesizeToFile(getPackageName(), text, filename,
+                        getParams(params));
             }
+        }, ERROR, "synthesizeToFile");
+    }
+
+    private Bundle getParams(HashMap<String, String> params) {
+        if (params != null && !params.isEmpty()) {
+            Bundle bundle = new Bundle(mParams);
+            copyIntParam(bundle, params, Engine.KEY_PARAM_STREAM);
+            copyStringParam(bundle, params, Engine.KEY_PARAM_UTTERANCE_ID);
+            copyFloatParam(bundle, params, Engine.KEY_PARAM_VOLUME);
+            copyFloatParam(bundle, params, Engine.KEY_PARAM_PAN);
+            return bundle;
+        } else {
+            return mParams;
+        }
+    }
+
+    private void copyStringParam(Bundle bundle, HashMap<String, String> params, String key) {
+        String value = params.get(key);
+        if (value != null) {
+            bundle.putString(key, value);
+        }
+    }
+
+    private void copyIntParam(Bundle bundle, HashMap<String, String> params, String key) {
+        String valueString = params.get(key);
+        if (!TextUtils.isEmpty(valueString)) {
             try {
-                if ((params != null) && (!params.isEmpty())) {
-                    // no need to read the stream type here
-                    setCachedParam(params, Engine.KEY_PARAM_UTTERANCE_ID,
-                            Engine.PARAM_POSITION_UTTERANCE_ID);
-                    setCachedParam(params, Engine.KEY_PARAM_ENGINE, Engine.PARAM_POSITION_ENGINE);
-                }
-                result = mITts.synthesizeToFile(mPackageName, text, mCachedParams, filename) ?
-                        SUCCESS : ERROR;
-            } catch (RemoteException e) {
-                restart("synthesizeToFile", e);
-            } catch (NullPointerException e) {
-                restart("synthesizeToFile", e);
-            } catch (IllegalStateException e) {
-                restart("synthesizeToFile", e);
-            } finally {
-                resetCachedParams();
+                int value = Integer.parseInt(valueString);
+                bundle.putInt(key, value);
+            } catch (NumberFormatException ex) {
+                // don't set the value in the bundle
             }
-            return result;
         }
     }
 
-
-    /**
-     * Convenience method to reset the cached parameters to the current default values
-     * if they are not persistent between calls to the service.
-     */
-    private void resetCachedParams() {
-        mCachedParams[Engine.PARAM_POSITION_STREAM + 1] =
-                String.valueOf(Engine.DEFAULT_STREAM);
-        mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID+ 1] = "";
-        mCachedParams[Engine.PARAM_POSITION_VOLUME + 1] = Engine.DEFAULT_VOLUME_STRING;
-        mCachedParams[Engine.PARAM_POSITION_PAN + 1] = Engine.DEFAULT_PAN_STRING;
-    }
-
-    /**
-     * Convenience method to save a parameter in the cached parameter array, at the given index,
-     * for a property saved in the given hashmap.
-     */
-    private void setCachedParam(HashMap<String,String> params, String key, int keyIndex) {
-        String extra = params.get(key);
-        if (extra != null) {
-            mCachedParams[keyIndex+1] = extra;
+    private void copyFloatParam(Bundle bundle, HashMap<String, String> params, String key) {
+        String valueString = params.get(key);
+        if (!TextUtils.isEmpty(valueString)) {
+            try {
+                float value = Float.parseFloat(valueString);
+                bundle.putFloat(key, value);
+            } catch (NumberFormatException ex) {
+                // don't set the value in the bundle
+            }
         }
     }
 
     /**
-     * Sets the OnUtteranceCompletedListener that will fire when an utterance completes.
+     * Sets the listener that will be notified when synthesis of an utterance completes.
      *
-     * @param listener
-     *            The OnUtteranceCompletedListener
+     * @param listener The listener to use.
      *
-     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+     * @return {@link #ERROR} or {@link #SUCCESS}.
      */
-    public int setOnUtteranceCompletedListener(
-            final OnUtteranceCompletedListener listener) {
-        synchronized (mStartLock) {
-            int result = ERROR;
-            if (!mStarted) {
-                return result;
-            }
-            mITtscallback = new ITtsCallback.Stub() {
-                public void utteranceCompleted(String utteranceId) throws RemoteException {
-                    if (listener != null) {
-                        listener.onUtteranceCompleted(utteranceId);
+    public int setOnUtteranceCompletedListener(final OnUtteranceCompletedListener listener) {
+        return runAction(new Action<Integer>() {
+            @Override
+            public Integer run(ITextToSpeechService service) throws RemoteException {
+                ITextToSpeechCallback.Stub callback = new ITextToSpeechCallback.Stub() {
+                    public void utteranceCompleted(String utteranceId) {
+                        if (listener != null) {
+                            listener.onUtteranceCompleted(utteranceId);
+                        }
                     }
-                }
-            };
-            try {
-                result = mITts.registerCallback(mPackageName, mITtscallback);
-            } catch (RemoteException e) {
-                restart("registerCallback", e);
-            } catch (NullPointerException e) {
-                restart("registerCallback", e);
-            } catch (IllegalStateException e) {
-                restart("registerCallback", e);
+                };
+                service.setCallback(getPackageName(), callback);
+                return SUCCESS;
             }
-            return result;
-        }
+        }, ERROR, "setOnUtteranceCompletedListener");
     }
 
     /**
-     * Sets the speech synthesis engine to be used by its packagename.
+     * Sets the TTS engine to use.
      *
-     * @param enginePackageName
-     *            The packagename for the synthesis engine (ie, "com.svox.pico")
+     * @param enginePackageName The package name for the synthesis engine (e.g. "com.svox.pico")
      *
-     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+     * @return {@link #ERROR} or {@link #SUCCESS}.
      */
+    // TODO: add @Deprecated{This method does not tell the caller when the new engine
+    // has been initialized. You should create a new TextToSpeech object with the new
+    // engine instead.}
     public int setEngineByPackageName(String enginePackageName) {
-        synchronized (mStartLock) {
-            int result = TextToSpeech.ERROR;
-            if (!mStarted) {
-                return result;
-            }
-            try {
-                result = mITts.setEngineByPackageName(enginePackageName);
-                if (result == TextToSpeech.SUCCESS){
-                    mCachedParams[Engine.PARAM_POSITION_ENGINE + 1] = enginePackageName;
-                }
-            } catch (RemoteException e) {
-                restart("setEngineByPackageName", e);
-            } catch (NullPointerException e) {
-                restart("setEngineByPackageName", e);
-            } catch (IllegalStateException e) {
-                restart("setEngineByPackageName", e);
-            }
-            return result;
-        }
+        mRequestedEngine = enginePackageName;
+        return initTts();
     }
 
-
     /**
-     * Gets the packagename of the default speech synthesis engine.
+     * Gets the package name of the default speech synthesis engine.
      *
-     * @return Packagename of the TTS engine that the user has chosen as their default.
+     * @return Package name of the TTS engine that the user has chosen as their default.
      */
     public String getDefaultEngine() {
-        synchronized (mStartLock) {
-            String engineName = "";
-            if (!mStarted) {
-                return engineName;
-            }
-            try {
-                engineName = mITts.getDefaultEngine();
-            } catch (RemoteException e) {
-                restart("getDefaultEngine", e);
-            } catch (NullPointerException e) {
-                restart("getDefaultEngine", e);
-            } catch (IllegalStateException e) {
-                restart("getDefaultEngine", e);
-            }
-            return engineName;
-        }
+        String engine = Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.TTS_DEFAULT_SYNTH);
+        return engine != null ? engine : Engine.DEFAULT_ENGINE;
     }
 
-
     /**
-     * Returns whether or not the user is forcing their defaults to override the
-     * Text-To-Speech settings set by applications.
-     *
-     * @return Whether or not defaults are enforced.
+     * Checks whether the user's settings should override settings requested by the calling
+     * application.
      */
     public boolean areDefaultsEnforced() {
-        synchronized (mStartLock) {
-            boolean defaultsEnforced = false;
-            if (!mStarted) {
-                return defaultsEnforced;
-            }
-            try {
-                defaultsEnforced = mITts.areDefaultsEnforced();
-            } catch (RemoteException e) {
-                restart("areDefaultsEnforced", e);
-            } catch (NullPointerException e) {
-                restart("areDefaultsEnforced", e);
-            } catch (IllegalStateException e) {
-                restart("areDefaultsEnforced", e);
-            }
-            return defaultsEnforced;
+        return Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.TTS_USE_DEFAULTS, Engine.USE_DEFAULTS) == 1;
+    }
+
+    private boolean isEngineEnabled(String engine) {
+        if (Engine.DEFAULT_ENGINE.equals(engine)) {
+            return true;
         }
+        for (String enabled : getEnabledEngines()) {
+            if (engine.equals(enabled)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private String[] getEnabledEngines() {
+        String str = Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.TTS_ENABLED_PLUGINS);
+        if (TextUtils.isEmpty(str)) {
+            return new String[0];
+        }
+        return str.split(" ");
     }
 
     /**
-     * Restarts the TTS after a failure.
+     * Gets a list of all installed TTS engines.
+     *
+     * @return A list of engine info objects. The list can be empty, but will never by {@code null}.
+     *
+     * @hide Pending approval
      */
-    private void restart(String method, Exception e) {
-        // TTS died; restart it.
-        Log.e(TAG, method, e);
-        mStarted = false;
-        initTts();
+    public List<EngineInfo> getEngines() {
+        PackageManager pm = mContext.getPackageManager();
+        Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
+        List<ResolveInfo> resolveInfos = pm.queryIntentServices(intent, 0);
+        if (resolveInfos == null) return Collections.emptyList();
+        List<EngineInfo> engines = new ArrayList<EngineInfo>(resolveInfos.size());
+        for (ResolveInfo resolveInfo : resolveInfos) {
+            ServiceInfo service = resolveInfo.serviceInfo;
+            if (service != null) {
+                EngineInfo engine = new EngineInfo();
+                // Using just the package name isn't great, since it disallows having
+                // multiple engines in the same package, but that's what the existing API does.
+                engine.name = service.packageName;
+                CharSequence label = service.loadLabel(pm);
+                engine.label = TextUtils.isEmpty(label) ? engine.name : label.toString();
+                engine.icon = service.getIconResource();
+                engines.add(engine);
+            }
+        }
+        return engines;
+    }
+
+    private class Connection implements ServiceConnection {
+        private ITextToSpeechService mService;
+
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            Log.i(TAG, "Connected to " + name);
+            synchronized(mStartLock) {
+                if (mServiceConnection != null) {
+                    // Disconnect any previous service connection
+                    mServiceConnection.disconnect();
+                }
+                mServiceConnection = this;
+                mService = ITextToSpeechService.Stub.asInterface(service);
+                dispatchOnInit(SUCCESS);
+            }
+        }
+
+        public void onServiceDisconnected(ComponentName name) {
+            synchronized(mStartLock) {
+                mService = null;
+                // If this is the active connection, clear it
+                if (mServiceConnection == this) {
+                    mServiceConnection = null;
+                }
+            }
+        }
+
+        public void disconnect() {
+            mContext.unbindService(this);
+        }
+
+        public <R> R runAction(Action<R> action, R errorResult, String method, boolean reconnect) {
+            try {
+                synchronized (mStartLock) {
+                    if (mService == null) {
+                        Log.w(TAG, method + " failed: not connected to TTS engine");
+                        return errorResult;
+                    }
+                    return action.run(mService);
+                }
+            } catch (RemoteException ex) {
+                Log.e(TAG, method + " failed", ex);
+                if (reconnect) {
+                    disconnect();
+                    initTts();
+                }
+                return errorResult;
+            }
+        }
+    }
+
+    private interface Action<R> {
+        R run(ITextToSpeechService service) throws RemoteException;
+    }
+
+    /**
+     * Information about an installed text-to-speech engine.
+     *
+     * @see TextToSpeech#getEngines
+     * @hide Pending approval
+     */
+    public static class EngineInfo {
+        /**
+         * Engine package name..
+         */
+        public String name;
+        /**
+         * Localized label for the engine.
+         */
+        public String label;
+        /**
+         * Icon for the engine.
+         */
+        public int icon;
+
+        @Override
+        public String toString() {
+            return "EngineInfo{name=" + name + "}";
+        }
+
     }
 }
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
new file mode 100644
index 0000000..a408ea2
--- /dev/null
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -0,0 +1,715 @@
+/*
+ * Copyright (C) 2011 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.speech.tts;
+
+import android.app.Service;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.speech.tts.TextToSpeech.Engine;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Locale;
+
+
+/**
+ * Abstract base class for TTS engine implementations.
+ *
+ * @hide Pending approval
+ */
+public abstract class TextToSpeechService extends Service {
+
+    private static final boolean DBG = false;
+    private static final String TAG = "TextToSpeechService";
+
+    private static final int MAX_SPEECH_ITEM_CHAR_LENGTH = 4000;
+    private static final String SYNTH_THREAD_NAME = "SynthThread";
+
+    private SynthHandler mSynthHandler;
+
+    private CallbackMap mCallbacks;
+
+    @Override
+    public void onCreate() {
+        if (DBG) Log.d(TAG, "onCreate()");
+        super.onCreate();
+
+        SynthThread synthThread = new SynthThread();
+        synthThread.start();
+        mSynthHandler = new SynthHandler(synthThread.getLooper());
+
+        mCallbacks = new CallbackMap();
+
+        // Load default language
+        onLoadLanguage(getDefaultLanguage(), getDefaultCountry(), getDefaultVariant());
+    }
+
+    @Override
+    public void onDestroy() {
+        if (DBG) Log.d(TAG, "onDestroy()");
+
+        // Tell the synthesizer to stop
+        mSynthHandler.quit();
+
+        // Unregister all callbacks.
+        mCallbacks.kill();
+
+        super.onDestroy();
+    }
+
+    /**
+     * Checks whether the engine supports a given language.
+     *
+     * Can be called on multiple threads.
+     *
+     * @param lang ISO-3 language code.
+     * @param country ISO-3 country code. May be empty or null.
+     * @param variant Language variant. May be empty or null.
+     * @return Code indicating the support status for the locale.
+     *         One of {@link TextToSpeech#LANG_AVAILABLE},
+     *         {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
+     *         {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
+     *         {@link TextToSpeech#LANG_MISSING_DATA}
+     *         {@link TextToSpeech#LANG_NOT_SUPPORTED}.
+     */
+    protected abstract int onIsLanguageAvailable(String lang, String country, String variant);
+
+    /**
+     * Returns the language, country and variant currently being used by the TTS engine.
+     *
+     * Can be called on multiple threads.
+     *
+     * @return A 3-element array, containing language (ISO 3-letter code),
+     *         country (ISO 3-letter code) and variant used by the engine.
+     *         The country and variant may be {@code ""}. If country is empty, then variant must
+     *         be empty too.
+     * @see Locale#getISO3Language()
+     * @see Locale#getISO3Country()
+     * @see Locale#getVariant()
+     */
+    protected abstract String[] onGetLanguage();
+
+    /**
+     * Notifies the engine that it should load a speech synthesis language. There is no guarantee
+     * that this method is always called before the language is used for synthesis. It is merely
+     * a hint to the engine that it will probably get some synthesis requests for this language
+     * at some point in the future.
+     *
+     * Can be called on multiple threads.
+     *
+     * @param lang ISO-3 language code.
+     * @param country ISO-3 country code. May be empty or null.
+     * @param variant Language variant. May be empty or null.
+     * @return Code indicating the support status for the locale.
+     *         One of {@link TextToSpeech#LANG_AVAILABLE},
+     *         {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
+     *         {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
+     *         {@link TextToSpeech#LANG_MISSING_DATA}
+     *         {@link TextToSpeech#LANG_NOT_SUPPORTED}.
+     */
+    protected abstract int onLoadLanguage(String lang, String country, String variant);
+
+    /**
+     * Notifies the service that it should stop any in-progress speech synthesis.
+     * This method can be called even if no speech synthesis is currently in progress.
+     *
+     * Can be called on multiple threads, but not on the synthesis thread.
+     */
+    protected abstract void onStop();
+
+    /**
+     * Tells the service to synthesize speech from the given text. This method should
+     * block until the synthesis is finished.
+     *
+     * Called on the synthesis thread.
+     *
+     * @param request The synthesis request. The method should
+     *         call {@link SynthesisRequest#start}, {@link SynthesisRequest#audioAvailable},
+     *         and {@link SynthesisRequest#done} on this request.
+     * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+     */
+    protected abstract int onSynthesizeText(SynthesisRequest request);
+
+    private boolean areDefaultsEnforced() {
+        return getSecureSettingInt(Settings.Secure.TTS_USE_DEFAULTS,
+                TextToSpeech.Engine.USE_DEFAULTS) == 1;
+    }
+
+    private int getDefaultSpeechRate() {
+        return getSecureSettingInt(Settings.Secure.TTS_DEFAULT_RATE, Engine.DEFAULT_RATE);
+    }
+
+    private String getDefaultLanguage() {
+        return getSecureSettingString(Settings.Secure.TTS_DEFAULT_LANG,
+                Locale.getDefault().getISO3Language());
+    }
+
+    private String getDefaultCountry() {
+        return getSecureSettingString(Settings.Secure.TTS_DEFAULT_COUNTRY,
+                Locale.getDefault().getISO3Country());
+    }
+
+    private String getDefaultVariant() {
+        return getSecureSettingString(Settings.Secure.TTS_DEFAULT_VARIANT,
+                Locale.getDefault().getVariant());
+    }
+
+    private int getSecureSettingInt(String name, int defaultValue) {
+        return Settings.Secure.getInt(getContentResolver(), name, defaultValue);
+    }
+
+    private String getSecureSettingString(String name, String defaultValue) {
+        String value = Settings.Secure.getString(getContentResolver(), name);
+        return value != null ? value : defaultValue;
+    }
+
+    /**
+     * Synthesizer thread. This thread is used to run {@link SynthHandler}.
+     */
+    private class SynthThread extends HandlerThread implements MessageQueue.IdleHandler {
+
+        private boolean mFirstIdle = true;
+
+        public SynthThread() {
+            super(SYNTH_THREAD_NAME, android.os.Process.THREAD_PRIORITY_AUDIO);
+        }
+
+        @Override
+        protected void onLooperPrepared() {
+            getLooper().getQueue().addIdleHandler(this);
+        }
+
+        @Override
+        public boolean queueIdle() {
+            if (mFirstIdle) {
+                mFirstIdle = false;
+            } else {
+                broadcastTtsQueueProcessingCompleted();
+            }
+            return true;
+        }
+
+        private void broadcastTtsQueueProcessingCompleted() {
+            Intent i = new Intent(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED);
+            if (DBG) Log.d(TAG, "Broadcasting: " + i);
+            sendBroadcast(i);
+        }
+    }
+
+    private class SynthHandler extends Handler {
+
+        private SpeechItem mCurrentSpeechItem = null;
+
+        public SynthHandler(Looper looper) {
+            super(looper);
+        }
+
+        private void dispatchUtteranceCompleted(SpeechItem item) {
+            String utteranceId = item.getUtteranceId();
+            if (!TextUtils.isEmpty(utteranceId)) {
+                mCallbacks.dispatchUtteranceCompleted(item.getCallingApp(), utteranceId);
+            }
+        }
+
+        private synchronized SpeechItem getCurrentSpeechItem() {
+            return mCurrentSpeechItem;
+        }
+
+        private synchronized SpeechItem setCurrentSpeechItem(SpeechItem speechItem) {
+            SpeechItem old = mCurrentSpeechItem;
+            mCurrentSpeechItem = speechItem;
+            return old;
+        }
+
+        public boolean isSpeaking() {
+            return getCurrentSpeechItem() != null;
+        }
+
+        public void quit() {
+            // Don't process any more speech items
+            getLooper().quit();
+            // Stop the current speech item
+            SpeechItem current = setCurrentSpeechItem(null);
+            if (current != null) {
+                current.stop();
+            }
+        }
+
+        /**
+         * Adds a speech item to the queue.
+         *
+         * Called on a service binder thread.
+         */
+        public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) {
+            if (!speechItem.isValid()) {
+                return TextToSpeech.ERROR;
+            }
+            // TODO: The old code also supported the undocumented queueMode == 2,
+            // which clears out all pending items from the calling app, as well as all
+            // non-file items from other apps.
+            if (queueMode == TextToSpeech.QUEUE_FLUSH) {
+                stop(speechItem.getCallingApp());
+            }
+            Runnable runnable = new Runnable() {
+                @Override
+                public void run() {
+                    setCurrentSpeechItem(speechItem);
+                    if (speechItem.play() == TextToSpeech.SUCCESS) {
+                        dispatchUtteranceCompleted(speechItem);
+                    }
+                    setCurrentSpeechItem(null);
+                }
+            };
+            Message msg = Message.obtain(this, runnable);
+            // The obj is used to remove all callbacks from the given app in stop(String).
+            msg.obj = speechItem.getCallingApp();
+            if (sendMessage(msg)) {
+                return TextToSpeech.SUCCESS;
+            } else {
+                Log.w(TAG, "SynthThread has quit");
+                return TextToSpeech.ERROR;
+            }
+        }
+
+        /**
+         * Stops all speech output and removes any utterances still in the queue for
+         * the calling app.
+         *
+         * Called on a service binder thread.
+         */
+        public int stop(String callingApp) {
+            if (TextUtils.isEmpty(callingApp)) {
+                return TextToSpeech.ERROR;
+            }
+            removeCallbacksAndMessages(callingApp);
+            SpeechItem current = setCurrentSpeechItem(null);
+            if (current != null && TextUtils.equals(callingApp, current.getCallingApp())) {
+                current.stop();
+            }
+            return TextToSpeech.SUCCESS;
+        }
+    }
+
+    /**
+     * An item in the synth thread queue.
+     */
+    private static abstract class SpeechItem {
+        private final String mCallingApp;
+        private final Bundle mParams;
+        private boolean mStarted = false;
+        private boolean mStopped = false;
+
+        public SpeechItem(String callingApp, Bundle params) {
+            mCallingApp = callingApp;
+            mParams = params;
+        }
+
+        public String getCallingApp() {
+            return mCallingApp;
+        }
+
+        /**
+         * Checker whether the item is valid. If this method returns false, the item should not
+         * be played.
+         */
+        public abstract boolean isValid();
+
+        /**
+         * Plays the speech item. Blocks until playback is finished.
+         * Must not be called more than once.
+         *
+         * Only called on the synthesis thread.
+         *
+         * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+         */
+        public int play() {
+            synchronized (this) {
+                if (mStarted) {
+                    throw new IllegalStateException("play() called twice");
+                }
+                mStarted = true;
+            }
+            return playImpl();
+        }
+
+        /**
+         * Stops the speech item.
+         * Must not be called more than once.
+         *
+         * Can be called on multiple threads,  but not on the synthesis thread.
+         */
+        public void stop() {
+            synchronized (this) {
+                if (mStopped) {
+                    throw new IllegalStateException("stop() called twice");
+                }
+                mStopped = true;
+            }
+            stopImpl();
+        }
+
+        protected abstract int playImpl();
+
+        protected abstract void stopImpl();
+
+        public int getStreamType() {
+            return getIntParam(Engine.KEY_PARAM_STREAM, Engine.DEFAULT_STREAM);
+        }
+
+        public float getVolume() {
+            return getFloatParam(Engine.KEY_PARAM_VOLUME, Engine.DEFAULT_VOLUME);
+        }
+
+        public float getPan() {
+            return getFloatParam(Engine.KEY_PARAM_PAN, Engine.DEFAULT_PAN);
+        }
+
+        public String getUtteranceId() {
+            return getStringParam(Engine.KEY_PARAM_UTTERANCE_ID, null);
+        }
+
+        protected String getStringParam(String key, String defaultValue) {
+            return mParams == null ? defaultValue : mParams.getString(key, defaultValue);
+        }
+
+        protected int getIntParam(String key, int defaultValue) {
+            return mParams == null ? defaultValue : mParams.getInt(key, defaultValue);
+        }
+
+        protected float getFloatParam(String key, float defaultValue) {
+            return mParams == null ? defaultValue : mParams.getFloat(key, defaultValue);
+        }
+    }
+
+    private class SynthesisSpeechItem extends SpeechItem {
+        private final String mText;
+        private SynthesisRequest mSynthesisRequest;
+
+        public SynthesisSpeechItem(String callingApp, Bundle params, String text) {
+            super(callingApp, params);
+            mText = text;
+        }
+
+        public String getText() {
+            return mText;
+        }
+
+        @Override
+        public boolean isValid() {
+            if (TextUtils.isEmpty(mText)) {
+                Log.w(TAG, "Got empty text");
+                return false;
+            }
+            if (mText.length() >= MAX_SPEECH_ITEM_CHAR_LENGTH){
+                Log.w(TAG, "Text too long: " + mText.length() + " chars");
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        protected int playImpl() {
+            SynthesisRequest synthesisRequest;
+            synchronized (this) {
+                mSynthesisRequest = createSynthesisRequest();
+                synthesisRequest = mSynthesisRequest;
+            }
+            setRequestParams(synthesisRequest);
+            return TextToSpeechService.this.onSynthesizeText(synthesisRequest);
+        }
+
+        protected SynthesisRequest createSynthesisRequest() {
+            return new PlaybackSynthesisRequest(mText, getStreamType(), getVolume(), getPan());
+        }
+
+        private void setRequestParams(SynthesisRequest request) {
+            if (areDefaultsEnforced()) {
+                request.setLanguage(getDefaultLanguage(), getDefaultCountry(), getDefaultVariant());
+                request.setSpeechRate(getDefaultSpeechRate());
+            } else {
+                request.setLanguage(getLanguage(), getCountry(), getVariant());
+                request.setSpeechRate(getSpeechRate());
+            }
+            request.setPitch(getPitch());
+        }
+
+        @Override
+        protected void stopImpl() {
+            SynthesisRequest synthesisRequest;
+            synchronized (this) {
+                synthesisRequest = mSynthesisRequest;
+            }
+            synthesisRequest.stop();
+            TextToSpeechService.this.onStop();
+        }
+
+        public String getLanguage() {
+            return getStringParam(Engine.KEY_PARAM_LANGUAGE, getDefaultLanguage());
+        }
+
+        private boolean hasLanguage() {
+            return !TextUtils.isEmpty(getStringParam(Engine.KEY_PARAM_LANGUAGE, null));
+        }
+
+        private String getCountry() {
+            if (!hasLanguage()) return getDefaultCountry();
+            return getStringParam(Engine.KEY_PARAM_COUNTRY, "");
+        }
+
+        private String getVariant() {
+            if (!hasLanguage()) return getDefaultVariant();
+            return getStringParam(Engine.KEY_PARAM_VARIANT, "");
+        }
+
+        private int getSpeechRate() {
+            return getIntParam(Engine.KEY_PARAM_RATE, getDefaultSpeechRate());
+        }
+
+        private int getPitch() {
+            return getIntParam(Engine.KEY_PARAM_PITCH, Engine.DEFAULT_PITCH);
+        }
+    }
+
+    private class SynthesisToFileSpeechItem extends SynthesisSpeechItem {
+        private final File mFile;
+
+        public SynthesisToFileSpeechItem(String callingApp, Bundle params, String text,
+                File file) {
+            super(callingApp, params, text);
+            mFile = file;
+        }
+
+        @Override
+        public boolean isValid() {
+            if (!super.isValid()) {
+                return false;
+            }
+            return checkFile(mFile);
+        }
+
+        @Override
+        protected SynthesisRequest createSynthesisRequest() {
+            return new FileSynthesisRequest(getText(), mFile);
+        }
+
+        /**
+         * Checks that the given file can be used for synthesis output.
+         */
+        private boolean checkFile(File file) {
+            try {
+                if (file.exists()) {
+                    Log.v(TAG, "File " + file + " exists, deleting.");
+                    if (!file.delete()) {
+                        Log.e(TAG, "Failed to delete " + file);
+                        return false;
+                    }
+                }
+                if (!file.createNewFile()) {
+                    Log.e(TAG, "Can't create file " + file);
+                    return false;
+                }
+                if (!file.delete()) {
+                    Log.e(TAG, "Failed to delete " + file);
+                    return false;
+                }
+                return true;
+            } catch (IOException e) {
+                Log.e(TAG, "Can't use " + file + " due to exception " + e);
+                return false;
+            }
+        }
+    }
+
+    private class AudioSpeechItem extends SpeechItem {
+
+        private final BlockingMediaPlayer mPlayer;
+
+        public AudioSpeechItem(String callingApp, Bundle params, Uri uri) {
+            super(callingApp, params);
+            mPlayer = new BlockingMediaPlayer(TextToSpeechService.this, uri, getStreamType());
+        }
+
+        @Override
+        public boolean isValid() {
+            return true;
+        }
+
+        @Override
+        protected int playImpl() {
+            return mPlayer.startAndWait() ? TextToSpeech.SUCCESS : TextToSpeech.ERROR;
+        }
+
+        @Override
+        protected void stopImpl() {
+            mPlayer.stop();
+        }
+    }
+
+    private class SilenceSpeechItem extends SpeechItem {
+        private final long mDuration;
+        private final ConditionVariable mDone;
+
+        public SilenceSpeechItem(String callingApp, Bundle params, long duration) {
+            super(callingApp, params);
+            mDuration = duration;
+            mDone = new ConditionVariable();
+        }
+
+        @Override
+        public boolean isValid() {
+            return true;
+        }
+
+        @Override
+        protected int playImpl() {
+            boolean aborted = mDone.block(mDuration);
+            return aborted ? TextToSpeech.ERROR : TextToSpeech.SUCCESS;
+        }
+
+        @Override
+        protected void stopImpl() {
+            mDone.open();
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE.equals(intent.getAction())) {
+            return mBinder;
+        }
+        return null;
+    }
+
+    /**
+     * Binder returned from {@code #onBind(Intent)}. The methods in this class can be
+     * called called from several different threads.
+     */
+    private final ITextToSpeechService.Stub mBinder = new ITextToSpeechService.Stub() {
+
+        public int speak(String callingApp, String text, int queueMode, Bundle params) {
+            SpeechItem item = new SynthesisSpeechItem(callingApp, params, text);
+            return mSynthHandler.enqueueSpeechItem(queueMode, item);
+        }
+
+        public int synthesizeToFile(String callingApp, String text, String filename,
+                Bundle params) {
+            File file = new File(filename);
+            SpeechItem item = new SynthesisToFileSpeechItem(callingApp, params, text, file);
+            return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
+        }
+
+        public int playAudio(String callingApp, Uri audioUri, int queueMode, Bundle params) {
+            SpeechItem item = new AudioSpeechItem(callingApp, params, audioUri);
+            return mSynthHandler.enqueueSpeechItem(queueMode, item);
+        }
+
+        public int playSilence(String callingApp, long duration, int queueMode, Bundle params) {
+            SpeechItem item = new SilenceSpeechItem(callingApp, params, duration);
+            return mSynthHandler.enqueueSpeechItem(queueMode, item);
+        }
+
+        public boolean isSpeaking() {
+            return mSynthHandler.isSpeaking();
+        }
+
+        public int stop(String callingApp) {
+            return mSynthHandler.stop(callingApp);
+        }
+
+        public String[] getLanguage() {
+            return onGetLanguage();
+        }
+
+        public int isLanguageAvailable(String lang, String country, String variant) {
+            return onIsLanguageAvailable(lang, country, variant);
+        }
+
+        public int loadLanguage(String lang, String country, String variant) {
+            return onLoadLanguage(lang, country, variant);
+        }
+
+        public void setCallback(String packageName, ITextToSpeechCallback cb) {
+            mCallbacks.setCallback(packageName, cb);
+        }
+    };
+
+    private class CallbackMap extends RemoteCallbackList<ITextToSpeechCallback> {
+
+        private final HashMap<String, ITextToSpeechCallback> mAppToCallback
+                = new HashMap<String, ITextToSpeechCallback>();
+
+        public void setCallback(String packageName, ITextToSpeechCallback cb) {
+            synchronized (mAppToCallback) {
+                ITextToSpeechCallback old;
+                if (cb != null) {
+                    register(cb, packageName);
+                    old = mAppToCallback.put(packageName, cb);
+                } else {
+                    old = mAppToCallback.remove(packageName);
+                }
+                if (old != null && old != cb) {
+                    unregister(old);
+                }
+            }
+        }
+
+        public void dispatchUtteranceCompleted(String packageName, String utteranceId) {
+            ITextToSpeechCallback cb;
+            synchronized (mAppToCallback) {
+                cb = mAppToCallback.get(packageName);
+            }
+            if (cb == null) return;
+            try {
+                cb.utteranceCompleted(utteranceId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Callback failed: " + e);
+            }
+        }
+
+        @Override
+        public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) {
+            String packageName = (String) cookie;
+            synchronized (mAppToCallback) {
+                mAppToCallback.remove(packageName);
+            }
+            mSynthHandler.stop(packageName);
+        }
+
+        @Override
+        public void kill() {
+            synchronized (mAppToCallback) {
+                mAppToCallback.clear();
+                super.kill();
+            }
+        }
+
+    }
+
+}
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index ee6342a..ac5db62 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -37,6 +37,7 @@
 import android.text.style.StrikethroughSpan;
 import android.text.style.StyleSpan;
 import android.text.style.SubscriptSpan;
+import android.text.style.SuggestionSpan;
 import android.text.style.SuperscriptSpan;
 import android.text.style.TextAppearanceSpan;
 import android.text.style.TypefaceSpan;
@@ -566,7 +567,7 @@
     /** @hide */
     public static final int ANNOTATION = 18;
     /** @hide */
-    public static final int CORRECTION_SPAN = 19;
+    public static final int SUGGESTION_SPAN = 19;
 
     /**
      * Flatten a CharSequence and whatever styles can be copied across processes
@@ -712,6 +713,10 @@
                     readSpan(p, sp, new Annotation(p));
                     break;
 
+                case SUGGESTION_SPAN:
+                    readSpan(p, sp, new SuggestionSpan(p));
+                    break;
+
                 default:
                     throw new RuntimeException("bogus span encoding " + kind);
                 }
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index 5091c9e..7083641 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -26,7 +26,7 @@
 import java.util.Locale;
 
 /**
- * Sets correction candidates of words under this span.
+ * Holds suggestion candidates of words under this span.
  */
 public class SuggestionSpan implements ParcelableSpan {
 
@@ -139,7 +139,7 @@
 
     @Override
     public int getSpanTypeId() {
-        return TextUtils.CORRECTION_SPAN;
+        return TextUtils.SUGGESTION_SPAN;
     }
 
     public static final Parcelable.Creator<SuggestionSpan> CREATOR =
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index a39c7c7..eef2a33 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -505,6 +505,13 @@
         }
     }
 
+    /**
+     * Returns a list of enabled input method subtypes for the specified input method info.
+     * @param imi An input method info whose subtypes list will be returned.
+     * @param allowsImplicitlySelectedSubtypes A boolean flag to allow to return the implicitly
+     * selected subtypes. If an input method info doesn't have enabled subtypes, the framework
+     * will implicitly enable subtypes according to the current system language.
+     */
     public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi,
             boolean allowsImplicitlySelectedSubtypes) {
         try {
@@ -1429,16 +1436,26 @@
         }
     }
 
-    public void showInputMethodAndSubtypeEnabler(String topId) {
+    /**
+     * Show the settings for enabling subtypes of the specified input method.
+     * @param imiId An input method, whose subtypes settings will be shown. If imiId is null,
+     * subtypes of all input methods will be shown.
+     */
+    public void showInputMethodAndSubtypeEnabler(String imiId) {
         synchronized (mH) {
             try {
-                mService.showInputMethodAndSubtypeEnablerFromClient(mClient, topId);
+                mService.showInputMethodAndSubtypeEnablerFromClient(mClient, imiId);
             } catch (RemoteException e) {
                 Log.w(TAG, "IME died: " + mCurId, e);
             }
         }
     }
 
+    /**
+     * Returns the current input method subtype. This subtype is one of the subtypes in
+     * the current input method. This method returns null when the current input method doesn't
+     * have any input method subtype.
+     */
     public InputMethodSubtype getCurrentInputMethodSubtype() {
         synchronized (mH) {
             try {
@@ -1450,6 +1467,12 @@
         }
     }
 
+    /**
+     * Switch to a new input method subtype of the current input method.
+     * @param subtype A new input method subtype to switch.
+     * @return true if the current subtype was successfully switched. When the specified subtype is
+     * null, this method returns false.
+     */
     public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
         synchronized (mH) {
             try {
@@ -1461,6 +1484,9 @@
         }
     }
 
+    /**
+     * Returns a map of all shortcut input method info and their subtypes.
+     */
     public Map<InputMethodInfo, List<InputMethodSubtype>> getShortcutInputMethodsAndSubtypes() {
         synchronized (mH) {
             HashMap<InputMethodInfo, List<InputMethodSubtype>> ret =
@@ -1493,6 +1519,15 @@
         }
     }
 
+    /**
+     * Force switch to the last used input method and subtype. If the last input method didn't have
+     * any subtypes, the framework will simply switch to the last input method with no subtype
+     * specified.
+     * @param imeToken Supplies the identifying token given to an input method when it was started,
+     * which allows it to perform this operation on itself.
+     * @return true if the current input method and subtype was successfully switched to the last
+     * used input method and subtype.
+     */
     public boolean switchToLastInputMethod(IBinder imeToken) {
         synchronized (mH) {
             try {
@@ -1504,6 +1539,17 @@
         }
     }
 
+    public InputMethodSubtype getLastInputMethodSubtype() {
+        synchronized (mH) {
+            try {
+                return mService.getLastInputMethodSubtype();
+            } catch (RemoteException e) {
+                Log.w(TAG, "IME died: " + mCurId, e);
+                return null;
+            }
+        }
+    }
+
     void doDump(FileDescriptor fd, PrintWriter fout, String[] args) {
         final Printer p = new PrintWriterPrinter(fout);
         p.println("Input method client state for " + this + ":");
diff --git a/core/java/android/webkit/webdriver/By.java b/core/java/android/webkit/webdriver/By.java
new file mode 100644
index 0000000..b40351d
--- /dev/null
+++ b/core/java/android/webkit/webdriver/By.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2011 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.webdriver;
+
+/**
+ * Mechanism to locate elements within the DOM of the page.
+ * @hide
+ */
+public abstract class By {
+    public abstract WebElement findElement(WebElement element);
+
+    /**
+     * Locates an element by its HTML id attribute.
+     *
+     * @param id The HTML id attribute to look for.
+     * @return A By instance that locates elements by their HTML id attributes.
+     */
+    public static By id(final String id) {
+        throwIfNull(id);
+        return new By() {
+            @Override
+            public WebElement findElement(WebElement element) {
+                return element.findElementById(id);
+            }
+
+            @Override
+            public String toString() {
+                return "By.id: " + id;
+            }
+        };
+    }
+
+    /**
+     * Locates an element by the matching the exact text on the HTML link.
+     *
+     * @param linkText The exact text to match against.
+     * @return A By instance that locates elements by the text displayed by
+     * the link.
+     */
+    public static By linkText(final String linkText) {
+        throwIfNull(linkText);
+        return new By() {
+            @Override
+            public WebElement findElement(WebElement element) {
+                return element.findElementByLinkText(linkText);
+            }
+
+            @Override
+            public String toString() {
+                return "By.linkText: " + linkText;
+            }
+        };
+    }
+
+    /**
+     * Locates an element by matching partial part of the text displayed by an
+     * HTML link.
+     *
+     * @param linkText The text that should be contained by the text displayed
+     * on the link.
+     * @return A By instance that locates elements that contain the given link
+     * text.
+     */
+    public static By partialLinkText(final String linkText) {
+        throwIfNull(linkText);
+        return new By() {
+            @Override
+            public WebElement findElement(WebElement element) {
+                return element.findElementByPartialLinkText(linkText);
+            }
+
+            @Override
+            public String toString() {
+                return "By.partialLinkText: " + linkText;
+            }
+        };
+    }
+
+    /**
+     * Locates an element by matching its HTML name attribute.
+     *
+     * @param name The value of the HTML name attribute.
+     * @return A By instance that locates elements by the HTML name attribute.
+     */
+    public static By name(final String name) {
+        throwIfNull(name);
+        return new By() {
+            @Override
+            public WebElement findElement(WebElement element) {
+                return element.findElementByName(name);
+            }
+
+            @Override
+            public String toString() {
+                return "By.name: " + name;
+            }
+        };
+    }
+
+    /**
+     * Locates an element by matching its class name.
+     * @param className The class name
+     * @return A By instance that locates elements by their class name attribute.
+     */
+    public static By className(final String className) {
+        throwIfNull(className);
+        return new By() {
+            @Override
+            public WebElement findElement(WebElement element) {
+                return element.findElementByClassName(className);
+            }
+
+            @Override
+            public String toString() {
+                return "By.className: " + className;
+            }
+        };
+    }
+
+    /**
+     * Locates an element by matching its css property.
+     *
+     * @param css The css property.
+     * @return A By instance that locates elements by their css property.
+     */
+    public static By css(final String css) {
+        throwIfNull(css);
+        return new By() {
+            @Override
+            public WebElement findElement(WebElement element) {
+                return element.findElementByCss(css);
+            }
+
+            @Override
+            public String toString() {
+                return "By.css: " + css;
+            }
+        };
+    }
+
+    /**
+     * Locates an element by matching its HTML tag name.
+     *
+     * @param tagName The HTML tag name to look for.
+     * @return A By instance that locates elements using the name of the
+     * HTML tag.
+     */
+    public static By tagName(final String tagName) {
+        throwIfNull(tagName);
+        return new By() {
+            @Override
+            public WebElement findElement(WebElement element) {
+                return element.findElementByTagName(tagName);
+            }
+
+            @Override
+            public String toString() {
+                return "By.tagName: " + tagName;
+            }
+        };
+    }
+
+    /**
+     * Locates an element using an XPath expression.
+     *
+     * <p>When using XPath, be aware that this follows standard conventions: a
+     * search prefixed with "//" will search the entire document, not just the
+     * children of the current node. Use ".//" to limit your search to the
+     * children of this {@link android.webkit.webdriver.WebElement}.
+     *
+     * @param xpath The XPath expression to use.
+     * @return A By instance that locates elements using the given XPath.
+     */
+    public static By xpath(final String xpath) {
+        throwIfNull(xpath);
+        return new By() {
+            @Override
+            public WebElement findElement(WebElement element) {
+                return element.findElementByXPath(xpath);
+            }
+
+            @Override
+            public String toString() {
+                return "By.xpath: " + xpath;
+            }
+        };
+    }
+
+    private static void throwIfNull(String argument) {
+        if (argument == null) {
+            throw new IllegalArgumentException(
+                    "Cannot find elements with null locator.");
+        }
+    }
+}
diff --git a/core/java/android/webkit/webdriver/WebDriver.java b/core/java/android/webkit/webdriver/WebDriver.java
new file mode 100644
index 0000000..90e701f
--- /dev/null
+++ b/core/java/android/webkit/webdriver/WebDriver.java
@@ -0,0 +1,526 @@
+/*
+ * Copyright (C) 2011 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.webdriver;
+
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
+import com.android.internal.R;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.os.Handler;
+import android.os.Message;
+import android.webkit.WebView;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Drives a web application by controlling the WebView. This class
+ * provides a DOM-like API allowing to get information about the page,
+ * navigate, and interact with the web application. This is particularly useful
+ * for testing a web application.
+ *
+ * <p/>{@link android.webkit.webdriver.WebDriver} should be created in the main
+ * thread, and invoked from another thread. Here is a sample usage:
+ *
+ * public class WebDriverStubActivity extends Activity {
+ *   private WebDriver mDriver;
+ *
+ *   public void onCreate(Bundle savedInstanceState) {
+ *       super.onCreate(savedInstanceState);
+ *       WebView view = new WebView(this);
+ *       mDriver = new WebDriver(view);
+ *       setContentView(view);
+ *   }
+ *
+ *
+ *   public WebDriver getDriver() {
+ *       return mDriver;
+ *   }
+ *}
+ *
+ * public class WebDriverTest extends
+ *       ActivityInstrumentationTestCase2<WebDriverStubActivity>{
+ *   private WebDriver mDriver;
+ *
+ *   public WebDriverTest() {
+ *       super(WebDriverStubActivity.class);
+ *   }
+ *
+ *   protected void setUp() throws Exception {
+ *       super.setUp();
+ *       mDriver = getActivity().getDriver();
+ *   }
+ *
+ *   public void testGoogle() {
+ *       mDriver.get("http://google.com");
+ *       WebElement searchBox = mDriver.findElement(By.name("q"));
+ *       q.sendKeys("Cheese!");
+ *       q.submit();
+ *       assertTrue(mDriver.findElements(By.partialLinkText("Cheese")).size() > 0);
+ *   }
+ *}
+ *
+ * @hide
+ */
+public class WebDriver {
+    // Timeout for page load in milliseconds.
+    private static final int LOADING_TIMEOUT = 30000;
+    // Timeout for executing JavaScript in the WebView in milliseconds.
+    private static final int JS_EXECUTION_TIMEOUT = 10000;
+
+    // Commands posted to the handler
+    private static final int CMD_GET_URL = 1;
+    private static final int CMD_EXECUTE_SCRIPT = 2;
+
+    private static final String ELEMENT_KEY = "ELEMENT";
+    private static final String STATUS = "status";
+    private static final String VALUE = "value";
+
+    private static final long MAIN_THREAD = Thread.currentThread().getId();
+
+    // This is updated by a callabck from JavaScript when the result is ready.
+    private String mJsResult;
+
+    // Used for synchronization
+    private final Object mSyncObject;
+
+    // Updated when the command is done executing in the main thread.
+    private volatile boolean mCommandDone;
+
+    private WebView mWebView;
+
+    // This WebElement represents the object document.documentElement
+    private WebElement mDocumentElement;
+
+    // This Handler runs in the main UI thread.
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == CMD_GET_URL) {
+                final String url = (String) msg.obj;
+                mWebView.loadUrl(url);
+            } else if (msg.what == CMD_EXECUTE_SCRIPT) {
+                mWebView.loadUrl("javascript:" + (String) msg.obj);
+            }
+        }
+    };
+
+    /**
+     * Error codes from the WebDriver wire protocol
+     * http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes
+     */
+    private enum ErrorCode {
+        SUCCESS(0),
+        NO_SUCH_ELEMENT(7),
+        NO_SUCH_FRAME(8),
+        UNKNOWN_COMMAND(9),
+        UNSUPPORTED_OPERATION(9),  // Alias
+        STALE_ELEMENT_REFERENCE(10),
+        ELEMENT_NOT_VISISBLE(11),
+        INVALID_ELEMENT_STATE(12),
+        UNKNOWN_ERROR(13),
+        ELEMENT_NOT_SELECTABLE(15),
+        XPATH_LOOKUP_ERROR(19),
+        NO_SUCH_WINDOW(23),
+        INVALID_COOKIE_DOMAIN(24),
+        UNABLE_TO_SET_COOKIE(25),
+        MODAL_DIALOG_OPENED(26),
+        MODAL_DIALOG_OPEN(27),
+        SCRIPT_TIMEOUT(28);
+
+        private final int mCode;
+        private static ErrorCode[] values = ErrorCode.values();
+
+        ErrorCode(int code) {
+            this.mCode = code;
+        }
+
+        public int getCode() {
+            return mCode;
+        }
+
+        public static ErrorCode get(final int intValue) {
+            for (int i = 0; i < values.length; i++) {
+                if (values[i].getCode() == intValue) {
+                    return values[i];
+                }
+            }
+            throw new IllegalArgumentException(intValue
+                    + " does not map to any ErrorCode.");
+        }
+    }
+
+    public WebDriver(WebView webview) {
+        this.mWebView = webview;
+        if (mWebView == null) {
+            throw new IllegalArgumentException("WebView cannot be null");
+        }
+        if (!mWebView.getSettings().getJavaScriptEnabled()) {
+            throw new RuntimeException("Javascript is disabled in the WebView. "
+                    + "Enable it to use WebDriver");
+        }
+        shouldRunInMainThread(true);
+
+        mSyncObject = new Object();
+        this.mWebView = webview;
+        WebchromeClientWrapper chromeWrapper = new WebchromeClientWrapper(
+                webview.getWebChromeClient(), this);
+        mWebView.setWebChromeClient(chromeWrapper);
+        mDocumentElement = new WebElement(this, "");
+        mWebView.addJavascriptInterface(new JavascriptResultReady(),
+                "webdriver");
+    }
+
+    /**
+     * Loads a URL in the WebView. This function is blocking and will return
+     * when the page has finished loading.
+     *
+     * @param url The URL to load.
+     */
+    public void get(String url) {
+        executeCommand(CMD_GET_URL, url, LOADING_TIMEOUT);
+    }
+
+    /**
+     * @return The source page of the currently loaded page in WebView.
+     */
+    public String getPageSource() {
+        return (String) executeScript("return new XMLSerializer()."
+                + "serializeToString(document);");
+    }
+
+    /**
+     * Find the first {@link android.webkit.webdriver.WebElement} using the
+     * given method.
+     *
+     * @param by The locating mechanism to use.
+     * @return The first matching element on the current context.
+     * @throws {@link android.webkit.webdriver.WebElementNotFoundException} if
+     * no matching element was found.
+     */
+    public WebElement findElement(By by) {
+        return by.findElement(mDocumentElement);
+    }
+
+    /**
+     * Clears the WebView.
+     */
+    public void quit() {
+        mWebView.clearCache(true);
+        mWebView.clearFormData();
+        mWebView.clearHistory();
+        mWebView.clearSslPreferences();
+        mWebView.clearView();
+    }
+
+    /**
+     * Executes javascript in the context of the main frame.
+     *
+     * If the script has a return value the following happens:
+     * <ul>
+     * <li>For an HTML element, this method returns a WebElement</li>
+     * <li>For a decimal, a Double is returned</li>
+     * <li>For non-decimal number, a Long is returned</li>
+     * <li>For a boolean, a Boolean is returned</li>
+     * <li>For all other cases, a String is returned</li>
+     * <li>For an array, this returns a List<Object> with each object
+     * following the rules above.</li>
+     * <li>For an object literal this returns a Map<String, Object>. Note that
+     * Object literals keys can only be Strings. Non Strings keys will
+     * be filtered out.</li>
+     * </ul>
+     *
+     * <p> Arguments must be a number, a boolean, a string a WebElement or
+     * a list of any combination of the above. The arguments will be made
+     * available to the javascript via the "arguments" magic variable,
+     * as if the function was called via "Function.apply".
+     *
+     * @param script The JavaScript to execute.
+     * @param args The arguments to the script. Can be any of a number, boolean,
+     * string, WebElement or a List of those.
+     * @return A Boolean, Long, Double, String, WebElement, List or null.
+     */
+    public Object executeScript(final String script, final Object... args) {
+        String scriptArgs = "[" + convertToJsArgs(args) + "]";
+        String injectScriptJs = getResourceAsString(R.raw.execute_script_android);
+        return executeRawJavascript("(" + injectScriptJs +
+                ")(" + escapeAndQuote(script) + ", " + scriptArgs + ", true)");
+    }
+
+    /**
+     * Converts the arguments passed to a JavaScript friendly format.
+     *
+     * @param args The arguments to convert.
+     * @return Comma separated Strings containing the arguments.
+     */
+    /*package*/ String convertToJsArgs(final Object... args) {
+        StringBuilder toReturn = new StringBuilder();
+        int length = args.length;
+        for (int i = 0; i < length; i++) {
+            toReturn.append((i > 0) ? "," : "");
+            if (args[i] instanceof List<?>) {
+                toReturn.append("[");
+                List<Object> aList = (List<Object>) args[i];
+                for (int j = 0 ; j < aList.size(); j++) {
+                    String comma = ((j == 0) ? "" : ",");
+                    toReturn.append(comma + convertToJsArgs(aList.get(j)));
+                }
+                toReturn.append("]");
+            } else if (args[i] instanceof Map<?, ?>) {
+                Map<Object, Object> aMap = (Map<Object, Object>) args[i];
+                String toAdd = "{";
+                for (Object key: aMap.keySet()) {
+                    toAdd += key + ":"
+                            + convertToJsArgs(aMap.get(key)) + ",";
+                }
+                toReturn.append(toAdd.substring(0, toAdd.length() -1) + "}");
+            } else if (args[i] instanceof WebElement) {
+                // WebElement are represented in JavaScript by Objects as
+                // follow: {ELEMENT:"id"}
+                toReturn.append("{" + ELEMENT_KEY + ":\""
+                        + ((WebElement) args[i]).getId() + "\"}");
+            } else if (args[i] instanceof Number || args[i] instanceof Boolean) {
+                toReturn.append(String.valueOf(args[i]));
+            } else if (args[i] instanceof String) {
+                toReturn.append(escapeAndQuote((String) args[i]));
+            } else {
+                throw new IllegalArgumentException(
+                        "Javascript arguments can be "
+                            + "a Number, a Boolean, a String, a WebElement, "
+                            + "or a List or a Map of those. Got: "
+                            + ((args[i] == null) ? "null" : args[i].toString()));
+            }
+        }
+        return toReturn.toString();
+    }
+
+    /*package*/ Object executeRawJavascript(final String script) {
+        String result = executeCommand(CMD_EXECUTE_SCRIPT,
+                "window.webdriver.resultReady(" + script + ")",
+                JS_EXECUTION_TIMEOUT);
+        try {
+            JSONObject json = new JSONObject(result);
+            throwIfError(json);
+            Object value = json.get(VALUE);
+            return convertJsonToJavaObject(value);
+        } catch (JSONException e) {
+            throw new RuntimeException("Failed to parse JavaScript result: "
+                    + result.toString(), e);
+        }
+    }
+
+    /*package*/ String getResourceAsString(final int resourceId) {
+        InputStream is = mWebView.getResources().openRawResource(resourceId);
+        BufferedReader br = new BufferedReader(new InputStreamReader(is));
+        StringBuilder sb = new StringBuilder();
+        String line = null;
+        try {
+            while ((line = br.readLine()) != null) {
+                sb.append(line);
+            }
+            br.close();
+            is.close();
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to open JavaScript resource.", e);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Wraps the given string into quotes and escape existing quotes
+     * and backslashes.
+     * "foo" -> "\"foo\""
+     * "foo\"" -> "\"foo\\\"\""
+     * "fo\o" -> "\"fo\\o\""
+     *
+     * @param toWrap The String to wrap in quotes
+     * @return a String wrapping the original String in quotes
+     */
+    private static String escapeAndQuote(final String toWrap) {
+        StringBuilder toReturn = new StringBuilder("\"");
+        for (int i = 0; i < toWrap.length(); i++) {
+            char c = toWrap.charAt(i);
+            if (c == '\"') {
+                toReturn.append("\\\"");
+            } else if (c == '\\') {
+                toReturn.append("\\\\");
+            } else {
+                toReturn.append(c);
+            }
+        }
+        toReturn.append("\"");
+        return toReturn.toString();
+    }
+
+    private Object convertJsonToJavaObject(final Object toConvert) {
+        try {
+            if (toConvert == null
+                    || toConvert.equals(null)
+                    || "undefined".equals(toConvert)
+                    || "null".equals(toConvert)) {
+                return null;
+            } else if (toConvert instanceof Boolean) {
+                return toConvert;
+            } else if (toConvert instanceof Double
+                    || toConvert instanceof Float) {
+                return Double.valueOf(String.valueOf(toConvert));
+            } else if (toConvert instanceof Integer
+                    || toConvert instanceof Long) {
+              return Long.valueOf(String.valueOf(toConvert));
+            } else if (toConvert instanceof JSONArray) { // List
+                return convertJsonArrayToList((JSONArray) toConvert);
+            } else if (toConvert instanceof JSONObject) { // Map or WebElment
+                JSONObject map = (JSONObject) toConvert;
+                if (map.opt(ELEMENT_KEY) != null) { // WebElement
+                    return new WebElement(this, (String) map.get(ELEMENT_KEY));
+                } else { // Map
+                    return convertJsonObjectToMap(map);
+                }
+            } else {
+                return toConvert.toString();
+            }
+        } catch (JSONException e) {
+            throw new RuntimeException("Failed to parse JavaScript result: "
+                    + toConvert.toString(), e);
+        }
+    }
+
+    private List<Object> convertJsonArrayToList(final JSONArray json) {
+        List<Object> toReturn = Lists.newArrayList();
+        for (int i = 0; i < json.length(); i++) {
+            try {
+                toReturn.add(convertJsonToJavaObject(json.get(i)));
+            } catch (JSONException e) {
+                throw new RuntimeException("Failed to parse JSON: "
+                        + json.toString(), e);
+            }
+        }
+        return toReturn;
+    }
+
+    private Map<Object, Object> convertJsonObjectToMap(final JSONObject json) {
+        Map<Object, Object> toReturn = Maps.newHashMap();
+        for (Iterator it = json.keys(); it.hasNext();) {
+            String key = (String) it.next();
+            try {
+                Object value = json.get(key);
+                toReturn.put(convertJsonToJavaObject(key),
+                        convertJsonToJavaObject(value));
+            } catch (JSONException e) {
+                throw new RuntimeException("Failed to parse JSON:"
+                        + json.toString(), e);
+            }
+        }
+        return toReturn;
+    }
+
+    private void throwIfError(final JSONObject jsonObject) {
+        ErrorCode status;
+        String errorMsg;
+        try {
+            status = ErrorCode.get((Integer) jsonObject.get(STATUS));
+            errorMsg  = String.valueOf(jsonObject.get(VALUE));
+        } catch (JSONException e) {
+            throw new RuntimeException("Failed to parse JSON Object: "
+                    + jsonObject, e);
+        }
+        switch (status) {
+            case SUCCESS:
+                return;
+            case NO_SUCH_ELEMENT:
+                throw new WebElementNotFoundException("Could not find "
+                        + "WebElement.");
+            case STALE_ELEMENT_REFERENCE:
+                throw new WebElementStaleException("WebElement is stale.");
+            default:
+                throw new RuntimeException("Error: " + errorMsg);
+        }
+    }
+
+    private void shouldRunInMainThread(boolean value) {
+        assert (value == (MAIN_THREAD == Thread.currentThread().getId()));
+    }
+
+    /**
+     * Interface called from JavaScript when the result is ready.
+     */
+    private class JavascriptResultReady {
+
+        /**
+         * A callback from JavaScript to Java that passes the result as a
+         * parameter. This method is available from the WebView's
+         * JavaScript DOM as window.webdriver.resultReady().
+         *
+         * @param result The result that should be sent to Java from Javascript.
+         */
+        public void resultReady(final String result) {
+            synchronized (mSyncObject) {
+                mJsResult = result;
+                mCommandDone = true;
+                mSyncObject.notify();
+            }
+        }
+    }
+
+    /* package */ void notifyCommandDone() {
+        synchronized (mSyncObject) {
+            mCommandDone = true;
+            mSyncObject.notify();
+        }
+    }
+
+    /**
+     * Executes the given command by posting a message to mHandler. This thread
+     * will block until the command which runs in the main thread is done.
+     *
+     * @param command The command to run.
+     * @param arg The argument for that command.
+     * @param timeout A timeout in milliseconds.
+     */
+    private String executeCommand(int command, final Object arg, long timeout) {
+        shouldRunInMainThread(false);
+        synchronized (mSyncObject) {
+            mCommandDone = false;
+            Message msg = mHandler.obtainMessage(command);
+            msg.obj = arg;
+            mHandler.sendMessage(msg);
+
+            long end = System.currentTimeMillis() + timeout;
+            while (!mCommandDone) {
+                if (System.currentTimeMillis() >= end) {
+                    throw new RuntimeException("Timeout executing command.");
+                }
+                try {
+                    mSyncObject.wait(timeout);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+        return mJsResult;
+    }
+}
diff --git a/core/java/android/webkit/webdriver/WebElement.java b/core/java/android/webkit/webdriver/WebElement.java
new file mode 100644
index 0000000..384d55f
--- /dev/null
+++ b/core/java/android/webkit/webdriver/WebElement.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2011 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.webdriver;
+
+import com.android.internal.R;
+
+/**
+ * Represents an HTML element. Typically most interactions with a web page
+ * will be performed through this class.
+ *
+ * @hide
+ */
+public class WebElement {
+    private final String mId;
+    private final WebDriver mDriver;
+
+    /**
+     * Package constructor to prevent clients from creating a new WebElement
+     * instance.
+     *
+     * <p> A WebElement represents an HTML element on the page.
+     * The corresponding HTML element is stored in a JS cache in the page
+     * that can be accessed through JavaScript using "bot.inject.cache".
+     *
+     * @param driver The WebDriver instance to use.
+     * @param id The index of the HTML element in the JavaSctipt cache. Pass
+     * an empty String to indicate that this is the
+     * document.documentElement object.
+     */
+    /* Package */ WebElement(final WebDriver driver, final String id) {
+        this.mId = id;
+        this.mDriver = driver;
+    }
+
+    /**
+     * Finds the first {@link android.webkit.webdriver.WebElement} using the
+     * given method.
+     *
+     * @param by The locating mechanism to use.
+     * @return The first matching element on the current context.
+     */
+    public WebElement findElement(final By by) {
+        return by.findElement(this);
+    }
+
+    /**
+     * Gets the visisble (i.e. not hidden by CSS) innerText of this element,
+     * inlcuding sub-elements.
+     *
+     * @return the innerText of this element.
+     * @throws {@link android.webkit.webdriver.WebElementStaleException} if this
+     * element is stale, i.e. not on the current DOM.
+     */
+    public String getText() {
+        String getText = mDriver.getResourceAsString(R.raw.get_text_android);
+        if (mId.equals("")) {
+            return null;
+        }
+        return (String) executeAtom(getText, this);
+    }
+
+    /*package*/ String getId() {
+        return mId;
+    }
+
+    /* package */ WebElement findElementById(final String locator) {
+        return findElement("id", locator);
+    }
+
+    /* package */ WebElement findElementByLinkText(final String linkText) {
+        return findElement("linkText", linkText);
+    }
+
+    /* package */ WebElement findElementByPartialLinkText(
+            final String linkText) {
+        return findElement("partialLinkText", linkText);
+    }
+
+    /* package */ WebElement findElementByName(final String name) {
+        return findElement("name", name);
+    }
+
+    /* package */ WebElement findElementByClassName(final String className) {
+        return findElement("className", className);
+    }
+
+    /* package */ WebElement findElementByCss(final String css) {
+        return findElement("css", css);
+    }
+
+    /* package */ WebElement findElementByTagName(final String tagName) {
+        return findElement("tagName", tagName);
+    }
+
+    /* package */ WebElement findElementByXPath(final String xpath) {
+        return findElement("xpath", xpath);
+    }
+
+    private Object executeAtom(final String atom, final Object... args) {
+        String scriptArgs = mDriver.convertToJsArgs(args);
+        return mDriver.executeRawJavascript("(" +
+                atom + ")(" + scriptArgs + ")");
+    }
+
+    private WebElement findElement(String strategy, String locator) {
+        String findElement = mDriver.getResourceAsString(
+                R.raw.find_element_android);
+        WebElement el;
+        if (mId.equals("")) {
+            // Use default as root which is the document object
+            el = (WebElement) executeAtom(findElement, strategy, locator);
+        } else {
+            // Use this as root
+            el = (WebElement) executeAtom(findElement, strategy, locator, this);
+        }
+        if (el == null) {
+            throw new WebElementNotFoundException("Could not find element "
+                    + "with " + strategy + ": " + locator);
+        }
+        return el;
+    }
+}
diff --git a/core/java/android/webkit/webdriver/WebElementNotFoundException.java b/core/java/android/webkit/webdriver/WebElementNotFoundException.java
new file mode 100644
index 0000000..e66d279
--- /dev/null
+++ b/core/java/android/webkit/webdriver/WebElementNotFoundException.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011 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.webdriver;
+
+/**
+ * Thrown when a {@link android.webkit.webdriver.WebElement} is not found in the
+ * DOM of the page.
+ * @hide
+ */
+public class WebElementNotFoundException extends RuntimeException {
+
+    public WebElementNotFoundException() {
+        super();
+    }
+
+    public WebElementNotFoundException(String reason) {
+        super(reason);
+    }
+
+    public WebElementNotFoundException(String reason, Throwable cause) {
+        super(reason, cause);
+    }
+
+    public WebElementNotFoundException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/core/java/android/webkit/webdriver/WebElementStaleException.java b/core/java/android/webkit/webdriver/WebElementStaleException.java
new file mode 100644
index 0000000..c59e7945
--- /dev/null
+++ b/core/java/android/webkit/webdriver/WebElementStaleException.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2011 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.webdriver;
+
+/**
+ * Thrown when trying to access a {@link android.webkit.webdriver.WebElement}
+ * that is stale. This mean that the {@link android.webkit.webdriver.WebElement}
+ * is no longer present on the DOM of the page.
+ * @hide
+ */
+public class WebElementStaleException extends RuntimeException {
+
+    public WebElementStaleException() {
+        super();
+    }
+
+    public  WebElementStaleException(String reason) {
+        super(reason);
+    }
+
+    public WebElementStaleException(String reason, Throwable cause) {
+        super(reason, cause);
+    }
+
+    public WebElementStaleException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/core/java/android/webkit/webdriver/WebchromeClientWrapper.java b/core/java/android/webkit/webdriver/WebchromeClientWrapper.java
new file mode 100644
index 0000000..ea33c5b
--- /dev/null
+++ b/core/java/android/webkit/webdriver/WebchromeClientWrapper.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2011 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.webdriver;
+
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Message;
+import android.view.View;
+import android.webkit.ConsoleMessage;
+import android.webkit.GeolocationPermissions;
+import android.webkit.JsPromptResult;
+import android.webkit.JsResult;
+import android.webkit.ValueCallback;
+import android.webkit.WebChromeClient;
+import android.webkit.WebStorage;
+import android.webkit.WebView;
+
+/* package */ class WebchromeClientWrapper extends WebChromeClient {
+
+    private final WebChromeClient mDelegate;
+    private final WebDriver mDriver;
+
+    public WebchromeClientWrapper(WebChromeClient delegate, WebDriver driver) {
+        if (delegate == null) {
+            this.mDelegate = new WebChromeClient();
+        } else {
+            this.mDelegate = delegate;
+        }
+        this.mDriver = driver;
+    }
+
+    @Override
+    public void onProgressChanged(WebView view, int newProgress) {
+        if (newProgress == 100) {
+            mDriver.notifyCommandDone();
+        }
+        mDelegate.onProgressChanged(view, newProgress);
+    }
+
+    @Override
+    public void onReceivedTitle(WebView view, String title) {
+        mDelegate.onReceivedTitle(view, title);
+    }
+
+    @Override
+    public void onReceivedIcon(WebView view, Bitmap icon) {
+        mDelegate.onReceivedIcon(view, icon);
+    }
+
+    @Override
+    public void onReceivedTouchIconUrl(WebView view, String url,
+            boolean precomposed) {
+        mDelegate.onReceivedTouchIconUrl(view, url, precomposed);
+    }
+
+    @Override
+    public void onShowCustomView(View view,
+            CustomViewCallback callback) {
+        mDelegate.onShowCustomView(view, callback);
+    }
+
+    @Override
+    public void onHideCustomView() {
+        mDelegate.onHideCustomView();
+    }
+
+    @Override
+    public boolean onCreateWindow(WebView view, boolean dialog,
+            boolean userGesture, Message resultMsg) {
+        return mDelegate.onCreateWindow(view, dialog, userGesture, resultMsg);
+    }
+
+    @Override
+    public void onRequestFocus(WebView view) {
+        mDelegate.onRequestFocus(view);
+    }
+
+    @Override
+    public void onCloseWindow(WebView window) {
+        mDelegate.onCloseWindow(window);
+    }
+
+    @Override
+    public boolean onJsAlert(WebView view, String url, String message,
+            JsResult result) {
+        return mDelegate.onJsAlert(view, url, message, result);
+    }
+
+    @Override
+    public boolean onJsConfirm(WebView view, String url, String message,
+            JsResult result) {
+        return mDelegate.onJsConfirm(view, url, message, result);
+    }
+
+    @Override
+    public boolean onJsPrompt(WebView view, String url, String message,
+            String defaultValue, JsPromptResult result) {
+        return mDelegate.onJsPrompt(view, url, message, defaultValue, result);
+    }
+
+    @Override
+    public boolean onJsBeforeUnload(WebView view, String url, String message,
+            JsResult result) {
+        return mDelegate.onJsBeforeUnload(view, url, message, result);
+    }
+
+    @Override
+    public void onExceededDatabaseQuota(String url, String databaseIdentifier,
+            long currentQuota, long estimatedSize, long totalUsedQuota,
+            WebStorage.QuotaUpdater quotaUpdater) {
+        mDelegate.onExceededDatabaseQuota(url, databaseIdentifier, currentQuota,
+                estimatedSize, totalUsedQuota, quotaUpdater);
+    }
+
+    @Override
+    public void onReachedMaxAppCacheSize(long spaceNeeded, long totalUsedQuota,
+            WebStorage.QuotaUpdater quotaUpdater) {
+        mDelegate.onReachedMaxAppCacheSize(spaceNeeded, totalUsedQuota,
+                quotaUpdater);
+    }
+
+    @Override
+    public void onGeolocationPermissionsShowPrompt(String origin,
+            GeolocationPermissions.Callback callback) {
+        mDelegate.onGeolocationPermissionsShowPrompt(origin, callback);
+    }
+
+    @Override
+    public void onGeolocationPermissionsHidePrompt() {
+        mDelegate.onGeolocationPermissionsHidePrompt();
+    }
+
+    @Override
+    public boolean onJsTimeout() {
+        return mDelegate.onJsTimeout();
+    }
+
+    @Override
+    public void onConsoleMessage(String message, int lineNumber,
+            String sourceID) {
+        mDelegate.onConsoleMessage(message, lineNumber, sourceID);
+    }
+
+    @Override
+    public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
+        return mDelegate.onConsoleMessage(consoleMessage);
+    }
+
+    @Override
+    public Bitmap getDefaultVideoPoster() {
+        return mDelegate.getDefaultVideoPoster();
+    }
+
+    @Override
+    public View getVideoLoadingProgressView() {
+        return mDelegate.getVideoLoadingProgressView();
+    }
+
+    @Override
+    public void getVisitedHistory(ValueCallback<String[]> callback) {
+        mDelegate.getVisitedHistory(callback);
+    }
+
+    @Override
+    public void openFileChooser(ValueCallback<Uri> uploadFile,
+            String acceptType) {
+        mDelegate.openFileChooser(uploadFile, acceptType);
+    }
+
+    @Override
+    public void setInstallableWebApp() {
+        mDelegate.setInstallableWebApp();
+    }
+
+    @Override
+    public void setupAutoFill(Message msg) {
+        mDelegate.setupAutoFill(msg);
+    }
+}
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index f659ead..590a768 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -39,7 +39,7 @@
  * Children are drawn in a stack, with the most recently added child on top.
  * The size of the frame layout is the size of its largest child (plus padding), visible
  * or not (if the FrameLayout's parent permits). Views that are GONE are used for sizing
- * only if {@link #setMeasureAllChildren(boolean) setConsiderGoneChildrenWhenMeasuring()}
+ * only if {@link #setMeasureAllChildren(boolean) setMeasureAllChildren()}
  * is set to true.
  *
  * @attr ref android.R.styleable#FrameLayout_foreground
@@ -566,4 +566,3 @@
         }
     }
 }
-
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index d86504d..7cf33fc 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -36,6 +36,7 @@
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
 import android.util.Log;
+import android.util.LogWriter;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
 import android.util.Slog;
@@ -70,7 +71,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 54;
+    private static final int VERSION = 60;
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS = 2000;
@@ -154,11 +155,27 @@
     boolean mHaveBatteryLevel = false;
     boolean mRecordingHistory = true;
     int mNumHistoryItems;
+
+    static final int MAX_HISTORY_BUFFER = 128*1024; // 128KB
+    static final int MAX_MAX_HISTORY_BUFFER = 144*1024; // 144KB
+    final Parcel mHistoryBuffer = Parcel.obtain();
+    final HistoryItem mHistoryLastWritten = new HistoryItem();
+    final HistoryItem mHistoryLastLastWritten = new HistoryItem();
+    final HistoryItem mHistoryReadTmp = new HistoryItem();
+    int mHistoryBufferLastPos = -1;
+    boolean mHistoryOverflow = false;
+    long mLastHistoryTime = 0;
+
+    final HistoryItem mHistoryCur = new HistoryItem();
+
     HistoryItem mHistory;
     HistoryItem mHistoryEnd;
     HistoryItem mHistoryLastEnd;
     HistoryItem mHistoryCache;
-    final HistoryItem mHistoryCur = new HistoryItem();
+
+    private HistoryItem mHistoryIterator;
+    private boolean mReadOverflow;
+    private boolean mIteratingHistory;
 
     int mStartCount;
 
@@ -1189,9 +1206,84 @@
         mBtHeadset = headset;
     }
 
+    int mChangedBufferStates = 0;
+
+    void addHistoryBufferLocked(long curTime) {
+        if (!mHaveBatteryLevel || !mRecordingHistory) {
+            return;
+        }
+
+        final long timeDiff = (mHistoryBaseTime+curTime) - mHistoryLastWritten.time;
+        if (mHistoryBufferLastPos >= 0 && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE
+                && timeDiff < 2000
+                && ((mHistoryLastWritten.states^mHistoryCur.states)&mChangedBufferStates) == 0) {
+            // If the current is the same as the one before, then we no
+            // longer need the entry.
+            mHistoryBuffer.setDataSize(mHistoryBufferLastPos);
+            mHistoryBuffer.setDataPosition(mHistoryBufferLastPos);
+            mHistoryBufferLastPos = -1;
+            if (mHistoryLastLastWritten.cmd == HistoryItem.CMD_UPDATE
+                    && timeDiff < 500 && mHistoryLastLastWritten.same(mHistoryCur)) {
+                // If this results in us returning to the state written
+                // prior to the last one, then we can just delete the last
+                // written one and drop the new one.  Nothing more to do.
+                mHistoryLastWritten.setTo(mHistoryLastLastWritten);
+                mHistoryLastLastWritten.cmd = HistoryItem.CMD_NULL;
+                return;
+            }
+            mChangedBufferStates |= mHistoryLastWritten.states^mHistoryCur.states;
+            curTime = mHistoryLastWritten.time - mHistoryBaseTime;
+            mHistoryLastWritten.setTo(mHistoryLastLastWritten);
+        } else {
+            mChangedBufferStates = 0;
+        }
+
+        final int dataSize = mHistoryBuffer.dataSize();
+        if (dataSize >= MAX_HISTORY_BUFFER) {
+            if (!mHistoryOverflow) {
+                mHistoryOverflow = true;
+                addHistoryBufferLocked(curTime, HistoryItem.CMD_OVERFLOW);
+            }
+
+            // Once we've reached the maximum number of items, we only
+            // record changes to the battery level and the most interesting states.
+            // Once we've reached the maximum maximum number of items, we only
+            // record changes to the battery level.
+            if (mHistoryLastWritten.batteryLevel == mHistoryCur.batteryLevel &&
+                    (dataSize >= MAX_MAX_HISTORY_BUFFER
+                            || ((mHistoryEnd.states^mHistoryCur.states)
+                                    & HistoryItem.MOST_INTERESTING_STATES) == 0)) {
+                return;
+            }
+        }
+
+        addHistoryBufferLocked(curTime, HistoryItem.CMD_UPDATE);
+    }
+
+    void addHistoryBufferLocked(long curTime, byte cmd) {
+        int origPos = 0;
+        if (mIteratingHistory) {
+            origPos = mHistoryBuffer.dataPosition();
+            mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
+        }
+        mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
+        mHistoryLastLastWritten.setTo(mHistoryLastWritten);
+        mHistoryLastWritten.setTo(mHistoryBaseTime + curTime, cmd, mHistoryCur);
+        mHistoryLastWritten.writeDelta(mHistoryBuffer, mHistoryLastLastWritten);
+        mLastHistoryTime = curTime;
+        if (DEBUG_HISTORY) Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
+                + " now " + mHistoryBuffer.dataPosition()
+                + " size is now " + mHistoryBuffer.dataSize());
+        if (mIteratingHistory) {
+            mHistoryBuffer.setDataPosition(origPos);
+        }
+    }
+
     int mChangedStates = 0;
 
     void addHistoryRecordLocked(long curTime) {
+        addHistoryBufferLocked(curTime);
+
         if (!mHaveBatteryLevel || !mRecordingHistory) {
             return;
         }
@@ -1206,6 +1298,7 @@
             // If the current is the same as the one before, then we no
             // longer need the entry.
             if (mHistoryLastEnd != null && mHistoryLastEnd.cmd == HistoryItem.CMD_UPDATE
+                    && (mHistoryBaseTime+curTime) < (mHistoryEnd.time+500)
                     && mHistoryLastEnd.same(mHistoryCur)) {
                 mHistoryLastEnd.next = null;
                 mHistoryEnd.next = mHistoryCache;
@@ -1268,6 +1361,7 @@
     }
 
     void clearHistoryLocked() {
+        if (DEBUG_HISTORY) Slog.i(TAG, "********** CLEARING HISTORY!");
         if (mHistory != null) {
             mHistoryEnd.next = mHistoryCache;
             mHistoryCache = mHistory;
@@ -1275,6 +1369,15 @@
         }
         mNumHistoryItems = 0;
         mHistoryBaseTime = 0;
+        mLastHistoryTime = 0;
+
+        mHistoryBuffer.setDataSize(0);
+        mHistoryBuffer.setDataPosition(0);
+        mHistoryBuffer.setDataCapacity(MAX_HISTORY_BUFFER/2);
+        mHistoryLastLastWritten.cmd = HistoryItem.CMD_NULL;
+        mHistoryLastWritten.cmd = HistoryItem.CMD_NULL;
+        mHistoryBufferLastPos = -1;
+        mHistoryOverflow = false;
     }
 
     public void doUnplugLocked(long batteryUptime, long batteryRealtime) {
@@ -3910,11 +4013,13 @@
         mDischargeUnplugLevel = 0;
         mDischargeCurrentLevel = 0;
         initDischarge();
+        clearHistoryLocked();
     }
 
     public BatteryStatsImpl(Parcel p) {
         mFile = null;
         mHandler = null;
+        clearHistoryLocked();
         readFromParcel(p);
     }
 
@@ -3932,25 +4037,84 @@
         }
     }
 
-    private HistoryItem mHistoryIterator;
-
-    public boolean startIteratingHistoryLocked() {
+    @Override
+    public boolean startIteratingOldHistoryLocked() {
+        if (DEBUG_HISTORY) Slog.i(TAG, "ITERATING: buff size=" + mHistoryBuffer.dataSize()
+                + " pos=" + mHistoryBuffer.dataPosition());
+        mHistoryBuffer.setDataPosition(0);
+        mHistoryReadTmp.clear();
+        mReadOverflow = false;
+        mIteratingHistory = true;
         return (mHistoryIterator = mHistory) != null;
     }
 
-    public boolean getNextHistoryLocked(HistoryItem out) {
+    @Override
+    public boolean getNextOldHistoryLocked(HistoryItem out) {
+        boolean end = mHistoryBuffer.dataPosition() >= mHistoryBuffer.dataSize();
+        if (!end) {
+            mHistoryReadTmp.readDelta(mHistoryBuffer);
+            mReadOverflow |= mHistoryReadTmp.cmd == HistoryItem.CMD_OVERFLOW;
+        }
         HistoryItem cur = mHistoryIterator;
         if (cur == null) {
+            if (!mReadOverflow && !end) {
+                Slog.w(TAG, "Old history ends before new history!");
+            }
             return false;
         }
         out.setTo(cur);
         mHistoryIterator = cur.next;
+        if (!mReadOverflow) {
+            if (end) {
+                Slog.w(TAG, "New history ends before old history!");
+            } else if (!out.same(mHistoryReadTmp)) {
+                long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
+                PrintWriter pw = new PrintWriter(new LogWriter(android.util.Log.WARN, TAG));
+                pw.println("Histories differ!");
+                pw.println("Old history:");
+                (new HistoryPrinter()).printNextItem(pw, out, now);
+                pw.println("New history:");
+                (new HistoryPrinter()).printNextItem(pw, mHistoryReadTmp, now);
+            }
+        }
         return true;
     }
 
     @Override
-    public HistoryItem getHistory() {
-        return mHistory;
+    public void finishIteratingOldHistoryLocked() {
+        mIteratingHistory = false;
+        mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
+    }
+
+    @Override
+    public boolean startIteratingHistoryLocked() {
+        if (DEBUG_HISTORY) Slog.i(TAG, "ITERATING: buff size=" + mHistoryBuffer.dataSize()
+                + " pos=" + mHistoryBuffer.dataPosition());
+        mHistoryBuffer.setDataPosition(0);
+        mReadOverflow = false;
+        mIteratingHistory = true;
+        return mHistoryBuffer.dataSize() > 0;
+    }
+
+    @Override
+    public boolean getNextHistoryLocked(HistoryItem out) {
+        final int pos = mHistoryBuffer.dataPosition();
+        if (pos == 0) {
+            out.clear();
+        }
+        boolean end = pos >= mHistoryBuffer.dataSize();
+        if (end) {
+            return false;
+        }
+
+        out.readDelta(mHistoryBuffer);
+        return true;
+    }
+
+    @Override
+    public void finishIteratingHistoryLocked() {
+        mIteratingHistory = false;
+        mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
     }
 
     @Override
@@ -4697,7 +4861,9 @@
             Slog.e("BatteryStats", "Error reading battery statistics", e);
         }
 
-        addHistoryRecordLocked(SystemClock.elapsedRealtime(), HistoryItem.CMD_START);
+        long now = SystemClock.elapsedRealtime();
+        addHistoryRecordLocked(now, HistoryItem.CMD_START);
+        addHistoryBufferLocked(now, HistoryItem.CMD_START);
     }
 
     public int describeContents() {
@@ -4705,30 +4871,54 @@
     }
 
     void readHistory(Parcel in) {
-        mHistory = mHistoryEnd = mHistoryCache = null;
-        mHistoryBaseTime = 0;
-        long time;
-        while ((time=in.readLong()) >= 0) {
-            HistoryItem rec = new HistoryItem(time, in);
-            addHistoryRecordLocked(rec);
-            if (rec.time > mHistoryBaseTime) {
-                mHistoryBaseTime = rec.time;
-            }
+        mHistoryBaseTime = in.readLong();
+
+        mHistoryBuffer.setDataSize(0);
+        mHistoryBuffer.setDataPosition(0);
+
+        int bufSize = in.readInt();
+        int curPos = in.dataPosition();
+        if (bufSize >= (MAX_MAX_HISTORY_BUFFER*3)) {
+            Slog.w(TAG, "File corrupt: history data buffer too large " + bufSize);
+        } else if ((bufSize&~3) != bufSize) {
+            Slog.w(TAG, "File corrupt: history data buffer not aligned " + bufSize);
+        } else {
+            if (DEBUG_HISTORY) Slog.i(TAG, "***************** READING NEW HISTORY: " + bufSize
+                    + " bytes at " + curPos);
+            mHistoryBuffer.appendFrom(in, curPos, bufSize);
+            in.setDataPosition(curPos + bufSize);
         }
 
-        long oldnow = SystemClock.elapsedRealtime() - (5*60*100);
+        long oldnow = SystemClock.elapsedRealtime() - (5*60*1000);
         if (oldnow > 0) {
             // If the system process has restarted, but not the entire
             // system, then the mHistoryBaseTime already accounts for
             // much of the elapsed time.  We thus want to adjust it back,
             // to avoid large gaps in the data.  We determine we are
             // in this case by arbitrarily saying it is so if at this
-            // point in boot the elapsed time is already more than 5 seconds.
+            // point in boot the elapsed time is already more than 5 minutes.
             mHistoryBaseTime -= oldnow;
         }
     }
 
+    void readOldHistory(Parcel in) {
+        mHistory = mHistoryEnd = mHistoryCache = null;
+        long time;
+        while ((time=in.readLong()) >= 0) {
+            HistoryItem rec = new HistoryItem(time, in);
+            addHistoryRecordLocked(rec);
+        }
+    }
+
     void writeHistory(Parcel out) {
+        out.writeLong(mLastHistoryTime);
+        out.writeInt(mHistoryBuffer.dataSize());
+        if (DEBUG_HISTORY) Slog.i(TAG, "***************** WRITING HISTORY: "
+                + mHistoryBuffer.dataSize() + " bytes at " + out.dataPosition());
+        out.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
+    }
+
+    void writeOldHistory(Parcel out) {
         HistoryItem rec = mHistory;
         while (rec != null) {
             if (rec.time >= 0) rec.writeToParcel(out, 0);
@@ -4746,6 +4936,7 @@
         }
 
         readHistory(in);
+        readOldHistory(in);
 
         mStartCount = in.readInt();
         mBatteryUptime = in.readLong();
@@ -4935,6 +5126,9 @@
      * @param out the Parcel to be written to.
      */
     public void writeSummaryToParcel(Parcel out) {
+        // Need to update with current kernel wake lock counts.
+        updateKernelWakelocksLocked();
+
         final long NOW_SYS = SystemClock.uptimeMillis() * 1000;
         final long NOWREAL_SYS = SystemClock.elapsedRealtime() * 1000;
         final long NOW = getBatteryUptimeLocked(NOW_SYS);
@@ -4943,6 +5137,7 @@
         out.writeInt(VERSION);
 
         writeHistory(out);
+        writeOldHistory(out);
 
         out.writeInt(mStartCount);
         out.writeLong(computeBatteryUptime(NOW_SYS, STATS_SINCE_CHARGED));
@@ -5256,6 +5451,9 @@
 
     @SuppressWarnings("unused")
     void writeToParcelLocked(Parcel out, boolean inclUids, int flags) {
+        // Need to update with current kernel wake lock counts.
+        updateKernelWakelocksLocked();
+
         final long uSecUptime = SystemClock.uptimeMillis() * 1000;
         final long uSecRealtime = SystemClock.elapsedRealtime() * 1000;
         final long batteryUptime = getBatteryUptimeLocked(uSecUptime);
@@ -5358,6 +5556,11 @@
         }
     };
 
+    public void prepareForDumpLocked() {
+        // Need to retrieve current kernel wake lock stats before printing.
+        updateKernelWakelocksLocked();
+    }
+
     public void dumpLocked(PrintWriter pw) {
         if (DEBUG) {
             Printer pr = new PrintWriterPrinter(pw);
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 611d987..4ffa4e1 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -33,6 +33,7 @@
     List<InputMethodInfo> getEnabledInputMethodList();
     List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in InputMethodInfo imi,
             boolean allowsImplicitlySelectedSubtypes);
+    InputMethodSubtype getLastInputMethodSubtype();
     // TODO: We should change the return type from List to List<Parcelable>
     // Currently there is a bug that aidl doesn't accept List<Parcelable>
     List getShortcutInputMethodsAndSubtypes();
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index f29d83e..b628b9d 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -1,19 +1,18 @@
-/* //device/libs/android_runtime/AndroidRuntime.cpp
-**
-** Copyright 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.
-*/
+/*
+ * 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.
+ */
 
 #define LOG_TAG "AndroidRuntime"
 //#define LOG_NDEBUG 0
@@ -212,7 +211,7 @@
     if (value != NULL && strcmp(value, "true") == 0) {
         return 1;
     }
-    
+
     return 0;
 }
 
@@ -227,7 +226,7 @@
     { "isComputerOn", "()I",
         (void*) com_android_internal_os_RuntimeInit_isComputerOn },
     { "turnComputerOn", "()V",
-        (void*) com_android_internal_os_RuntimeInit_turnComputerOn },    
+        (void*) com_android_internal_os_RuntimeInit_turnComputerOn },
     { "getQwertyKeyboard", "()I",
         (void*) com_android_internal_os_RuntimeInit_getQwertyKeyboard },
 };
@@ -278,51 +277,16 @@
     return jniRegisterNativeMethods(env, className, gMethods, numMethods);
 }
 
-/*
- * Call a static Java Programming Language function that takes no arguments and returns void.
- */
-status_t AndroidRuntime::callStatic(const char* className, const char* methodName)
+status_t AndroidRuntime::callMain(const char* className,
+    jclass clazz, int argc, const char* const argv[])
 {
     JNIEnv* env;
-    jclass clazz;
-    jmethodID methodId;
-
-    env = getJNIEnv();
-    if (env == NULL)
-        return UNKNOWN_ERROR;
-
-    clazz = findClass(env, className);
-    if (clazz == NULL) {
-        LOGE("ERROR: could not find class '%s'\n", className);
-        return UNKNOWN_ERROR;
-    }
-    methodId = env->GetStaticMethodID(clazz, methodName, "()V");
-    if (methodId == NULL) {
-        LOGE("ERROR: could not find method %s.%s\n", className, methodName);
-        return UNKNOWN_ERROR;
-    }
-
-    env->CallStaticVoidMethod(clazz, methodId);
-
-    return NO_ERROR;
-}
-
-status_t AndroidRuntime::callMain(
-    const char* className, int argc, const char* const argv[])
-{
-    JNIEnv* env;
-    jclass clazz;
     jmethodID methodId;
 
     LOGD("Calling main entry %s", className);
 
     env = getJNIEnv();
-    if (env == NULL)
-        return UNKNOWN_ERROR;
-
-    clazz = findClass(env, className);
-    if (clazz == NULL) {
-        LOGE("ERROR: could not find class '%s'\n", className);
+    if (clazz == NULL || env == NULL) {
         return UNKNOWN_ERROR;
     }
 
@@ -352,70 +316,6 @@
 }
 
 /*
- * Find the named class.
- */
-jclass AndroidRuntime::findClass(JNIEnv* env, const char* className)
-{
-    if (env->ExceptionCheck()) {
-        LOGE("ERROR: exception pending on entry to findClass()");
-        return NULL;
-    }
-
-    /*
-     * This is a little awkward because the JNI FindClass call uses the
-     * class loader associated with the native method we're executing in.
-     * Because this native method is part of a "boot" class, JNI doesn't
-     * look for the class in CLASSPATH, which unfortunately is a likely
-     * location for it.  (Had we issued the FindClass call before calling
-     * into the VM -- at which point there isn't a native method frame on
-     * the stack -- the VM would have checked CLASSPATH.  We have to do
-     * this because we call into Java Programming Language code and
-     * bounce back out.)
-     *
-     * JNI lacks a "find class in a specific class loader" operation, so we
-     * have to do things the hard way.
-     */
-    jclass cls = NULL;
-
-    jclass javaLangClassLoader;
-    jmethodID getSystemClassLoader, loadClass;
-    jobject systemClassLoader;
-    jstring strClassName;
-
-    /* find the "system" class loader; none of this is expected to fail */
-    javaLangClassLoader = env->FindClass("java/lang/ClassLoader");
-    assert(javaLangClassLoader != NULL);
-    getSystemClassLoader = env->GetStaticMethodID(javaLangClassLoader,
-        "getSystemClassLoader", "()Ljava/lang/ClassLoader;");
-    loadClass = env->GetMethodID(javaLangClassLoader,
-        "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
-    assert(getSystemClassLoader != NULL && loadClass != NULL);
-    systemClassLoader = env->CallStaticObjectMethod(javaLangClassLoader,
-        getSystemClassLoader);
-    assert(systemClassLoader != NULL);
-
-    /* create an object for the class name string; alloc could fail */
-    strClassName = env->NewStringUTF(className);
-    if (env->ExceptionCheck()) {
-        LOGE("ERROR: unable to convert '%s' to string", className);
-        return NULL;
-    }
-    LOGV("system class loader is %p, loading %p (%s)",
-        systemClassLoader, strClassName, className);
-
-    /* try to find the named class */
-    cls = (jclass) env->CallObjectMethod(systemClassLoader, loadClass,
-                        strClassName);
-    if (env->ExceptionCheck()) {
-        LOGE("ERROR: unable to load class '%s' from %p",
-            className, systemClassLoader);
-        return NULL;
-    }
-
-    return cls;
-}
-
-/*
  * The VM calls this through the "exit" hook.
  */
 static void runtime_exit(int code)
@@ -457,7 +357,7 @@
 int AndroidRuntime::addVmArguments(int argc, const char* const argv[])
 {
     int i;
-    
+
     for (i = 0; i<argc; i++) {
         if (argv[i][0] != '-') {
             return i;
@@ -890,6 +790,17 @@
     return result;
 }
 
+char* AndroidRuntime::toSlashClassName(const char* className)
+{
+    char* result = strdup(className);
+    for (char* cp = result; *cp != '\0'; cp++) {
+        if (*cp == '.') {
+            *cp = '/';
+        }
+    }
+    return result;
+}
+
 /*
  * Start the Android runtime.  This involves starting the virtual machine
  * and calling the "static void main(String[] args)" method in the class
@@ -900,20 +811,16 @@
     LOGD("\n>>>>>> AndroidRuntime START %s <<<<<<\n",
             className != NULL ? className : "(unknown)");
 
-    char* slashClassName = NULL;
-    char* cp;
-    JNIEnv* env;
-
     blockSigpipe();
 
-    /* 
-     * 'startSystemServer == true' means runtime is obsolete and not run from 
+    /*
+     * 'startSystemServer == true' means runtime is obsolete and not run from
      * init.rc anymore, so we print out the boot start event here.
      */
     if (startSystemServer) {
         /* track our progress through the boot sequence */
         const int LOG_BOOT_PROGRESS_START = 3000;
-        LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START, 
+        LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START,
                        ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
     }
 
@@ -922,7 +829,7 @@
         rootDir = "/system";
         if (!hasDir("/system")) {
             LOG_FATAL("No root directory specified, and /android does not exist.");
-            goto bail;
+            return;
         }
         setenv("ANDROID_ROOT", rootDir, 1);
     }
@@ -931,15 +838,18 @@
     //LOGD("Found LD_ASSUME_KERNEL='%s'\n", kernelHack);
 
     /* start the virtual machine */
-    if (startVm(&mJavaVM, &env) != 0)
-        goto bail;
+    JNIEnv* env;
+    if (startVm(&mJavaVM, &env) != 0) {
+        return;
+    }
+    onVmCreated(env);
 
     /*
      * Register android functions.
      */
     if (startReg(env) < 0) {
         LOGE("Unable to register all android natives\n");
-        goto bail;
+        return;
     }
 
     /*
@@ -959,7 +869,7 @@
     classNameStr = env->NewStringUTF(className);
     assert(classNameStr != NULL);
     env->SetObjectArrayElement(strArray, 0, classNameStr);
-    startSystemServerStr = env->NewStringUTF(startSystemServer ? 
+    startSystemServerStr = env->NewStringUTF(startSystemServer ?
                                                  "true" : "false");
     env->SetObjectArrayElement(strArray, 1, startSystemServerStr);
 
@@ -967,20 +877,13 @@
      * Start VM.  This thread becomes the main thread of the VM, and will
      * not return until the VM exits.
      */
-    jclass startClass;
-    jmethodID startMeth;
-
-    slashClassName = strdup(className);
-    for (cp = slashClassName; *cp != '\0'; cp++)
-        if (*cp == '.')
-            *cp = '/';
-
-    startClass = env->FindClass(slashClassName);
+    char* slashClassName = toSlashClassName(className);
+    jclass startClass = env->FindClass(slashClassName);
     if (startClass == NULL) {
         LOGE("JavaVM unable to locate class '%s'\n", slashClassName);
         /* keep going */
     } else {
-        startMeth = env->GetStaticMethodID(startClass, "main",
+        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
             "([Ljava/lang/String;)V");
         if (startMeth == NULL) {
             LOGE("JavaVM unable to find main() in '%s'\n", className);
@@ -994,15 +897,13 @@
 #endif
         }
     }
+    free(slashClassName);
 
     LOGD("Shutting down VM\n");
     if (mJavaVM->DetachCurrentThread() != JNI_OK)
         LOGW("Warning: unable to detach main thread\n");
     if (mJavaVM->DestroyJavaVM() != 0)
         LOGW("Warning: VM did not shut down cleanly\n");
-
-bail:
-    free(slashClassName);
 }
 
 void AndroidRuntime::start()
@@ -1017,6 +918,11 @@
     exit(code);
 }
 
+void AndroidRuntime::onVmCreated(JNIEnv* env)
+{
+    // If AndroidRuntime had anything to do here, we'd have done it in 'start'.
+}
+
 /*
  * Get the JNIEnv pointer for this thread.
  *
@@ -1111,7 +1017,7 @@
  * into the VM before it really starts executing.
  */
 /*static*/ int AndroidRuntime::javaCreateThreadEtc(
-                                android_thread_func_t entryFunction, 
+                                android_thread_func_t entryFunction,
                                 void* userData,
                                 const char* threadName,
                                 int32_t threadPriority,
@@ -1299,7 +1205,7 @@
     REG_JNI(register_android_backup_BackupDataOutput),
     REG_JNI(register_android_backup_FileBackupHelperBase),
     REG_JNI(register_android_backup_BackupHelperDispatcher),
-    
+
     REG_JNI(register_android_app_ActivityThread),
     REG_JNI(register_android_app_NativeActivity),
     REG_JNI(register_android_view_InputChannel),
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 6c28e65..64bd207 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -350,14 +350,10 @@
     SkASSERT(bitmap);
     SkASSERT(bitmap->pixelRef());
 
-    jobject obj = env->AllocObject(gBitmap_class);
-    if (obj) {
-        env->CallVoidMethod(obj, gBitmap_constructorMethodID,
-                            (jint)bitmap, buffer, isMutable, ninepatch, density);
-        if (hasException(env)) {
-            obj = NULL;
-        }
-    }
+    jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
+            static_cast<jint>(reinterpret_cast<uintptr_t>(bitmap)),
+            buffer, isMutable, ninepatch, density);
+    hasException(env); // For the side effect of logging.
     return obj;
 }
 
@@ -372,30 +368,19 @@
 {
     SkASSERT(bitmap != NULL);
 
-    jobject obj = env->AllocObject(gBitmapRegionDecoder_class);
-    if (hasException(env)) {
-        obj = NULL;
-        return obj;
-    }
-    if (obj) {
-        env->CallVoidMethod(obj, gBitmapRegionDecoder_constructorMethodID, (jint)bitmap);
-        if (hasException(env)) {
-            obj = NULL;
-        }
-    }
+    jobject obj = env->NewObject(gBitmapRegionDecoder_class,
+            gBitmapRegionDecoder_constructorMethodID,
+            static_cast<jint>(reinterpret_cast<uintptr_t>(bitmap)));
+    hasException(env); // For the side effect of logging.
     return obj;
 }
 
 jobject GraphicsJNI::createRegion(JNIEnv* env, SkRegion* region)
 {
     SkASSERT(region != NULL);
-    jobject obj = env->AllocObject(gRegion_class);
-    if (obj) {
-        env->CallVoidMethod(obj, gRegion_constructorMethodID, (jint)region, 0);
-        if (hasException(env)) {
-            obj = NULL;
-        }
-    }
+    jobject obj = env->NewObject(gRegion_class, gRegion_constructorMethodID,
+            static_cast<jint>(reinterpret_cast<uintptr_t>(region)), 0);
+    hasException(env); // For the side effect of logging.
     return obj;
 }
 
diff --git a/core/jni/android/graphics/Movie.cpp b/core/jni/android/graphics/Movie.cpp
index 7145433..c1124238 100644
--- a/core/jni/android/graphics/Movie.cpp
+++ b/core/jni/android/graphics/Movie.cpp
@@ -23,11 +23,8 @@
     if (NULL == moov) {
         return NULL;
     }
-    jobject obj = env->AllocObject(gMovie_class);
-    if (obj) {
-        env->CallVoidMethod(obj, gMovie_constructorMethodID, (jint)moov);
-    }
-    return obj;
+    return env->NewObject(gMovie_class, gMovie_constructorMethodID,
+            static_cast<jint>(reinterpret_cast<uintptr_t>(moov)));
 }
 
 static SkMovie* J2Movie(JNIEnv* env, jobject movie) {
diff --git a/core/jni/android_emoji_EmojiFactory.cpp b/core/jni/android_emoji_EmojiFactory.cpp
index 56859b8..81dae88 100644
--- a/core/jni/android_emoji_EmojiFactory.cpp
+++ b/core/jni/android_emoji_EmojiFactory.cpp
@@ -106,15 +106,11 @@
 
 static jobject create_java_EmojiFactory(
     JNIEnv* env, EmojiFactory* factory, jstring name) {
-  jobject obj = env->AllocObject(gEmojiFactory_class);
-  if (obj) {
-    env->CallVoidMethod(obj, gEmojiFactory_constructorMethodID,
-                        (jint)factory, name);
-    if (env->ExceptionCheck() != 0) {
-      LOGE("*** Uncaught exception returned from Java call!\n");
-      env->ExceptionDescribe();
-      obj = NULL;
-    }
+  jobject obj = env->NewObject(gEmojiFactory_class, gEmojiFactory_constructorMethodID,
+      static_cast<jint>(reinterpret_cast<uintptr_t>(factory)), name);
+  if (env->ExceptionCheck() != 0) {
+    LOGE("*** Uncaught exception returned from Java call!\n");
+    env->ExceptionDescribe();
   }
   return obj;
 }
@@ -180,17 +176,12 @@
     return NULL;
   }
 
-  jobject obj = env->AllocObject(gBitmap_class);
-  if (obj) {
-    env->CallVoidMethod(obj, gBitmap_constructorMethodID,
-                        reinterpret_cast<jint>(bitmap), NULL, false, NULL, -1);
-    if (env->ExceptionCheck() != 0) {
-      LOGE("*** Uncaught exception returned from Java call!\n");
-      env->ExceptionDescribe();
-      return NULL;
-    }
+  jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
+      static_cast<jint>(reinterpret_cast<uintptr_t>(bitmap)), NULL, false, NULL, -1);
+  if (env->ExceptionCheck() != 0) {
+    LOGE("*** Uncaught exception returned from Java call!\n");
+    env->ExceptionDescribe();
   }
-
   return obj;
 }
 
diff --git a/core/jni/android_net_TrafficStats.cpp b/core/jni/android_net_TrafficStats.cpp
index 0c84f11..203b5ef 100644
--- a/core/jni/android_net_TrafficStats.cpp
+++ b/core/jni/android_net_TrafficStats.cpp
@@ -25,6 +25,7 @@
 #include <android_runtime/AndroidRuntime.h>
 #include <cutils/logger.h>
 #include <jni.h>
+#include <ScopedUtfChars.h>
 #include <utils/misc.h>
 #include <utils/Log.h>
 
@@ -130,13 +131,14 @@
             "/sys/class/net/ppp0/statistics/rx_bytes");
 }
 
-static jlong getData(JNIEnv* env, char *what, jstring interface) {
+static jlong getData(JNIEnv* env, const char* what, jstring javaInterface) {
+    ScopedUtfChars interface(env, javaInterface);
+    if (interface.c_str() == NULL) {
+        return -1;
+    }
+
     char filename[80];
-    jboolean isCopy;
-
-    const char *interfaceStr = env->GetStringUTFChars(interface, &isCopy);
-    snprintf(filename, sizeof(filename), "/sys/class/net/%s/statistics/%s", interfaceStr, what);
-
+    snprintf(filename, sizeof(filename), "/sys/class/net/%s/statistics/%s", interface.c_str(), what);
     return readNumber(filename);
 }
 
diff --git a/core/jni/android_net_wifi_Wifi.cpp b/core/jni/android_net_wifi_Wifi.cpp
index 2abb9ea..9d17fe9 100644
--- a/core/jni/android_net_wifi_Wifi.cpp
+++ b/core/jni/android_net_wifi_Wifi.cpp
@@ -17,6 +17,7 @@
 #define LOG_TAG "wifi"
 
 #include "jni.h"
+#include <ScopedUtfChars.h>
 #include <utils/misc.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/Log.h>
@@ -47,82 +48,100 @@
     }
 }
 
-static jint doIntCommand(const char *cmd)
+static jint doIntCommand(const char* fmt, ...)
 {
-    char reply[BUF_SIZE];
-
-    if (doCommand(cmd, reply, sizeof(reply)) != 0) {
-        return (jint)-1;
-    } else {
-        return (jint)atoi(reply);
+    char buf[BUF_SIZE];
+    va_list args;
+    va_start(args, fmt);
+    int byteCount = vsnprintf(buf, sizeof(buf), fmt, args);
+    va_end(args);
+    if (byteCount < 0 || byteCount >= BUF_SIZE) {
+        return -1;
     }
+    char reply[BUF_SIZE];
+    if (doCommand(buf, reply, sizeof(reply)) != 0) {
+        return -1;
+    }
+    return static_cast<jint>(atoi(reply));
 }
 
-static jboolean doBooleanCommand(const char *cmd, const char *expect)
+static jboolean doBooleanCommand(const char* expect, const char* fmt, ...)
 {
-    char reply[BUF_SIZE];
-
-    if (doCommand(cmd, reply, sizeof(reply)) != 0) {
-        return (jboolean)JNI_FALSE;
-    } else {
-        return (jboolean)(strcmp(reply, expect) == 0);
+    char buf[BUF_SIZE];
+    va_list args;
+    va_start(args, fmt);
+    int byteCount = vsnprintf(buf, sizeof(buf), fmt, args);
+    va_end(args);
+    if (byteCount < 0 || byteCount >= BUF_SIZE) {
+        return JNI_FALSE;
     }
+    char reply[BUF_SIZE];
+    if (doCommand(buf, reply, sizeof(reply)) != 0) {
+        return JNI_FALSE;
+    }
+    return (strcmp(reply, expect) == 0);
 }
 
 // Send a command to the supplicant, and return the reply as a String
-static jstring doStringCommand(JNIEnv *env, const char *cmd)
-{
-    char reply[4096];
-
-    if (doCommand(cmd, reply, sizeof(reply)) != 0) {
-        return env->NewStringUTF(NULL);
-    } else {
-        String16 str((char *)reply);
-        return env->NewString((const jchar *)str.string(), str.size());
+static jstring doStringCommand(JNIEnv* env, const char* fmt, ...) {
+    char buf[BUF_SIZE];
+    va_list args;
+    va_start(args, fmt);
+    int byteCount = vsnprintf(buf, sizeof(buf), fmt, args);
+    va_end(args);
+    if (byteCount < 0 || byteCount >= BUF_SIZE) {
+        return NULL;
     }
+    char reply[4096];
+    if (doCommand(buf, reply, sizeof(reply)) != 0) {
+        return NULL;
+    }
+    // TODO: why not just NewStringUTF?
+    String16 str((char *)reply);
+    return env->NewString((const jchar *)str.string(), str.size());
 }
 
-static jboolean android_net_wifi_isDriverLoaded(JNIEnv* env, jobject clazz)
+static jboolean android_net_wifi_isDriverLoaded(JNIEnv* env, jobject)
 {
     return (jboolean)(::is_wifi_driver_loaded() == 1);
 }
 
-static jboolean android_net_wifi_loadDriver(JNIEnv* env, jobject clazz)
+static jboolean android_net_wifi_loadDriver(JNIEnv* env, jobject)
 {
     return (jboolean)(::wifi_load_driver() == 0);
 }
 
-static jboolean android_net_wifi_unloadDriver(JNIEnv* env, jobject clazz)
+static jboolean android_net_wifi_unloadDriver(JNIEnv* env, jobject)
 {
     return (jboolean)(::wifi_unload_driver() == 0);
 }
 
-static jboolean android_net_wifi_startSupplicant(JNIEnv* env, jobject clazz)
+static jboolean android_net_wifi_startSupplicant(JNIEnv* env, jobject)
 {
     return (jboolean)(::wifi_start_supplicant() == 0);
 }
 
-static jboolean android_net_wifi_stopSupplicant(JNIEnv* env, jobject clazz)
+static jboolean android_net_wifi_stopSupplicant(JNIEnv* env, jobject)
 {
-    return doBooleanCommand("TERMINATE", "OK");
+    return doBooleanCommand("OK", "TERMINATE");
 }
 
-static jboolean android_net_wifi_killSupplicant(JNIEnv* env, jobject clazz)
+static jboolean android_net_wifi_killSupplicant(JNIEnv* env, jobject)
 {
     return (jboolean)(::wifi_stop_supplicant() == 0);
 }
 
-static jboolean android_net_wifi_connectToSupplicant(JNIEnv* env, jobject clazz)
+static jboolean android_net_wifi_connectToSupplicant(JNIEnv* env, jobject)
 {
     return (jboolean)(::wifi_connect_to_supplicant() == 0);
 }
 
-static void android_net_wifi_closeSupplicantConnection(JNIEnv* env, jobject clazz)
+static void android_net_wifi_closeSupplicantConnection(JNIEnv* env, jobject)
 {
     ::wifi_close_supplicant_connection();
 }
 
-static jstring android_net_wifi_waitForEvent(JNIEnv* env, jobject clazz)
+static jstring android_net_wifi_waitForEvent(JNIEnv* env, jobject)
 {
     char buf[BUF_SIZE];
 
@@ -130,197 +149,143 @@
     if (nread > 0) {
         return env->NewStringUTF(buf);
     } else {
-        return  env->NewStringUTF(NULL);
+        return NULL;
     }
 }
 
-static jstring android_net_wifi_listNetworksCommand(JNIEnv* env, jobject clazz)
+static jstring android_net_wifi_listNetworksCommand(JNIEnv* env, jobject)
 {
     return doStringCommand(env, "LIST_NETWORKS");
 }
 
-static jint android_net_wifi_addNetworkCommand(JNIEnv* env, jobject clazz)
+static jint android_net_wifi_addNetworkCommand(JNIEnv* env, jobject)
 {
     return doIntCommand("ADD_NETWORK");
 }
 
-static jboolean android_net_wifi_wpsPbcCommand(JNIEnv* env, jobject clazz, jstring bssid)
+static jboolean android_net_wifi_wpsPbcCommand(JNIEnv* env, jobject, jstring javaBssid)
 {
-    char cmdstr[BUF_SIZE];
-    jboolean isCopy;
-
-    const char *bssidStr = env->GetStringUTFChars(bssid, &isCopy);
-    int numWritten = snprintf(cmdstr, sizeof(cmdstr), "WPS_PBC %s", bssidStr);
-    env->ReleaseStringUTFChars(bssid, bssidStr);
-
-    if ((numWritten == -1) || (numWritten >= sizeof(cmdstr))) {
-        return false;
+    ScopedUtfChars bssid(env, javaBssid);
+    if (bssid.c_str() == NULL) {
+        return JNI_FALSE;
     }
-    return doBooleanCommand(cmdstr, "OK");
+    return doBooleanCommand("OK", "WPS_PBC %s", bssid.c_str());
 }
 
-static jboolean android_net_wifi_wpsPinFromAccessPointCommand(JNIEnv* env, jobject clazz,
-        jstring bssid, jstring apPin)
+static jboolean android_net_wifi_wpsPinFromAccessPointCommand(JNIEnv* env, jobject,
+        jstring javaBssid, jstring javaApPin)
 {
-    char cmdstr[BUF_SIZE];
-    jboolean isCopy;
-
-    const char *bssidStr = env->GetStringUTFChars(bssid, &isCopy);
-    const char *apPinStr = env->GetStringUTFChars(apPin, &isCopy);
-    int numWritten = snprintf(cmdstr, sizeof(cmdstr), "WPS_REG %s %s", bssidStr, apPinStr);
-    env->ReleaseStringUTFChars(bssid, bssidStr);
-    env->ReleaseStringUTFChars(apPin, apPinStr);
-
-    if ((numWritten == -1) || (numWritten >= (int)sizeof(cmdstr))) {
-        return false;
+    ScopedUtfChars bssid(env, javaBssid);
+    if (bssid.c_str() == NULL) {
+        return JNI_FALSE;
     }
-    return doBooleanCommand(cmdstr, "OK");
+    ScopedUtfChars apPin(env, javaApPin);
+    if (apPin.c_str() == NULL) {
+        return JNI_FALSE;
+    }
+    return doBooleanCommand("OK", "WPS_REG %s %s", bssid.c_str(), apPin.c_str());
 }
 
-static jstring android_net_wifi_wpsPinFromDeviceCommand(JNIEnv* env, jobject clazz, jstring bssid)
+static jstring android_net_wifi_wpsPinFromDeviceCommand(JNIEnv* env, jobject, jstring javaBssid)
 {
-    char cmdstr[BUF_SIZE];
-    jboolean isCopy;
-
-    const char *bssidStr = env->GetStringUTFChars(bssid, &isCopy);
-    int numWritten = snprintf(cmdstr, sizeof(cmdstr), "WPS_PIN %s", bssidStr);
-    env->ReleaseStringUTFChars(bssid, bssidStr);
-
-    if ((numWritten == -1) || (numWritten >= (int)sizeof(cmdstr))) {
+    ScopedUtfChars bssid(env, javaBssid);
+    if (bssid.c_str() == NULL) {
         return NULL;
     }
-    return doStringCommand(env, cmdstr);
+    return doStringCommand(env, "WPS_PIN %s", bssid.c_str());
 }
 
-static jboolean android_net_wifi_setCountryCodeCommand(JNIEnv* env, jobject clazz, jstring country)
+static jboolean android_net_wifi_setCountryCodeCommand(JNIEnv* env, jobject, jstring javaCountry)
 {
-    char cmdstr[BUF_SIZE];
-    jboolean isCopy;
-
-    const char *countryStr = env->GetStringUTFChars(country, &isCopy);
-    int numWritten = snprintf(cmdstr, sizeof(cmdstr), "DRIVER COUNTRY %s", countryStr);
-    env->ReleaseStringUTFChars(country, countryStr);
-
-    if ((numWritten == -1) || (numWritten >= (int)sizeof(cmdstr))) {
-        return false;
+    ScopedUtfChars country(env, javaCountry);
+    if (country.c_str() == NULL) {
+        return JNI_FALSE;
     }
-    return doBooleanCommand(cmdstr, "OK");
+    return doBooleanCommand("OK", "DRIVER COUNTRY %s", country.c_str());
 }
 
 static jboolean android_net_wifi_setNetworkVariableCommand(JNIEnv* env,
-                                                           jobject clazz,
+                                                           jobject,
                                                            jint netId,
-                                                           jstring name,
-                                                           jstring value)
+                                                           jstring javaName,
+                                                           jstring javaValue)
 {
-    char cmdstr[BUF_SIZE];
-    jboolean isCopy;
-
-    const char *nameStr = env->GetStringUTFChars(name, &isCopy);
-    const char *valueStr = env->GetStringUTFChars(value, &isCopy);
-
-    if (nameStr == NULL || valueStr == NULL)
+    ScopedUtfChars name(env, javaName);
+    if (name.c_str() == NULL) {
         return JNI_FALSE;
-
-    int cmdTooLong = snprintf(cmdstr, sizeof(cmdstr), "SET_NETWORK %d %s %s",
-                 netId, nameStr, valueStr) >= (int)sizeof(cmdstr);
-
-    env->ReleaseStringUTFChars(name, nameStr);
-    env->ReleaseStringUTFChars(value, valueStr);
-
-    return (jboolean)!cmdTooLong && doBooleanCommand(cmdstr, "OK");
+    }
+    ScopedUtfChars value(env, javaValue);
+    if (value.c_str() == NULL) {
+        return JNI_FALSE;
+    }
+    return doBooleanCommand("OK", "SET_NETWORK %d %s %s", netId, name.c_str(), value.c_str());
 }
 
 static jstring android_net_wifi_getNetworkVariableCommand(JNIEnv* env,
-                                                          jobject clazz,
+                                                          jobject,
                                                           jint netId,
-                                                          jstring name)
+                                                          jstring javaName)
 {
-    char cmdstr[BUF_SIZE];
-    jboolean isCopy;
-
-    const char *nameStr = env->GetStringUTFChars(name, &isCopy);
-
-    if (nameStr == NULL)
-        return env->NewStringUTF(NULL);
-
-    int cmdTooLong = snprintf(cmdstr, sizeof(cmdstr), "GET_NETWORK %d %s",
-                             netId, nameStr) >= (int)sizeof(cmdstr);
-
-    env->ReleaseStringUTFChars(name, nameStr);
-
-    return cmdTooLong ? env->NewStringUTF(NULL) : doStringCommand(env, cmdstr);
+    ScopedUtfChars name(env, javaName);
+    if (name.c_str() == NULL) {
+        return NULL;
+    }
+    return doStringCommand(env, "GET_NETWORK %d %s", netId, name.c_str());
 }
 
-static jboolean android_net_wifi_removeNetworkCommand(JNIEnv* env, jobject clazz, jint netId)
+static jboolean android_net_wifi_removeNetworkCommand(JNIEnv* env, jobject, jint netId)
 {
-    char cmdstr[BUF_SIZE];
-
-    int numWritten = snprintf(cmdstr, sizeof(cmdstr), "REMOVE_NETWORK %d", netId);
-    int cmdTooLong = numWritten >= (int)sizeof(cmdstr);
-
-    return (jboolean)!cmdTooLong && doBooleanCommand(cmdstr, "OK");
+    return doBooleanCommand("OK", "REMOVE_NETWORK %d", netId);
 }
 
 static jboolean android_net_wifi_enableNetworkCommand(JNIEnv* env,
-                                                  jobject clazz,
+                                                  jobject,
                                                   jint netId,
                                                   jboolean disableOthers)
 {
-    char cmdstr[BUF_SIZE];
-    const char *cmd = disableOthers ? "SELECT_NETWORK" : "ENABLE_NETWORK";
-
-    int numWritten = snprintf(cmdstr, sizeof(cmdstr), "%s %d", cmd, netId);
-    int cmdTooLong = numWritten >= (int)sizeof(cmdstr);
-
-    return (jboolean)!cmdTooLong && doBooleanCommand(cmdstr, "OK");
+    return doBooleanCommand("OK", "%s_NETWORK %d", disableOthers ? "SELECT" : "ENABLE", netId);
 }
 
-static jboolean android_net_wifi_disableNetworkCommand(JNIEnv* env, jobject clazz, jint netId)
+static jboolean android_net_wifi_disableNetworkCommand(JNIEnv* env, jobject, jint netId)
 {
-    char cmdstr[BUF_SIZE];
-
-    int numWritten = snprintf(cmdstr, sizeof(cmdstr), "DISABLE_NETWORK %d", netId);
-    int cmdTooLong = numWritten >= (int)sizeof(cmdstr);
-
-    return (jboolean)!cmdTooLong && doBooleanCommand(cmdstr, "OK");
+    return doBooleanCommand("OK", "DISABLE_NETWORK %d", netId);
 }
 
-static jstring android_net_wifi_statusCommand(JNIEnv* env, jobject clazz)
+static jstring android_net_wifi_statusCommand(JNIEnv* env, jobject)
 {
     return doStringCommand(env, "STATUS");
 }
 
-static jboolean android_net_wifi_pingCommand(JNIEnv* env, jobject clazz)
+static jboolean android_net_wifi_pingCommand(JNIEnv* env, jobject)
 {
-    return doBooleanCommand("PING", "PONG");
+    return doBooleanCommand("PONG", "PING");
 }
 
-static jstring android_net_wifi_scanResultsCommand(JNIEnv* env, jobject clazz)
+static jstring android_net_wifi_scanResultsCommand(JNIEnv* env, jobject)
 {
     return doStringCommand(env, "SCAN_RESULTS");
 }
 
-static jboolean android_net_wifi_disconnectCommand(JNIEnv* env, jobject clazz)
+static jboolean android_net_wifi_disconnectCommand(JNIEnv* env, jobject)
 {
-    return doBooleanCommand("DISCONNECT", "OK");
+    return doBooleanCommand("OK", "DISCONNECT");
 }
 
-static jboolean android_net_wifi_reconnectCommand(JNIEnv* env, jobject clazz)
+static jboolean android_net_wifi_reconnectCommand(JNIEnv* env, jobject)
 {
-    return doBooleanCommand("RECONNECT", "OK");
+    return doBooleanCommand("OK", "RECONNECT");
 }
-static jboolean android_net_wifi_reassociateCommand(JNIEnv* env, jobject clazz)
+static jboolean android_net_wifi_reassociateCommand(JNIEnv* env, jobject)
 {
-    return doBooleanCommand("REASSOCIATE", "OK");
+    return doBooleanCommand("OK", "REASSOCIATE");
 }
 
 static jboolean doSetScanMode(jboolean setActive)
 {
-    return doBooleanCommand((setActive ? "DRIVER SCAN-ACTIVE" : "DRIVER SCAN-PASSIVE"), "OK");
+    return doBooleanCommand("OK", (setActive ? "DRIVER SCAN-ACTIVE" : "DRIVER SCAN-PASSIVE"));
 }
 
-static jboolean android_net_wifi_scanCommand(JNIEnv* env, jobject clazz, jboolean forceActive)
+static jboolean android_net_wifi_scanCommand(JNIEnv* env, jobject, jboolean forceActive)
 {
     jboolean result;
 
@@ -328,43 +293,43 @@
     // The scan will still work.
     if (forceActive && !sScanModeActive)
         doSetScanMode(true);
-    result = doBooleanCommand("SCAN", "OK");
+    result = doBooleanCommand("OK", "SCAN");
     if (forceActive && !sScanModeActive)
         doSetScanMode(sScanModeActive);
     return result;
 }
 
-static jboolean android_net_wifi_setScanModeCommand(JNIEnv* env, jobject clazz, jboolean setActive)
+static jboolean android_net_wifi_setScanModeCommand(JNIEnv* env, jobject, jboolean setActive)
 {
     sScanModeActive = setActive;
     return doSetScanMode(setActive);
 }
 
-static jboolean android_net_wifi_startDriverCommand(JNIEnv* env, jobject clazz)
+static jboolean android_net_wifi_startDriverCommand(JNIEnv* env, jobject)
 {
-    return doBooleanCommand("DRIVER START", "OK");
+    return doBooleanCommand("OK", "DRIVER START");
 }
 
-static jboolean android_net_wifi_stopDriverCommand(JNIEnv* env, jobject clazz)
+static jboolean android_net_wifi_stopDriverCommand(JNIEnv* env, jobject)
 {
-    return doBooleanCommand("DRIVER STOP", "OK");
+    return doBooleanCommand("OK", "DRIVER STOP");
 }
 
-static jboolean android_net_wifi_startPacketFiltering(JNIEnv* env, jobject clazz)
+static jboolean android_net_wifi_startPacketFiltering(JNIEnv* env, jobject)
 {
-    return doBooleanCommand("DRIVER RXFILTER-ADD 0", "OK")
-	&& doBooleanCommand("DRIVER RXFILTER-ADD 1", "OK")
-	&& doBooleanCommand("DRIVER RXFILTER-ADD 3", "OK")
-	&& doBooleanCommand("DRIVER RXFILTER-START", "OK");
+    return doBooleanCommand("OK", "DRIVER RXFILTER-ADD 0")
+            && doBooleanCommand("OK", "DRIVER RXFILTER-ADD 1")
+            && doBooleanCommand("OK", "DRIVER RXFILTER-ADD 3")
+            && doBooleanCommand("OK", "DRIVER RXFILTER-START");
 }
 
-static jboolean android_net_wifi_stopPacketFiltering(JNIEnv* env, jobject clazz)
+static jboolean android_net_wifi_stopPacketFiltering(JNIEnv* env, jobject)
 {
-    jboolean result = doBooleanCommand("DRIVER RXFILTER-STOP", "OK");
+    jboolean result = doBooleanCommand("OK", "DRIVER RXFILTER-STOP");
     if (result) {
-	(void)doBooleanCommand("DRIVER RXFILTER-REMOVE 3", "OK");
-	(void)doBooleanCommand("DRIVER RXFILTER-REMOVE 1", "OK");
-	(void)doBooleanCommand("DRIVER RXFILTER-REMOVE 0", "OK");
+        (void)doBooleanCommand("OK", "DRIVER RXFILTER-REMOVE 3");
+        (void)doBooleanCommand("OK", "DRIVER RXFILTER-REMOVE 1");
+        (void)doBooleanCommand("OK", "DRIVER RXFILTER-REMOVE 0");
     }
 
     return result;
@@ -399,17 +364,17 @@
     return (jint)rssi;
 }
 
-static jint android_net_wifi_getRssiCommand(JNIEnv* env, jobject clazz)
+static jint android_net_wifi_getRssiCommand(JNIEnv* env, jobject)
 {
     return android_net_wifi_getRssiHelper("DRIVER RSSI");
 }
 
-static jint android_net_wifi_getRssiApproxCommand(JNIEnv* env, jobject clazz)
+static jint android_net_wifi_getRssiApproxCommand(JNIEnv* env, jobject)
 {
     return android_net_wifi_getRssiHelper("DRIVER RSSI-APPROX");
 }
 
-static jint android_net_wifi_getLinkSpeedCommand(JNIEnv* env, jobject clazz)
+static jint android_net_wifi_getLinkSpeedCommand(JNIEnv* env, jobject)
 {
     char reply[BUF_SIZE];
     int linkspeed;
@@ -423,33 +388,28 @@
     return (jint)linkspeed;
 }
 
-static jstring android_net_wifi_getMacAddressCommand(JNIEnv* env, jobject clazz)
+static jstring android_net_wifi_getMacAddressCommand(JNIEnv* env, jobject)
 {
     char reply[BUF_SIZE];
     char buf[BUF_SIZE];
 
     if (doCommand("DRIVER MACADDR", reply, sizeof(reply)) != 0) {
-        return env->NewStringUTF(NULL);
+        return NULL;
     }
     // reply comes back in the form "Macaddr = XX.XX.XX.XX.XX.XX" where XX
     // is the part of the string we're interested in.
-    if (sscanf(reply, "%*s = %255s", buf) == 1)
+    if (sscanf(reply, "%*s = %255s", buf) == 1) {
         return env->NewStringUTF(buf);
-    else
-        return env->NewStringUTF(NULL);
+    }
+    return NULL;
 }
 
-static jboolean android_net_wifi_setPowerModeCommand(JNIEnv* env, jobject clazz, jint mode)
+static jboolean android_net_wifi_setPowerModeCommand(JNIEnv* env, jobject, jint mode)
 {
-    char cmdstr[BUF_SIZE];
-
-    int numWritten = snprintf(cmdstr, sizeof(cmdstr), "DRIVER POWERMODE %d", mode);
-    int cmdTooLong = numWritten >= (int)sizeof(cmdstr);
-
-    return (jboolean)!cmdTooLong && doBooleanCommand(cmdstr, "OK");
+    return doBooleanCommand("OK", "DRIVER POWERMODE %d", mode);
 }
 
-static jint android_net_wifi_getPowerModeCommand(JNIEnv* env, jobject clazz)
+static jint android_net_wifi_getPowerModeCommand(JNIEnv* env, jobject)
 {
     char reply[BUF_SIZE];
     int power;
@@ -463,17 +423,12 @@
     return (jint)power;
 }
 
-static jboolean android_net_wifi_setBandCommand(JNIEnv* env, jobject clazz, jint band)
+static jboolean android_net_wifi_setBandCommand(JNIEnv* env, jobject, jint band)
 {
-    char cmdstr[25];
-
-    int numWritten = snprintf(cmdstr, sizeof(cmdstr), "DRIVER SETBAND %d", band);
-    int cmdTooLong = numWritten >= (int)sizeof(cmdstr);
-
-    return (jboolean)!cmdTooLong && doBooleanCommand(cmdstr, "OK");
+    return doBooleanCommand("OK", "DRIVER SETBAND %d", band);
 }
 
-static jint android_net_wifi_getBandCommand(JNIEnv* env, jobject clazz)
+static jint android_net_wifi_getBandCommand(JNIEnv* env, jobject)
 {
     char reply[25];
     int band;
@@ -487,94 +442,66 @@
     return (jint)band;
 }
 
-static jboolean android_net_wifi_setBluetoothCoexistenceModeCommand(JNIEnv* env, jobject clazz, jint mode)
+static jboolean android_net_wifi_setBluetoothCoexistenceModeCommand(JNIEnv* env, jobject, jint mode)
 {
-    char cmdstr[BUF_SIZE];
-
-    int numWritten = snprintf(cmdstr, sizeof(cmdstr), "DRIVER BTCOEXMODE %d", mode);
-    int cmdTooLong = numWritten >= (int)sizeof(cmdstr);
-
-    return (jboolean)!cmdTooLong && doBooleanCommand(cmdstr, "OK");
+    return doBooleanCommand("OK", "DRIVER BTCOEXMODE %d", mode);
 }
 
-static jboolean android_net_wifi_setBluetoothCoexistenceScanModeCommand(JNIEnv* env, jobject clazz, jboolean setCoexScanMode)
+static jboolean android_net_wifi_setBluetoothCoexistenceScanModeCommand(JNIEnv* env, jobject, jboolean setCoexScanMode)
 {
-    char cmdstr[BUF_SIZE];
-
-    int numWritten = snprintf(cmdstr, sizeof(cmdstr), "DRIVER BTCOEXSCAN-%s", setCoexScanMode ? "START" : "STOP");
-    int cmdTooLong = numWritten >= (int)sizeof(cmdstr);
-
-    return (jboolean)!cmdTooLong && doBooleanCommand(cmdstr, "OK");
+    return doBooleanCommand("OK", "DRIVER BTCOEXSCAN-%s", setCoexScanMode ? "START" : "STOP");
 }
 
-static jboolean android_net_wifi_saveConfigCommand(JNIEnv* env, jobject clazz)
+static jboolean android_net_wifi_saveConfigCommand(JNIEnv* env, jobject)
 {
     // Make sure we never write out a value for AP_SCAN other than 1
-    (void)doBooleanCommand("AP_SCAN 1", "OK");
-    return doBooleanCommand("SAVE_CONFIG", "OK");
+    (void)doBooleanCommand("OK", "AP_SCAN 1");
+    return doBooleanCommand("OK", "SAVE_CONFIG");
 }
 
-static jboolean android_net_wifi_reloadConfigCommand(JNIEnv* env, jobject clazz)
+static jboolean android_net_wifi_reloadConfigCommand(JNIEnv* env, jobject)
 {
-    return doBooleanCommand("RECONFIGURE", "OK");
+    return doBooleanCommand("OK", "RECONFIGURE");
 }
 
-static jboolean android_net_wifi_setScanResultHandlingCommand(JNIEnv* env, jobject clazz, jint mode)
+static jboolean android_net_wifi_setScanResultHandlingCommand(JNIEnv* env, jobject, jint mode)
 {
-    char cmdstr[BUF_SIZE];
-
-    int numWritten = snprintf(cmdstr, sizeof(cmdstr), "AP_SCAN %d", mode);
-    int cmdTooLong = numWritten >= (int)sizeof(cmdstr);
-
-    return (jboolean)!cmdTooLong && doBooleanCommand(cmdstr, "OK");
+    return doBooleanCommand("OK", "AP_SCAN %d", mode);
 }
 
-static jboolean android_net_wifi_addToBlacklistCommand(JNIEnv* env, jobject clazz, jstring bssid)
+static jboolean android_net_wifi_addToBlacklistCommand(JNIEnv* env, jobject, jstring javaBssid)
 {
-    char cmdstr[BUF_SIZE];
-    jboolean isCopy;
-
-    const char *bssidStr = env->GetStringUTFChars(bssid, &isCopy);
-
-    int cmdTooLong = snprintf(cmdstr, sizeof(cmdstr), "BLACKLIST %s", bssidStr) >= (int)sizeof(cmdstr);
-
-    env->ReleaseStringUTFChars(bssid, bssidStr);
-
-    return (jboolean)!cmdTooLong && doBooleanCommand(cmdstr, "OK");
+    ScopedUtfChars bssid(env, javaBssid);
+    if (bssid.c_str() == NULL) {
+        return JNI_FALSE;
+    }
+    return doBooleanCommand("OK", "BLACKLIST %s", bssid.c_str());
 }
 
-static jboolean android_net_wifi_clearBlacklistCommand(JNIEnv* env, jobject clazz)
+static jboolean android_net_wifi_clearBlacklistCommand(JNIEnv* env, jobject)
 {
-    return doBooleanCommand("BLACKLIST clear", "OK");
+    return doBooleanCommand("OK", "BLACKLIST clear");
 }
 
-static jboolean android_net_wifi_setSuspendOptimizationsCommand(JNIEnv* env, jobject clazz, jboolean enabled)
+static jboolean android_net_wifi_setSuspendOptimizationsCommand(JNIEnv* env, jobject, jboolean enabled)
 {
-    char cmdstr[BUF_SIZE];
-
-    snprintf(cmdstr, sizeof(cmdstr), "DRIVER SETSUSPENDOPT %d", enabled ? 0 : 1);
-    return doBooleanCommand(cmdstr, "OK");
+    return doBooleanCommand("OK", "DRIVER SETSUSPENDOPT %d", enabled ? 0 : 1);
 }
 
-static void android_net_wifi_enableBackgroundScanCommand(JNIEnv* env, jobject clazz, jboolean enable)
+static void android_net_wifi_enableBackgroundScanCommand(JNIEnv* env, jobject, jboolean enable)
 {
     //Note: BGSCAN-START and BGSCAN-STOP are documented in core/res/res/values/config.xml
     //and will need an update if the names are changed
     if (enable) {
-        doBooleanCommand("DRIVER BGSCAN-START", "OK");
-    }
-    else {
-        doBooleanCommand("DRIVER BGSCAN-STOP", "OK");
+        doBooleanCommand("OK", "DRIVER BGSCAN-START");
+    } else {
+        doBooleanCommand("OK", "DRIVER BGSCAN-STOP");
     }
 }
 
-static void android_net_wifi_setScanIntervalCommand(JNIEnv* env, jobject clazz, jint scanInterval)
+static void android_net_wifi_setScanIntervalCommand(JNIEnv* env, jobject, jint scanInterval)
 {
-    char cmdstr[BUF_SIZE];
-
-    int numWritten = snprintf(cmdstr, sizeof(cmdstr), "SCAN_INTERVAL %d", scanInterval);
-
-    if(numWritten < (int)sizeof(cmdstr)) doBooleanCommand(cmdstr, "OK");
+    doBooleanCommand("OK", "SCAN_INTERVAL %d", scanInterval);
 }
 
 
diff --git a/core/jni/android_os_ParcelFileDescriptor.cpp b/core/jni/android_os_ParcelFileDescriptor.cpp
index bfa80e1..4ec131c 100644
--- a/core/jni/android_os_ParcelFileDescriptor.cpp
+++ b/core/jni/android_os_ParcelFileDescriptor.cpp
@@ -29,31 +29,11 @@
 namespace android
 {
 
-static struct socket_offsets_t
-{
-    jfieldID mSocketImpl;
-} gSocketOffsets;
-
-static struct socket_impl_offsets_t
-{
-    jfieldID mFileDescriptor;
-} gSocketImplOffsets;
-
 static struct parcel_file_descriptor_offsets_t
 {
-    jclass mClass;
     jfieldID mFileDescriptor;
 } gParcelFileDescriptorOffsets;
 
-static jobject android_os_ParcelFileDescriptor_getFileDescriptorFromSocket(JNIEnv* env,
-    jobject clazz, jobject object)
-{
-    jobject socketImpl = env->GetObjectField(object, gSocketOffsets.mSocketImpl);
-    jobject fileDescriptor = env->GetObjectField(socketImpl, gSocketImplOffsets.mFileDescriptor);
-    jint fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
-    return jniCreateFileDescriptor(env, dup(fd));
-}
-
 static int android_os_ParcelFileDescriptor_createPipeNative(JNIEnv* env,
     jobject clazz, jobjectArray outFds)
 {
@@ -122,8 +102,6 @@
 }
 
 static const JNINativeMethod gParcelFileDescriptorMethods[] = {
-    {"getFileDescriptorFromSocket", "(Ljava/net/Socket;)Ljava/io/FileDescriptor;",
-        (void*)android_os_ParcelFileDescriptor_getFileDescriptorFromSocket},
     {"createPipeNative", "([Ljava/io/FileDescriptor;)I",
         (void*)android_os_ParcelFileDescriptor_createPipeNative},
     {"getStatSize", "()J",
@@ -138,23 +116,8 @@
 
 int register_android_os_ParcelFileDescriptor(JNIEnv* env)
 {
-    jclass clazz;
-
-    clazz = env->FindClass("java/net/Socket");
-    LOG_FATAL_IF(clazz == NULL, "Unable to find class java.net.Socket");
-    gSocketOffsets.mSocketImpl = env->GetFieldID(clazz, "impl", "Ljava/net/SocketImpl;");
-    LOG_FATAL_IF(gSocketOffsets.mSocketImpl == NULL,
-        "Unable to find impl field in java.net.Socket");
-
-    clazz = env->FindClass("java/net/SocketImpl");
-    LOG_FATAL_IF(clazz == NULL, "Unable to find class java.net.SocketImpl");
-    gSocketImplOffsets.mFileDescriptor = env->GetFieldID(clazz, "fd", "Ljava/io/FileDescriptor;");
-    LOG_FATAL_IF(gSocketImplOffsets.mFileDescriptor == NULL,
-                 "Unable to find fd field in java.net.SocketImpl");
-
-    clazz = env->FindClass(kParcelFileDescriptorPathName);
+    jclass clazz = env->FindClass(kParcelFileDescriptorPathName);
     LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor");
-    gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
     gParcelFileDescriptorOffsets.mFileDescriptor = env->GetFieldID(clazz, "mFileDescriptor", "Ljava/io/FileDescriptor;");
     LOG_FATAL_IF(gParcelFileDescriptorOffsets.mFileDescriptor == NULL,
                  "Unable to find mFileDescriptor field in android.os.ParcelFileDescriptor");
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 34f0fdc..e5c28489 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -103,17 +103,6 @@
     }
 }
 
-
-static void fakeProcessEntry(void* arg)
-{
-    String8* cls = (String8*)arg;
-
-    AndroidRuntime* jr = AndroidRuntime::getRuntime();
-    jr->callMain(cls->string(), 0, NULL);
-
-    delete cls;
-}
-
 jint android_os_Process_myPid(JNIEnv* env, jobject clazz)
 {
     return getpid();
diff --git a/core/res/res/raw/execute_script_android.js b/core/res/res/raw/execute_script_android.js
new file mode 100644
index 0000000..d145754
--- /dev/null
+++ b/core/res/res/raw/execute_script_android.js
@@ -0,0 +1,8 @@
+function(){return function(){function h(a){var b=typeof a;if(b=="object")if(a){if(a instanceof Array)return"array";else if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if(c=="[object Window]")return"object";if(c=="[object Array]"||typeof a.length=="number"&&typeof a.splice!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("splice"))return"array";if(c=="[object Function]"||typeof a.call!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
+else if(b=="function"&&typeof a.call=="undefined")return"object";return b}function i(a){var b=h(a);return b=="array"||b=="object"&&typeof a.length=="number"}function j(a){a=h(a);return a=="object"||a=="array"||a=="function"}var k=Date.now||function(){return+new Date};function l(a,b){function c(){}c.prototype=b.prototype;a.c=b.prototype;a.prototype=new c};function m(a){this.stack=Error().stack||"";if(a)this.message=String(a)}l(m,Error);m.prototype.name="CustomError";function n(a,b,c){var d={};for(var e in a)if(b.call(c,a[e],e,a))d[e]=a[e];return d}function o(a,b,c){var d={};for(var e in a)d[e]=b.call(c,a[e],e,a);return d}function p(a,b,c){for(var d in a)if(b.call(c,a[d],d,a))return d};function q(a,b){m.call(this,b);this.code=a;this.name=r[a]||r[13]}l(q,m);var r,s={NoSuchElementError:7,NoSuchFrameError:8,UnknownCommandError:9,StaleElementReferenceError:10,ElementNotVisibleError:11,InvalidElementStateError:12,UnknownError:13,ElementNotSelectableError:15,XPathLookupError:19,NoSuchWindowError:23,InvalidCookieDomainError:24,UnableToSetCookieError:25,ModalDialogOpenedError:26,ModalDialogOpenError:27,ScriptTimeoutError:28},t={};for(var u in s)t[s[u]]=u;r=t;
+q.prototype.toString=function(){return"["+this.name+"] "+this.message};function v(a){for(var b=1;b<arguments.length;b++){var c=String(arguments[b]).replace(/\$/g,"$$$$");a=a.replace(/\%s/,c)}return a};function w(a,b){b.unshift(a);m.call(this,v.apply(null,b));b.shift();this.b=a}l(w,m);w.prototype.name="AssertionError";function x(a,b){if(!a){var c=Array.prototype.slice.call(arguments,2),d="Assertion failed";if(b){d+=": "+b;var e=c}throw new w(""+d,e||[]);}return a};var y=Array.prototype,z=y.map?function(a,b,c){x(a.length!=null);return y.map.call(a,b,c)}:function(a,b,c){var d=a.length,e=Array(d),f=typeof a=="string"?a.split(""):a;for(var g=0;g<d;g++)if(g in f)e[g]=b.call(c,f[g],g,a);return e};var A="",B;if(B=/WebKit\/(\S+)/){var C=B.exec(this.navigator?this.navigator.userAgent:null);A=C?C[1]:""};function D(){}
+function E(a,b,c){switch(typeof b){case "string":F(a,b,c);break;case "number":c.push(isFinite(b)&&!isNaN(b)?b:"null");break;case "boolean":c.push(b);break;case "undefined":c.push("null");break;case "object":if(b==null){c.push("null");break}if(h(b)=="array"){var d=b.length;c.push("[");var e="";for(var f=0;f<d;f++){c.push(e);E(a,b[f],c);e=","}c.push("]");break}c.push("{");d="";for(e in b)if(Object.prototype.hasOwnProperty.call(b,e)){f=b[e];if(typeof f!="function"){c.push(d);F(a,e,c);c.push(":");E(a,
+f,c);d=","}}c.push("}");break;case "function":break;default:throw Error("Unknown type: "+typeof b);}}var G={'"':'\\"',"\\":"\\\\","/":"\\/","\u0008":"\\b","\u000c":"\\f","\n":"\\n","\r":"\\r","\t":"\\t","\u000b":"\\u000b"},H=/\uffff/.test("\uffff")?/[\\\"\x00-\x1f\x7f-\uffff]/g:/[\\\"\x00-\x1f\x7f-\xff]/g;
+function F(a,b,c){c.push('"',b.replace(H,function(d){if(d in G)return G[d];var e=d.charCodeAt(0),f="\\u";if(e<16)f+="000";else if(e<256)f+="00";else if(e<4096)f+="0";return G[d]=f+e.toString(16)}),'"')};function I(a){switch(h(a)){case "string":case "number":case "boolean":return a;case "function":return a.toString();case "array":return z(a,I);case "object":a=a;if("tagName"in a&&"nodeType"in a&&a.nodeType==1){var b={};b.ELEMENT=J(a);return b}if(i(a))return z(a,I);a=n(a,function(c,d){return typeof d=="number"||typeof d=="string"});return o(a,I);default:return null}}
+function K(a,b){if(h(a)=="array")return z(a,function(c){return K(c,b)});else if(j(a))return"ELEMENT"in a?L(a.ELEMENT,b):o(a,function(c){return K(c,b)});return a}function M(a){a=a||document;var b=a.$wdc_;if(!b){b=a.$wdc_={};b.a=k()}return b}function J(a){var b=M(a.ownerDocument),c=p(b,function(d){return d==a});if(!c){c=":wdc:"+b.a++;b[c]=a}return c}
+function L(a,b){a=decodeURIComponent(a);var c=b||document,d=M(c);if(!(a in d))throw new q(10,"Element does not exist in cache");var e=d[a];for(var f=e;f;){if(f==c.documentElement)return e;f=f.parentNode}delete d[a];throw new q(10,"Element is no longer attached to the DOM");};function N(a,b,c){var d;try{if(typeof a=="string")a=new Function(a);var e=K(b),f=a.apply(null,e);d={status:0,value:I(f)}}catch(g){d={status:"code"in g?g.code:13,value:{message:g.message}}}if(c){a=[];E(new D,d,a);d=a.join("")}else d=d;return d}var O="_".split("."),P=this;!(O[0]in P)&&P.execScript&&P.execScript("var "+O[0]);for(var Q;O.length&&(Q=O.shift());)if(!O.length&&N!==undefined)P[Q]=N;else P=P[Q]?P[Q]:P[Q]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}
diff --git a/core/res/res/raw/find_element_android.js b/core/res/res/raw/find_element_android.js
new file mode 100644
index 0000000..f493223
--- /dev/null
+++ b/core/res/res/raw/find_element_android.js
@@ -0,0 +1,26 @@
+function(){return function(){var i=this;
+function j(a){var b=typeof a;if(b=="object")if(a){if(a instanceof Array)return"array";else if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if(c=="[object Window]")return"object";if(c=="[object Array]"||typeof a.length=="number"&&typeof a.splice!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("splice"))return"array";if(c=="[object Function]"||typeof a.call!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("call"))return"function"}else return"null";else if(b==
+"function"&&typeof a.call=="undefined")return"object";return b}function aa(a){var b=j(a);return b=="array"||b=="object"&&typeof a.length=="number"}function k(a){return typeof a=="string"}function l(a){return j(a)=="function"}function ba(a){a=j(a);return a=="object"||a=="array"||a=="function"}var ca=Date.now||function(){return+new Date};function m(a,b){function c(){}c.prototype=b.prototype;a.m=b.prototype;a.prototype=new c};function n(a){this.stack=Error().stack||"";if(a)this.message=String(a)}m(n,Error);n.prototype.name="CustomError";function da(a,b,c){var d={};for(var e in a)if(b.call(c,a[e],e,a))d[e]=a[e];return d}function ea(a,b,c){var d={};for(var e in a)d[e]=b.call(c,a[e],e,a);return d}function fa(a,b,c){for(var d in a)if(b.call(c,a[d],d,a))return d};function o(a,b){n.call(this,b);this.code=a;this.name=p[a]||p[13]}m(o,n);var p,ga={NoSuchElementError:7,NoSuchFrameError:8,UnknownCommandError:9,StaleElementReferenceError:10,ElementNotVisibleError:11,InvalidElementStateError:12,UnknownError:13,ElementNotSelectableError:15,XPathLookupError:19,NoSuchWindowError:23,InvalidCookieDomainError:24,UnableToSetCookieError:25,ModalDialogOpenedError:26,ModalDialogOpenError:27,ScriptTimeoutError:28},ha={};for(var ia in ga)ha[ga[ia]]=ia;p=ha;
+o.prototype.toString=function(){return"["+this.name+"] "+this.message};function ja(a){for(var b=1;b<arguments.length;b++){var c=String(arguments[b]).replace(/\$/g,"$$$$");a=a.replace(/\%s/,c)}return a}function q(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")}function r(a,b){if(a<b)return-1;else if(a>b)return 1;return 0}var ka={};function la(a){return ka[a]||(ka[a]=String(a).replace(/\-([a-z])/g,function(b,c){return c.toUpperCase()}))};function s(a,b){b.unshift(a);n.call(this,ja.apply(null,b));b.shift();this.n=a}m(s,n);s.prototype.name="AssertionError";function t(a,b){if(!a){var c=Array.prototype.slice.call(arguments,2),d="Assertion failed";if(b){d+=": "+b;var e=c}throw new s(""+d,e||[]);}return a};var u=Array.prototype,ma=u.indexOf?function(a,b,c){t(a.length!=null);return u.indexOf.call(a,b,c)}:function(a,b,c){c=c==null?0:c<0?Math.max(0,a.length+c):c;if(k(a)){if(!k(b)||b.length!=1)return-1;return a.indexOf(b,c)}for(c=c;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},na=u.forEach?function(a,b,c){t(a.length!=null);u.forEach.call(a,b,c)}:function(a,b,c){var d=a.length,e=k(a)?a.split(""):a;for(var f=0;f<d;f++)f in e&&b.call(c,e[f],f,a)},v=u.filter?function(a,b,c){t(a.length!=null);return u.filter.call(a,
+b,c)}:function(a,b,c){var d=a.length,e=[],f=0,g=k(a)?a.split(""):a;for(var h=0;h<d;h++)if(h in g){var G=g[h];if(b.call(c,G,h,a))e[f++]=G}return e},w=u.map?function(a,b,c){t(a.length!=null);return u.map.call(a,b,c)}:function(a,b,c){var d=a.length,e=Array(d),f=k(a)?a.split(""):a;for(var g=0;g<d;g++)if(g in f)e[g]=b.call(c,f[g],g,a);return e},oa=u.some?function(a,b,c){t(a.length!=null);return u.some.call(a,b,c)}:function(a,b,c){var d=a.length,e=k(a)?a.split(""):a;for(var f=0;f<d;f++)if(f in e&&b.call(c,
+e[f],f,a))return true;return false};function x(a,b,c){a:{var d=a.length,e=k(a)?a.split(""):a;for(var f=0;f<d;f++)if(f in e&&b.call(c,e[f],f,a)){b=f;break a}b=-1}return b<0?null:k(a)?a.charAt(b):a[b]};var A=true,pa,qa="",B;if(A)B=/WebKit\/(\S+)/;if(B){var ra=B.exec(i.navigator?i.navigator.userAgent:null);qa=ra?ra[1]:""}pa=qa;var sa={};var ta;function C(a,b){this.width=a;this.height=b}C.prototype.toString=function(){return"("+this.width+" x "+this.height+")"};function D(a){return a?new E(F(a)):ta||(ta=new E)}function H(a,b){if(a.contains&&b.nodeType==1)return a==b||a.contains(b);if(typeof a.compareDocumentPosition!="undefined")return a==b||Boolean(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a}function F(a){return a.nodeType==9?a:a.ownerDocument||a.document}function ua(a,b){var c=[];return va(a,b,c,true)?c[0]:undefined}
+function va(a,b,c,d){if(a!=null){var e=0;for(var f;f=a.childNodes[e];e++){if(b(f)){c.push(f);if(d)return true}if(va(f,b,c,d))return true}}return false}function wa(a,b,c,d){if(!c)a=a.parentNode;c=d==null;for(var e=0;a&&(c||e<=d);){if(b(a))return a;a=a.parentNode;e++}return null}function E(a){this.g=a||i.document||document}
+function I(a,b,c,d){a=d||a.g;b=b&&b!="*"?b.toUpperCase():"";if(d=a.querySelectorAll){if(d=a.querySelector){if(!(d=!A)){if(!(d=document.compatMode=="CSS1Compat")){if(!(d=sa["528"])){d=0;var e=q(String(pa)).split("."),f=q(String("528")).split("."),g=Math.max(e.length,f.length);for(var h=0;d==0&&h<g;h++){var G=e[h]||"",Oa=f[h]||"",Pa=RegExp("(\\d*)(\\D*)","g"),Qa=RegExp("(\\d*)(\\D*)","g");do{var y=Pa.exec(G)||["","",""],z=Qa.exec(Oa)||["","",""];if(y[0].length==0&&z[0].length==0)break;d=r(y[1].length==
+0?0:parseInt(y[1],10),z[1].length==0?0:parseInt(z[1],10))||r(y[2].length==0,z[2].length==0)||r(y[2],z[2])}while(d==0)}d=sa["528"]=d>=0}d=d}d=d}d=d}d=d}if(d&&(b||c))c=a.querySelectorAll(b+(c?"."+c:""));else if(c&&a.getElementsByClassName){a=a.getElementsByClassName(c);if(b){d={};f=e=0;for(;g=a[f];f++)if(b==g.nodeName)d[e++]=g;d.length=e;c=d}else c=a}else{a=a.getElementsByTagName(b||"*");if(c){d={};e=0;for(f=0;g=a[f];f++){b=g.className;if(typeof b.split=="function"&&ma(b.split(/\s+/),c)>=0)d[e++]=g}d.length=
+e;c=d}else c=a}return c}E.prototype.contains=H;function xa(){}
+function J(a,b,c){switch(typeof b){case "string":ya(a,b,c);break;case "number":c.push(isFinite(b)&&!isNaN(b)?b:"null");break;case "boolean":c.push(b);break;case "undefined":c.push("null");break;case "object":if(b==null){c.push("null");break}if(j(b)=="array"){var d=b.length;c.push("[");var e="";for(var f=0;f<d;f++){c.push(e);J(a,b[f],c);e=","}c.push("]");break}c.push("{");d="";for(e in b)if(Object.prototype.hasOwnProperty.call(b,e)){f=b[e];if(typeof f!="function"){c.push(d);ya(a,e,c);c.push(":");J(a,
+f,c);d=","}}c.push("}");break;case "function":break;default:throw Error("Unknown type: "+typeof b);}}var K={'"':'\\"',"\\":"\\\\","/":"\\/","\u0008":"\\b","\u000c":"\\f","\n":"\\n","\r":"\\r","\t":"\\t","\u000b":"\\u000b"},za=/\uffff/.test("\uffff")?/[\\\"\x00-\x1f\x7f-\uffff]/g:/[\\\"\x00-\x1f\x7f-\xff]/g;
+function ya(a,b,c){c.push('"',b.replace(za,function(d){if(d in K)return K[d];var e=d.charCodeAt(0),f="\\u";if(e<16)f+="000";else if(e<256)f+="00";else if(e<4096)f+="0";return K[d]=f+e.toString(16)}),'"')};function L(a){switch(j(a)){case "string":case "number":case "boolean":return a;case "function":return a.toString();case "array":return w(a,L);case "object":a=a;if("tagName"in a&&"nodeType"in a&&a.nodeType==1){var b={};b.ELEMENT=Aa(a);return b}if(aa(a))return w(a,L);a=da(a,function(c,d){return typeof d=="number"||k(d)});return ea(a,L);default:return null}}
+function M(a,b){if(j(a)=="array")return w(a,function(c){return M(c,b)});else if(ba(a))return"ELEMENT"in a?Ba(a.ELEMENT,b):ea(a,function(c){return M(c,b)});return a}function Ca(a){a=a||document;var b=a.$wdc_;if(!b){b=a.$wdc_={};b.l=ca()}return b}function Aa(a){var b=Ca(a.ownerDocument),c=fa(b,function(d){return d==a});if(!c){c=":wdc:"+b.l++;b[c]=a}return c}
+function Ba(a,b){a=decodeURIComponent(a);var c=b||document,d=Ca(c);if(!(a in d))throw new o(10,"Element does not exist in cache");var e=d[a];for(var f=e;f;){if(f==c.documentElement)return e;f=f.parentNode}delete d[a];throw new o(10,"Element is no longer attached to the DOM");};var Da=window;var N={};N.b=function(a,b){if(!a)throw Error("No class name specified");a=q(a);if(a.split(/\s+/).length>1)throw Error("Compound class names not permitted");var c=I(D(b),"*",a,b);return c.length?c[0]:null};N.e=function(a,b){if(!a)throw Error("No class name specified");a=q(a);if(a.split(/\s+/).length>1)throw Error("Compound class names not permitted");return I(D(b),"*",a,b)};var O={};O.b=function(a,b){if(!l(b.querySelector)&&0)throw Error("CSS selection is not supported");if(!a)throw Error("No selector specified");if(a.split(/,/).length>1)throw Error("Compound selectors not permitted");a=q(a);var c=b.querySelector(a);return c&&c.nodeType==1?c:null};O.e=function(a,b){if(!l(b.querySelectorAll)&&0)throw Error("CSS selection is not supported");if(!a)throw Error("No selector specified");if(a.split(/,/).length>1)throw Error("Compound selectors not permitted");a=q(a);return b.querySelectorAll(a)};function Ea(a,b){if(typeof a.selectNodes!="undefined"){var c=F(a);typeof c.setProperty!="undefined"&&c.setProperty("SelectionLanguage","XPath");return a.selectNodes(b)}else if(document.implementation.hasFeature("XPath","3.0")){c=F(a);var d=c.createNSResolver(c.documentElement);c=c.evaluate(b,a,d,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null);d=[];var e=c.snapshotLength;for(var f=0;f<e;f++)d.push(c.snapshotItem(f));return d}else return[]};var P={};P.b=function(a,b){try{var c;if(typeof b.selectSingleNode!="undefined"){var d=F(b);typeof d.setProperty!="undefined"&&d.setProperty("SelectionLanguage","XPath");c=b.selectSingleNode(a)}else if(document.implementation.hasFeature("XPath","3.0")){d=F(b);var e=d.createNSResolver(d.documentElement);c=d.evaluate(a,b,e,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue}else c=null}catch(f){return null}if(!c)return null;if(c.nodeType!=1)throw Error("Returned node is not an element: "+a);return c};
+P.e=function(a,b){var c=Ea(b,a);na(c,function(d){if(d.nodeType!=1)throw Error("Returned nodes must be elements: "+a);});return c};var Fa="StopIteration"in i?i.StopIteration:Error("StopIteration");function Ga(){}Ga.prototype.next=function(){throw Fa;};function Q(a,b,c,d,e){this.a=!!b;a&&R(this,a,d);this.f=e!=undefined?e:this.d||0;if(this.a)this.f*=-1;this.k=!c}m(Q,Ga);Q.prototype.c=null;Q.prototype.d=0;Q.prototype.j=false;function R(a,b,c,d){if(a.c=b)a.d=typeof c=="number"?c:a.c.nodeType!=1?0:a.a?-1:1;if(typeof d=="number")a.f=d}
+Q.prototype.next=function(){var a;if(this.j){if(!this.c||this.k&&this.f==0)throw Fa;a=this.c;var b=this.a?-1:1;if(this.d==b){var c=this.a?a.lastChild:a.firstChild;c?R(this,c):R(this,a,b*-1)}else(c=this.a?a.previousSibling:a.nextSibling)?R(this,c):R(this,a.parentNode,b*-1);this.f+=this.d*(this.a?-1:1)}else this.j=true;a=this.c;if(!this.c)throw Fa;return a};
+Q.prototype.splice=function(){var a=this.c,b=this.a?1:-1;if(this.d==b){this.d=b*-1;this.f+=this.d*(this.a?-1:1)}this.a=!this.a;Q.prototype.next.call(this);this.a=!this.a;b=aa(arguments[0])?arguments[0]:arguments;for(var c=b.length-1;c>=0;c--)a.parentNode&&a.parentNode.insertBefore(b[c],a.nextSibling);a&&a.parentNode&&a.parentNode.removeChild(a)};function Ha(a,b,c,d){Q.call(this,a,b,c,null,d)}m(Ha,Q);Ha.prototype.next=function(){do Ha.m.next.call(this);while(this.d==-1);return this.c};function Ia(a,b){var c=F(a);if(c.defaultView&&c.defaultView.getComputedStyle)if(c=c.defaultView.getComputedStyle(a,null))return c[b]||c.getPropertyValue(b);return""};function S(a,b){return!!a&&a.nodeType==1&&(!b||a.tagName.toUpperCase()==b)}
+var Ja=["async","autofocus","autoplay","checked","compact","complete","controls","declare","defaultchecked","defaultselected","defer","disabled","draggable","ended","formnovalidate","hidden","indeterminate","iscontenteditable","ismap","itemscope","loop","multiple","muted","nohref","noresize","noshade","novalidate","nowrap","open","paused","pubdate","readonly","required","reversed","scoped","seamless","seeking","selected","spellcheck","truespeed","willvalidate"];
+function T(a,b){if(8==a.nodeType)return null;b=b.toLowerCase();if(b=="style"){var c=q(a.style.cssText).toLowerCase();return c.charAt(c.length-1)==";"?c:c+";"}c=a.getAttributeNode(b);if(!c)return null;if(ma(Ja,b)>=0)return"true";return c.specified?c.value:null}function U(a){for(a=a.parentNode;a&&a.nodeType!=1&&a.nodeType!=9&&a.nodeType!=11;)a=a.parentNode;return S(a)?a:null}function V(a,b){b=la(String(b));return Ia(a,b)||Ka(a,b)}
+function Ka(a,b){var c=(a.currentStyle||a.style)[b];if(c!="inherit")return c!==undefined?c:null;return(c=U(a))?Ka(c,b):null}
+function La(a){if(l(a.getBBox))return a.getBBox();var b;if((Ia(a,"display")||(a.currentStyle?a.currentStyle.display:null)||a.style.display)!="none")b=new C(a.offsetWidth,a.offsetHeight);else{b=a.style;var c=b.display,d=b.visibility,e=b.position;b.visibility="hidden";b.position="absolute";b.display="inline";var f;f=a.offsetWidth;a=a.offsetHeight;b.display=c;b.position=e;b.visibility=d;b=new C(f,a)}return b}
+function W(a,b){function c(f){if(V(f,"display")=="none")return false;f=U(f);return!f||c(f)}function d(f){var g=La(f);if(g.height>0&&g.width>0)return true;if(f.innerText||f.textContent)if(Ma.test(f.innerText||f.textContent))return true;return A&&oa(f.childNodes,function(h){return S(h)&&d(h)})}if(!S(a))throw Error("Argument to isShown must be of type Element");if(S(a,"TITLE"))return(F(a)?F(a).parentWindow||F(a).defaultView:window)==Da;if(S(a,"OPTION")||S(a,"OPTGROUP")){var e=wa(a,function(f){return S(f,
+"SELECT")});return!!e&&W(e)}if(S(a,"MAP")){if(!a.name)return false;e=F(a);e=e.evaluate?P.b('/descendant::*[@usemap = "#'+a.name+'"]',e):ua(e,function(f){return S(f)&&T(f,"usemap")=="#"+a.name});return!!e&&W(e)}if(S(a,"AREA")){e=wa(a,function(f){return S(f,"MAP")});return!!e&&W(e)}if(S(a,"INPUT")&&a.type.toLowerCase()=="hidden")return false;if(V(a,"visibility")=="hidden")return false;if(!c(a))return false;if(!b&&Na(a)==0)return false;if(!d(a))return false;return true}
+function Ra(a){var b=[""];Sa(a,b);b=w(b,q);return q(b.join("\n"))}function Sa(a,b){if(S(a,"BR"))b.push("");else{var c=Ta(a);c&&b[b.length-1]&&b.push("");na(a.childNodes,function(d){if(d.nodeType==3){var e=U(d);if(e){W(e);if(e&&W(e)){d=d.nodeValue.replace(Ua," ");e=b.pop();var f=e.length-1;if(f>=0&&e.indexOf(" ",f)==f&&d.lastIndexOf(" ",0)==0)d=d.substr(1);b.push(e+d)}}}else S(d)&&Sa(d,b)});c&&b[b.length-1]&&b.push("")}}function Ta(a){a=V(a,"display");return a=="block"||a=="inline-block"}
+var Va="[\\s\\xa0"+String.fromCharCode(160)+"]+",Ua=RegExp(Va,"g"),Ma=RegExp("^"+Va+"$");function Na(a){var b=1,c=V(a,"opacity");if(c)b=Number(c);if(a=U(a))b*=Na(a);return b};var Wa={};Wa.b=function(a,b){var c=D(b),d=k(a)?c.g.getElementById(a):a;if(!d)return null;if(T(d,"id")==a&&H(b,d))return d;c=I(c,"*");return x(c,function(e){return T(e,"id")==a&&H(b,e)})};Wa.e=function(a,b){var c=I(D(b),"*",null,b);return v(c,function(d){return T(d,"id")==a})};var X={},Xa={};X.i=function(a,b,c){b=I(D(b),"A",null,b);return x(b,function(d){d=Ra(d);return c&&d.indexOf(a)!=-1||d==a})};X.h=function(a,b,c){b=I(D(b),"A",null,b);return v(b,function(d){d=Ra(d);return c&&d.indexOf(a)!=-1||d==a})};X.b=function(a,b){return X.i(a,b,false)};X.e=function(a,b){return X.h(a,b,false)};Xa.b=function(a,b){return X.i(a,b,true)};Xa.e=function(a,b){return X.h(a,b,true)};var Ya={};Ya.b=function(a,b){var c=I(D(b),"*",null,b);return x(c,function(d){return T(d,"name")==a})};Ya.e=function(a,b){var c=I(D(b),"*",null,b);return v(c,function(d){return T(d,"name")==a})};var Za={};Za.b=function(a,b){return I(D(b),a,null,b)[0]||null};Za.e=function(a,b){return I(D(b),a,null,b)};var $a={className:N,css:O,id:Wa,linkText:X,name:Ya,partialLinkText:Xa,tagName:Za,xpath:P};function ab(a,b){var c;a:{for(c in a){c=c;break a}c=void 0}if(c){var d=$a[c];if(d&&l(d.b))return d.b(a[c],b||F(Da))}throw Error("Unsupported locator strategy: "+c);};function bb(a,b,c){var d={};d[a]=b;a=ab;c=[d,c];var e;try{if(k(a))a=new Function(a);var f=M(c),g=a.apply(null,f);e={status:0,value:L(g)}}catch(h){e={status:"code"in h?h.code:13,value:{message:h.message}}}f=[];J(new xa,e,f);return f.join("")}var Y="_".split("."),Z=i;!(Y[0]in Z)&&Z.execScript&&Z.execScript("var "+Y[0]);for(var $;Y.length&&($=Y.shift());)if(!Y.length&&bb!==undefined)Z[$]=bb;else Z=Z[$]?Z[$]:Z[$]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}
diff --git a/core/res/res/raw/find_elements_android.js b/core/res/res/raw/find_elements_android.js
new file mode 100644
index 0000000..796da58
--- /dev/null
+++ b/core/res/res/raw/find_elements_android.js
@@ -0,0 +1,26 @@
+function(){return function(){var i=this;
+function j(a){var b=typeof a;if(b=="object")if(a){if(a instanceof Array)return"array";else if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if(c=="[object Window]")return"object";if(c=="[object Array]"||typeof a.length=="number"&&typeof a.splice!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("splice"))return"array";if(c=="[object Function]"||typeof a.call!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("call"))return"function"}else return"null";else if(b==
+"function"&&typeof a.call=="undefined")return"object";return b}function aa(a){var b=j(a);return b=="array"||b=="object"&&typeof a.length=="number"}function k(a){return typeof a=="string"}function l(a){return j(a)=="function"}function ba(a){a=j(a);return a=="object"||a=="array"||a=="function"}var ca=Date.now||function(){return+new Date};function m(a,b){function c(){}c.prototype=b.prototype;a.m=b.prototype;a.prototype=new c};function n(a){this.stack=Error().stack||"";if(a)this.message=String(a)}m(n,Error);n.prototype.name="CustomError";function da(a,b,c){var d={};for(var e in a)if(b.call(c,a[e],e,a))d[e]=a[e];return d}function ea(a,b,c){var d={};for(var e in a)d[e]=b.call(c,a[e],e,a);return d}function fa(a,b,c){for(var d in a)if(b.call(c,a[d],d,a))return d};function o(a,b){n.call(this,b);this.code=a;this.name=p[a]||p[13]}m(o,n);var p,ga={NoSuchElementError:7,NoSuchFrameError:8,UnknownCommandError:9,StaleElementReferenceError:10,ElementNotVisibleError:11,InvalidElementStateError:12,UnknownError:13,ElementNotSelectableError:15,XPathLookupError:19,NoSuchWindowError:23,InvalidCookieDomainError:24,UnableToSetCookieError:25,ModalDialogOpenedError:26,ModalDialogOpenError:27,ScriptTimeoutError:28},ha={};for(var ia in ga)ha[ga[ia]]=ia;p=ha;
+o.prototype.toString=function(){return"["+this.name+"] "+this.message};function ja(a){for(var b=1;b<arguments.length;b++){var c=String(arguments[b]).replace(/\$/g,"$$$$");a=a.replace(/\%s/,c)}return a}function q(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")}function r(a,b){if(a<b)return-1;else if(a>b)return 1;return 0}var ka={};function la(a){return ka[a]||(ka[a]=String(a).replace(/\-([a-z])/g,function(b,c){return c.toUpperCase()}))};function s(a,b){b.unshift(a);n.call(this,ja.apply(null,b));b.shift();this.n=a}m(s,n);s.prototype.name="AssertionError";function t(a,b){if(!a){var c=Array.prototype.slice.call(arguments,2),d="Assertion failed";if(b){d+=": "+b;var e=c}throw new s(""+d,e||[]);}return a};var u=Array.prototype,ma=u.indexOf?function(a,b,c){t(a.length!=null);return u.indexOf.call(a,b,c)}:function(a,b,c){c=c==null?0:c<0?Math.max(0,a.length+c):c;if(k(a)){if(!k(b)||b.length!=1)return-1;return a.indexOf(b,c)}for(c=c;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},na=u.forEach?function(a,b,c){t(a.length!=null);u.forEach.call(a,b,c)}:function(a,b,c){var d=a.length,e=k(a)?a.split(""):a;for(var f=0;f<d;f++)f in e&&b.call(c,e[f],f,a)},v=u.filter?function(a,b,c){t(a.length!=null);return u.filter.call(a,
+b,c)}:function(a,b,c){var d=a.length,e=[],f=0,g=k(a)?a.split(""):a;for(var h=0;h<d;h++)if(h in g){var G=g[h];if(b.call(c,G,h,a))e[f++]=G}return e},w=u.map?function(a,b,c){t(a.length!=null);return u.map.call(a,b,c)}:function(a,b,c){var d=a.length,e=Array(d),f=k(a)?a.split(""):a;for(var g=0;g<d;g++)if(g in f)e[g]=b.call(c,f[g],g,a);return e},oa=u.some?function(a,b,c){t(a.length!=null);return u.some.call(a,b,c)}:function(a,b,c){var d=a.length,e=k(a)?a.split(""):a;for(var f=0;f<d;f++)if(f in e&&b.call(c,
+e[f],f,a))return true;return false};function x(a,b,c){a:{var d=a.length,e=k(a)?a.split(""):a;for(var f=0;f<d;f++)if(f in e&&b.call(c,e[f],f,a)){b=f;break a}b=-1}return b<0?null:k(a)?a.charAt(b):a[b]};var A=true,pa,qa="",B;if(A)B=/WebKit\/(\S+)/;if(B){var ra=B.exec(i.navigator?i.navigator.userAgent:null);qa=ra?ra[1]:""}pa=qa;var sa={};var ta;function C(a,b){this.width=a;this.height=b}C.prototype.toString=function(){return"("+this.width+" x "+this.height+")"};function D(a){return a?new E(F(a)):ta||(ta=new E)}function H(a,b){if(a.contains&&b.nodeType==1)return a==b||a.contains(b);if(typeof a.compareDocumentPosition!="undefined")return a==b||Boolean(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a}function F(a){return a.nodeType==9?a:a.ownerDocument||a.document}function ua(a,b){var c=[];return va(a,b,c,true)?c[0]:undefined}
+function va(a,b,c,d){if(a!=null){var e=0;for(var f;f=a.childNodes[e];e++){if(b(f)){c.push(f);if(d)return true}if(va(f,b,c,d))return true}}return false}function wa(a,b,c,d){if(!c)a=a.parentNode;c=d==null;for(var e=0;a&&(c||e<=d);){if(b(a))return a;a=a.parentNode;e++}return null}function E(a){this.g=a||i.document||document}
+function I(a,b,c,d){a=d||a.g;b=b&&b!="*"?b.toUpperCase():"";if(d=a.querySelectorAll){if(d=a.querySelector){if(!(d=!A)){if(!(d=document.compatMode=="CSS1Compat")){if(!(d=sa["528"])){d=0;var e=q(String(pa)).split("."),f=q(String("528")).split("."),g=Math.max(e.length,f.length);for(var h=0;d==0&&h<g;h++){var G=e[h]||"",Oa=f[h]||"",Pa=RegExp("(\\d*)(\\D*)","g"),Qa=RegExp("(\\d*)(\\D*)","g");do{var y=Pa.exec(G)||["","",""],z=Qa.exec(Oa)||["","",""];if(y[0].length==0&&z[0].length==0)break;d=r(y[1].length==
+0?0:parseInt(y[1],10),z[1].length==0?0:parseInt(z[1],10))||r(y[2].length==0,z[2].length==0)||r(y[2],z[2])}while(d==0)}d=sa["528"]=d>=0}d=d}d=d}d=d}d=d}if(d&&(b||c))c=a.querySelectorAll(b+(c?"."+c:""));else if(c&&a.getElementsByClassName){a=a.getElementsByClassName(c);if(b){d={};f=e=0;for(;g=a[f];f++)if(b==g.nodeName)d[e++]=g;d.length=e;c=d}else c=a}else{a=a.getElementsByTagName(b||"*");if(c){d={};e=0;for(f=0;g=a[f];f++){b=g.className;if(typeof b.split=="function"&&ma(b.split(/\s+/),c)>=0)d[e++]=g}d.length=
+e;c=d}else c=a}return c}E.prototype.contains=H;function xa(){}
+function J(a,b,c){switch(typeof b){case "string":ya(a,b,c);break;case "number":c.push(isFinite(b)&&!isNaN(b)?b:"null");break;case "boolean":c.push(b);break;case "undefined":c.push("null");break;case "object":if(b==null){c.push("null");break}if(j(b)=="array"){var d=b.length;c.push("[");var e="";for(var f=0;f<d;f++){c.push(e);J(a,b[f],c);e=","}c.push("]");break}c.push("{");d="";for(e in b)if(Object.prototype.hasOwnProperty.call(b,e)){f=b[e];if(typeof f!="function"){c.push(d);ya(a,e,c);c.push(":");J(a,
+f,c);d=","}}c.push("}");break;case "function":break;default:throw Error("Unknown type: "+typeof b);}}var K={'"':'\\"',"\\":"\\\\","/":"\\/","\u0008":"\\b","\u000c":"\\f","\n":"\\n","\r":"\\r","\t":"\\t","\u000b":"\\u000b"},za=/\uffff/.test("\uffff")?/[\\\"\x00-\x1f\x7f-\uffff]/g:/[\\\"\x00-\x1f\x7f-\xff]/g;
+function ya(a,b,c){c.push('"',b.replace(za,function(d){if(d in K)return K[d];var e=d.charCodeAt(0),f="\\u";if(e<16)f+="000";else if(e<256)f+="00";else if(e<4096)f+="0";return K[d]=f+e.toString(16)}),'"')};function L(a){switch(j(a)){case "string":case "number":case "boolean":return a;case "function":return a.toString();case "array":return w(a,L);case "object":a=a;if("tagName"in a&&"nodeType"in a&&a.nodeType==1){var b={};b.ELEMENT=Aa(a);return b}if(aa(a))return w(a,L);a=da(a,function(c,d){return typeof d=="number"||k(d)});return ea(a,L);default:return null}}
+function M(a,b){if(j(a)=="array")return w(a,function(c){return M(c,b)});else if(ba(a))return"ELEMENT"in a?Ba(a.ELEMENT,b):ea(a,function(c){return M(c,b)});return a}function Ca(a){a=a||document;var b=a.$wdc_;if(!b){b=a.$wdc_={};b.l=ca()}return b}function Aa(a){var b=Ca(a.ownerDocument),c=fa(b,function(d){return d==a});if(!c){c=":wdc:"+b.l++;b[c]=a}return c}
+function Ba(a,b){a=decodeURIComponent(a);var c=b||document,d=Ca(c);if(!(a in d))throw new o(10,"Element does not exist in cache");var e=d[a];for(var f=e;f;){if(f==c.documentElement)return e;f=f.parentNode}delete d[a];throw new o(10,"Element is no longer attached to the DOM");};var Da=window;var N={};N.d=function(a,b){if(!a)throw Error("No class name specified");a=q(a);if(a.split(/\s+/).length>1)throw Error("Compound class names not permitted");var c=I(D(b),"*",a,b);return c.length?c[0]:null};N.b=function(a,b){if(!a)throw Error("No class name specified");a=q(a);if(a.split(/\s+/).length>1)throw Error("Compound class names not permitted");return I(D(b),"*",a,b)};var O={};O.d=function(a,b){if(!l(b.querySelector)&&0)throw Error("CSS selection is not supported");if(!a)throw Error("No selector specified");if(a.split(/,/).length>1)throw Error("Compound selectors not permitted");a=q(a);var c=b.querySelector(a);return c&&c.nodeType==1?c:null};O.b=function(a,b){if(!l(b.querySelectorAll)&&0)throw Error("CSS selection is not supported");if(!a)throw Error("No selector specified");if(a.split(/,/).length>1)throw Error("Compound selectors not permitted");a=q(a);return b.querySelectorAll(a)};function Ea(a,b){if(typeof a.selectNodes!="undefined"){var c=F(a);typeof c.setProperty!="undefined"&&c.setProperty("SelectionLanguage","XPath");return a.selectNodes(b)}else if(document.implementation.hasFeature("XPath","3.0")){c=F(a);var d=c.createNSResolver(c.documentElement);c=c.evaluate(b,a,d,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null);d=[];var e=c.snapshotLength;for(var f=0;f<e;f++)d.push(c.snapshotItem(f));return d}else return[]};var P={};P.d=function(a,b){try{var c;if(typeof b.selectSingleNode!="undefined"){var d=F(b);typeof d.setProperty!="undefined"&&d.setProperty("SelectionLanguage","XPath");c=b.selectSingleNode(a)}else if(document.implementation.hasFeature("XPath","3.0")){d=F(b);var e=d.createNSResolver(d.documentElement);c=d.evaluate(a,b,e,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue}else c=null}catch(f){return null}if(!c)return null;if(c.nodeType!=1)throw Error("Returned node is not an element: "+a);return c};
+P.b=function(a,b){var c=Ea(b,a);na(c,function(d){if(d.nodeType!=1)throw Error("Returned nodes must be elements: "+a);});return c};var Fa="StopIteration"in i?i.StopIteration:Error("StopIteration");function Ga(){}Ga.prototype.next=function(){throw Fa;};function Q(a,b,c,d,e){this.a=!!b;a&&R(this,a,d);this.f=e!=undefined?e:this.e||0;if(this.a)this.f*=-1;this.k=!c}m(Q,Ga);Q.prototype.c=null;Q.prototype.e=0;Q.prototype.j=false;function R(a,b,c,d){if(a.c=b)a.e=typeof c=="number"?c:a.c.nodeType!=1?0:a.a?-1:1;if(typeof d=="number")a.f=d}
+Q.prototype.next=function(){var a;if(this.j){if(!this.c||this.k&&this.f==0)throw Fa;a=this.c;var b=this.a?-1:1;if(this.e==b){var c=this.a?a.lastChild:a.firstChild;c?R(this,c):R(this,a,b*-1)}else(c=this.a?a.previousSibling:a.nextSibling)?R(this,c):R(this,a.parentNode,b*-1);this.f+=this.e*(this.a?-1:1)}else this.j=true;a=this.c;if(!this.c)throw Fa;return a};
+Q.prototype.splice=function(){var a=this.c,b=this.a?1:-1;if(this.e==b){this.e=b*-1;this.f+=this.e*(this.a?-1:1)}this.a=!this.a;Q.prototype.next.call(this);this.a=!this.a;b=aa(arguments[0])?arguments[0]:arguments;for(var c=b.length-1;c>=0;c--)a.parentNode&&a.parentNode.insertBefore(b[c],a.nextSibling);a&&a.parentNode&&a.parentNode.removeChild(a)};function Ha(a,b,c,d){Q.call(this,a,b,c,null,d)}m(Ha,Q);Ha.prototype.next=function(){do Ha.m.next.call(this);while(this.e==-1);return this.c};function Ia(a,b){var c=F(a);if(c.defaultView&&c.defaultView.getComputedStyle)if(c=c.defaultView.getComputedStyle(a,null))return c[b]||c.getPropertyValue(b);return""};function S(a,b){return!!a&&a.nodeType==1&&(!b||a.tagName.toUpperCase()==b)}
+var Ja=["async","autofocus","autoplay","checked","compact","complete","controls","declare","defaultchecked","defaultselected","defer","disabled","draggable","ended","formnovalidate","hidden","indeterminate","iscontenteditable","ismap","itemscope","loop","multiple","muted","nohref","noresize","noshade","novalidate","nowrap","open","paused","pubdate","readonly","required","reversed","scoped","seamless","seeking","selected","spellcheck","truespeed","willvalidate"];
+function T(a,b){if(8==a.nodeType)return null;b=b.toLowerCase();if(b=="style"){var c=q(a.style.cssText).toLowerCase();return c.charAt(c.length-1)==";"?c:c+";"}c=a.getAttributeNode(b);if(!c)return null;if(ma(Ja,b)>=0)return"true";return c.specified?c.value:null}function U(a){for(a=a.parentNode;a&&a.nodeType!=1&&a.nodeType!=9&&a.nodeType!=11;)a=a.parentNode;return S(a)?a:null}function V(a,b){b=la(String(b));return Ia(a,b)||Ka(a,b)}
+function Ka(a,b){var c=(a.currentStyle||a.style)[b];if(c!="inherit")return c!==undefined?c:null;return(c=U(a))?Ka(c,b):null}
+function La(a){if(l(a.getBBox))return a.getBBox();var b;if((Ia(a,"display")||(a.currentStyle?a.currentStyle.display:null)||a.style.display)!="none")b=new C(a.offsetWidth,a.offsetHeight);else{b=a.style;var c=b.display,d=b.visibility,e=b.position;b.visibility="hidden";b.position="absolute";b.display="inline";var f;f=a.offsetWidth;a=a.offsetHeight;b.display=c;b.position=e;b.visibility=d;b=new C(f,a)}return b}
+function W(a,b){function c(f){if(V(f,"display")=="none")return false;f=U(f);return!f||c(f)}function d(f){var g=La(f);if(g.height>0&&g.width>0)return true;if(f.innerText||f.textContent)if(Ma.test(f.innerText||f.textContent))return true;return A&&oa(f.childNodes,function(h){return S(h)&&d(h)})}if(!S(a))throw Error("Argument to isShown must be of type Element");if(S(a,"TITLE"))return(F(a)?F(a).parentWindow||F(a).defaultView:window)==Da;if(S(a,"OPTION")||S(a,"OPTGROUP")){var e=wa(a,function(f){return S(f,
+"SELECT")});return!!e&&W(e)}if(S(a,"MAP")){if(!a.name)return false;e=F(a);e=e.evaluate?P.d('/descendant::*[@usemap = "#'+a.name+'"]',e):ua(e,function(f){return S(f)&&T(f,"usemap")=="#"+a.name});return!!e&&W(e)}if(S(a,"AREA")){e=wa(a,function(f){return S(f,"MAP")});return!!e&&W(e)}if(S(a,"INPUT")&&a.type.toLowerCase()=="hidden")return false;if(V(a,"visibility")=="hidden")return false;if(!c(a))return false;if(!b&&Na(a)==0)return false;if(!d(a))return false;return true}
+function Ra(a){var b=[""];Sa(a,b);b=w(b,q);return q(b.join("\n"))}function Sa(a,b){if(S(a,"BR"))b.push("");else{var c=Ta(a);c&&b[b.length-1]&&b.push("");na(a.childNodes,function(d){if(d.nodeType==3){var e=U(d);if(e){W(e);if(e&&W(e)){d=d.nodeValue.replace(Ua," ");e=b.pop();var f=e.length-1;if(f>=0&&e.indexOf(" ",f)==f&&d.lastIndexOf(" ",0)==0)d=d.substr(1);b.push(e+d)}}}else S(d)&&Sa(d,b)});c&&b[b.length-1]&&b.push("")}}function Ta(a){a=V(a,"display");return a=="block"||a=="inline-block"}
+var Va="[\\s\\xa0"+String.fromCharCode(160)+"]+",Ua=RegExp(Va,"g"),Ma=RegExp("^"+Va+"$");function Na(a){var b=1,c=V(a,"opacity");if(c)b=Number(c);if(a=U(a))b*=Na(a);return b};var Wa={};Wa.d=function(a,b){var c=D(b),d=k(a)?c.g.getElementById(a):a;if(!d)return null;if(T(d,"id")==a&&H(b,d))return d;c=I(c,"*");return x(c,function(e){return T(e,"id")==a&&H(b,e)})};Wa.b=function(a,b){var c=I(D(b),"*",null,b);return v(c,function(d){return T(d,"id")==a})};var X={},Xa={};X.i=function(a,b,c){b=I(D(b),"A",null,b);return x(b,function(d){d=Ra(d);return c&&d.indexOf(a)!=-1||d==a})};X.h=function(a,b,c){b=I(D(b),"A",null,b);return v(b,function(d){d=Ra(d);return c&&d.indexOf(a)!=-1||d==a})};X.d=function(a,b){return X.i(a,b,false)};X.b=function(a,b){return X.h(a,b,false)};Xa.d=function(a,b){return X.i(a,b,true)};Xa.b=function(a,b){return X.h(a,b,true)};var Ya={};Ya.d=function(a,b){var c=I(D(b),"*",null,b);return x(c,function(d){return T(d,"name")==a})};Ya.b=function(a,b){var c=I(D(b),"*",null,b);return v(c,function(d){return T(d,"name")==a})};var Za={};Za.d=function(a,b){return I(D(b),a,null,b)[0]||null};Za.b=function(a,b){return I(D(b),a,null,b)};var $a={className:N,css:O,id:Wa,linkText:X,name:Ya,partialLinkText:Xa,tagName:Za,xpath:P};function ab(a,b){var c;a:{for(c in a){c=c;break a}c=void 0}if(c){var d=$a[c];if(d&&l(d.b))return d.b(a[c],b||F(Da))}throw Error("Unsupported locator strategy: "+c);};function bb(a,b,c){var d={};d[a]=b;a=ab;c=[d,c];var e;try{if(k(a))a=new Function(a);var f=M(c),g=a.apply(null,f);e={status:0,value:L(g)}}catch(h){e={status:"code"in h?h.code:13,value:{message:h.message}}}f=[];J(new xa,e,f);return f.join("")}var Y="_".split("."),Z=i;!(Y[0]in Z)&&Z.execScript&&Z.execScript("var "+Y[0]);for(var $;Y.length&&($=Y.shift());)if(!Y.length&&bb!==undefined)Z[$]=bb;else Z=Z[$]?Z[$]:Z[$]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}
diff --git a/core/res/res/raw/get_attribute_value_android.js b/core/res/res/raw/get_attribute_value_android.js
new file mode 100644
index 0000000..e96a868
--- /dev/null
+++ b/core/res/res/raw/get_attribute_value_android.js
@@ -0,0 +1,11 @@
+function(){return function(){function g(a){var c=typeof a;if(c=="object")if(a){if(a instanceof Array)return"array";else if(a instanceof Object)return c;var b=Object.prototype.toString.call(a);if(b=="[object Window]")return"object";if(b=="[object Array]"||typeof a.length=="number"&&typeof a.splice!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("splice"))return"array";if(b=="[object Function]"||typeof a.call!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
+else if(c=="function"&&typeof a.call=="undefined")return"object";return c}function i(a){var c=g(a);return c=="array"||c=="object"&&typeof a.length=="number"}function j(a){return typeof a=="string"}function k(a){a=g(a);return a=="object"||a=="array"||a=="function"}Math.floor(Math.random()*2147483648).toString(36);var l=Date.now||function(){return+new Date};function m(a,c){function b(){}b.prototype=c.prototype;a.h=c.prototype;a.prototype=new b};function n(a){this.stack=Error().stack||"";if(a)this.message=String(a)}m(n,Error);n.prototype.name="CustomError";function o(a,c,b){var d={};for(var e in a)if(c.call(b,a[e],e,a))d[e]=a[e];return d}function p(a,c,b){var d={};for(var e in a)d[e]=c.call(b,a[e],e,a);return d}function q(a,c,b){for(var d in a)if(c.call(b,a[d],d,a))return d};function r(a,c){n.call(this,c);this.code=a;this.name=s[a]||s[13]}m(r,n);var s,t={NoSuchElementError:7,NoSuchFrameError:8,UnknownCommandError:9,StaleElementReferenceError:10,ElementNotVisibleError:11,InvalidElementStateError:12,UnknownError:13,ElementNotSelectableError:15,XPathLookupError:19,NoSuchWindowError:23,InvalidCookieDomainError:24,UnableToSetCookieError:25,ModalDialogOpenedError:26,ModalDialogOpenError:27,ScriptTimeoutError:28},u={};for(var v in t)u[t[v]]=v;s=u;
+r.prototype.toString=function(){return"["+this.name+"] "+this.message};function w(a){for(var c=1;c<arguments.length;c++){var b=String(arguments[c]).replace(/\$/g,"$$$$");a=a.replace(/\%s/,b)}return a};function x(a,c){c.unshift(a);n.call(this,w.apply(null,c));c.shift();this.i=a}m(x,n);x.prototype.name="AssertionError";function y(a,c){if(!a){var b=Array.prototype.slice.call(arguments,2),d="Assertion failed";if(c){d+=": "+c;var e=b}throw new x(""+d,e||[]);}return a};var z=Array.prototype,A=z.indexOf?function(a,c,b){y(a.length!=null);return z.indexOf.call(a,c,b)}:function(a,c,b){b=b==null?0:b<0?Math.max(0,a.length+b):b;if(j(a)){if(!j(c)||c.length!=1)return-1;return a.indexOf(c,b)}for(b=b;b<a.length;b++)if(b in a&&a[b]===c)return b;return-1},C=z.map?function(a,c,b){y(a.length!=null);return z.map.call(a,c,b)}:function(a,c,b){var d=a.length,e=Array(d),f=j(a)?a.split(""):a;for(var h=0;h<d;h++)if(h in f)e[h]=c.call(b,f[h],h,a);return e};var D="",E;if(E=/WebKit\/(\S+)/){var F=E.exec(this.navigator?this.navigator.userAgent:null);D=F?F[1]:""};var G="StopIteration"in this?this.StopIteration:Error("StopIteration");function H(){}H.prototype.next=function(){throw G;};function I(a,c,b,d,e){this.a=!!c;a&&J(this,a,d);this.d=e!=undefined?e:this.c||0;if(this.a)this.d*=-1;this.f=!b}m(I,H);I.prototype.b=null;I.prototype.c=0;I.prototype.e=false;function J(a,c,b,d){if(a.b=c)a.c=typeof b=="number"?b:a.b.nodeType!=1?0:a.a?-1:1;if(typeof d=="number")a.d=d}
+I.prototype.next=function(){var a;if(this.e){if(!this.b||this.f&&this.d==0)throw G;a=this.b;var c=this.a?-1:1;if(this.c==c){var b=this.a?a.lastChild:a.firstChild;b?J(this,b):J(this,a,c*-1)}else(b=this.a?a.previousSibling:a.nextSibling)?J(this,b):J(this,a.parentNode,c*-1);this.d+=this.c*(this.a?-1:1)}else this.e=true;a=this.b;if(!this.b)throw G;return a};
+I.prototype.splice=function(){var a=this.b,c=this.a?1:-1;if(this.c==c){this.c=c*-1;this.d+=this.c*(this.a?-1:1)}this.a=!this.a;I.prototype.next.call(this);this.a=!this.a;c=i(arguments[0])?arguments[0]:arguments;for(var b=c.length-1;b>=0;b--)a.parentNode&&a.parentNode.insertBefore(c[b],a.nextSibling);a&&a.parentNode&&a.parentNode.removeChild(a)};function K(a,c,b,d){I.call(this,a,c,b,null,d)}m(K,I);K.prototype.next=function(){do K.h.next.call(this);while(this.c==-1);return this.b};var L=["async","autofocus","autoplay","checked","compact","complete","controls","declare","defaultchecked","defaultselected","defer","disabled","draggable","ended","formnovalidate","hidden","indeterminate","iscontenteditable","ismap","itemscope","loop","multiple","muted","nohref","noresize","noshade","novalidate","nowrap","open","paused","pubdate","readonly","required","reversed","scoped","seamless","seeking","selected","spellcheck","truespeed","willvalidate"];
+function M(a,c){if(8==a.nodeType)return null;c=c.toLowerCase();if(c=="style"){var b=a.style.cssText.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").toLowerCase();return b.charAt(b.length-1)==";"?b:b+";"}b=a.getAttributeNode(c);if(!b)return null;if(A(L,c)>=0)return"true";return b.specified?b.value:null}String.fromCharCode(160);function N(){}
+function O(a,c,b){switch(typeof c){case "string":P(a,c,b);break;case "number":b.push(isFinite(c)&&!isNaN(c)?c:"null");break;case "boolean":b.push(c);break;case "undefined":b.push("null");break;case "object":if(c==null){b.push("null");break}if(g(c)=="array"){var d=c.length;b.push("[");var e="";for(var f=0;f<d;f++){b.push(e);O(a,c[f],b);e=","}b.push("]");break}b.push("{");d="";for(e in c)if(Object.prototype.hasOwnProperty.call(c,e)){f=c[e];if(typeof f!="function"){b.push(d);P(a,e,b);b.push(":");O(a,
+f,b);d=","}}b.push("}");break;case "function":break;default:throw Error("Unknown type: "+typeof c);}}var Q={'"':'\\"',"\\":"\\\\","/":"\\/","\u0008":"\\b","\u000c":"\\f","\n":"\\n","\r":"\\r","\t":"\\t","\u000b":"\\u000b"},R=/\uffff/.test("\uffff")?/[\\\"\x00-\x1f\x7f-\uffff]/g:/[\\\"\x00-\x1f\x7f-\xff]/g;
+function P(a,c,b){b.push('"',c.replace(R,function(d){if(d in Q)return Q[d];var e=d.charCodeAt(0),f="\\u";if(e<16)f+="000";else if(e<256)f+="00";else if(e<4096)f+="0";return Q[d]=f+e.toString(16)}),'"')};function S(a){switch(g(a)){case "string":case "number":case "boolean":return a;case "function":return a.toString();case "array":return C(a,S);case "object":a=a;if("tagName"in a&&"nodeType"in a&&a.nodeType==1){var c={};c.ELEMENT=T(a);return c}if(i(a))return C(a,S);a=o(a,function(b,d){return typeof d=="number"||j(d)});return p(a,S);default:return null}}
+function U(a,c){if(g(a)=="array")return C(a,function(b){return U(b,c)});else if(k(a))return"ELEMENT"in a?V(a.ELEMENT,c):p(a,function(b){return U(b,c)});return a}function W(a){a=a||document;var c=a.$wdc_;if(!c){c=a.$wdc_={};c.g=l()}return c}function T(a){var c=W(a.ownerDocument),b=q(c,function(d){return d==a});if(!b){b=":wdc:"+c.g++;c[b]=a}return b}
+function V(a,c){a=decodeURIComponent(a);var b=c||document,d=W(b);if(!(a in d))throw new r(10,"Element does not exist in cache");var e=d[a];for(var f=e;f;){if(f==b.documentElement)return e;f=f.parentNode}delete d[a];throw new r(10,"Element is no longer attached to the DOM");};function X(a,c){var b=M,d=[a,c],e;try{if(j(b))b=new Function(b);var f=U(d),h=b.apply(null,f);e={status:0,value:S(h)}}catch(B){e={status:"code"in B?B.code:13,value:{message:B.message}}}b=[];O(new N,e,b);return b.join("")}var Y="_".split("."),Z=this;!(Y[0]in Z)&&Z.execScript&&Z.execScript("var "+Y[0]);for(var $;Y.length&&($=Y.shift());)if(!Y.length&&X!==undefined)Z[$]=X;else Z=Z[$]?Z[$]:Z[$]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}
diff --git a/core/res/res/raw/get_size_android.js b/core/res/res/raw/get_size_android.js
new file mode 100644
index 0000000..78f61ee
--- /dev/null
+++ b/core/res/res/raw/get_size_android.js
@@ -0,0 +1,11 @@
+function(){return function(){function g(a){var b=typeof a;if(b=="object")if(a){if(a instanceof Array)return"array";else if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if(c=="[object Window]")return"object";if(c=="[object Array]"||typeof a.length=="number"&&typeof a.splice!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("splice"))return"array";if(c=="[object Function]"||typeof a.call!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
+else if(b=="function"&&typeof a.call=="undefined")return"object";return b}function h(a){var b=g(a);return b=="array"||b=="object"&&typeof a.length=="number"}function i(a){a=g(a);return a=="object"||a=="array"||a=="function"}Math.floor(Math.random()*2147483648).toString(36);var j=Date.now||function(){return+new Date};function l(a,b){function c(){}c.prototype=b.prototype;a.h=b.prototype;a.prototype=new c};function m(a){this.stack=Error().stack||"";if(a)this.message=String(a)}l(m,Error);m.prototype.name="CustomError";function n(a,b,c){var d={};for(var e in a)if(b.call(c,a[e],e,a))d[e]=a[e];return d}function o(a,b,c){var d={};for(var e in a)d[e]=b.call(c,a[e],e,a);return d}function p(a,b,c){for(var d in a)if(b.call(c,a[d],d,a))return d};function q(a,b){m.call(this,b);this.code=a;this.name=r[a]||r[13]}l(q,m);var r,s={NoSuchElementError:7,NoSuchFrameError:8,UnknownCommandError:9,StaleElementReferenceError:10,ElementNotVisibleError:11,InvalidElementStateError:12,UnknownError:13,ElementNotSelectableError:15,XPathLookupError:19,NoSuchWindowError:23,InvalidCookieDomainError:24,UnableToSetCookieError:25,ModalDialogOpenedError:26,ModalDialogOpenError:27,ScriptTimeoutError:28},t={};for(var u in s)t[s[u]]=u;r=t;
+q.prototype.toString=function(){return"["+this.name+"] "+this.message};function v(a){for(var b=1;b<arguments.length;b++){var c=String(arguments[b]).replace(/\$/g,"$$$$");a=a.replace(/\%s/,c)}return a};function w(a,b){b.unshift(a);m.call(this,v.apply(null,b));b.shift();this.i=a}l(w,m);w.prototype.name="AssertionError";function x(a,b){if(!a){var c=Array.prototype.slice.call(arguments,2),d="Assertion failed";if(b){d+=": "+b;var e=c}throw new w(""+d,e||[]);}return a};var y=Array.prototype,z=y.map?function(a,b,c){x(a.length!=null);return y.map.call(a,b,c)}:function(a,b,c){var d=a.length,e=Array(d),f=typeof a=="string"?a.split(""):a;for(var k=0;k<d;k++)if(k in f)e[k]=b.call(c,f[k],k,a);return e};var A="",B;if(B=/WebKit\/(\S+)/){var C=B.exec(this.navigator?this.navigator.userAgent:null);A=C?C[1]:""};function D(a,b){this.width=a;this.height=b}D.prototype.toString=function(){return"("+this.width+" x "+this.height+")"};D.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};var E="StopIteration"in this?this.StopIteration:Error("StopIteration");function F(){}F.prototype.next=function(){throw E;};function G(a,b,c,d,e){this.a=!!b;a&&H(this,a,d);this.d=e!=undefined?e:this.c||0;if(this.a)this.d*=-1;this.f=!c}l(G,F);G.prototype.b=null;G.prototype.c=0;G.prototype.e=false;function H(a,b,c,d){if(a.b=b)a.c=typeof c=="number"?c:a.b.nodeType!=1?0:a.a?-1:1;if(typeof d=="number")a.d=d}
+G.prototype.next=function(){var a;if(this.e){if(!this.b||this.f&&this.d==0)throw E;a=this.b;var b=this.a?-1:1;if(this.c==b){var c=this.a?a.lastChild:a.firstChild;c?H(this,c):H(this,a,b*-1)}else(c=this.a?a.previousSibling:a.nextSibling)?H(this,c):H(this,a.parentNode,b*-1);this.d+=this.c*(this.a?-1:1)}else this.e=true;a=this.b;if(!this.b)throw E;return a};
+G.prototype.splice=function(){var a=this.b,b=this.a?1:-1;if(this.c==b){this.c=b*-1;this.d+=this.c*(this.a?-1:1)}this.a=!this.a;G.prototype.next.call(this);this.a=!this.a;b=h(arguments[0])?arguments[0]:arguments;for(var c=b.length-1;c>=0;c--)a.parentNode&&a.parentNode.insertBefore(b[c],a.nextSibling);a&&a.parentNode&&a.parentNode.removeChild(a)};function I(a,b,c,d){G.call(this,a,b,c,null,d)}l(I,G);I.prototype.next=function(){do I.h.next.call(this);while(this.c==-1);return this.b};function J(a){if(g(a.getBBox)=="function")return a.getBBox();var b;b:{b=a.nodeType==9?a:a.ownerDocument||a.document;if(b.defaultView&&b.defaultView.getComputedStyle)if(b=b.defaultView.getComputedStyle(a,null)){b=b.display||b.getPropertyValue("display");break b}b=""}if((b||(a.currentStyle?a.currentStyle.display:null)||a.style.display)!="none")a=new D(a.offsetWidth,a.offsetHeight);else{b=a.style;var c=b.display,d=b.visibility,e=b.position;b.visibility="hidden";b.position="absolute";b.display="inline";
+var f;f=a.offsetWidth;a=a.offsetHeight;b.display=c;b.position=e;b.visibility=d;a=new D(f,a)}return a}String.fromCharCode(160);function K(){}
+function L(a,b,c){switch(typeof b){case "string":M(a,b,c);break;case "number":c.push(isFinite(b)&&!isNaN(b)?b:"null");break;case "boolean":c.push(b);break;case "undefined":c.push("null");break;case "object":if(b==null){c.push("null");break}if(g(b)=="array"){var d=b.length;c.push("[");var e="";for(var f=0;f<d;f++){c.push(e);L(a,b[f],c);e=","}c.push("]");break}c.push("{");d="";for(e in b)if(Object.prototype.hasOwnProperty.call(b,e)){f=b[e];if(typeof f!="function"){c.push(d);M(a,e,c);c.push(":");L(a,
+f,c);d=","}}c.push("}");break;case "function":break;default:throw Error("Unknown type: "+typeof b);}}var N={'"':'\\"',"\\":"\\\\","/":"\\/","\u0008":"\\b","\u000c":"\\f","\n":"\\n","\r":"\\r","\t":"\\t","\u000b":"\\u000b"},O=/\uffff/.test("\uffff")?/[\\\"\x00-\x1f\x7f-\uffff]/g:/[\\\"\x00-\x1f\x7f-\xff]/g;
+function M(a,b,c){c.push('"',b.replace(O,function(d){if(d in N)return N[d];var e=d.charCodeAt(0),f="\\u";if(e<16)f+="000";else if(e<256)f+="00";else if(e<4096)f+="0";return N[d]=f+e.toString(16)}),'"')};function P(a){switch(g(a)){case "string":case "number":case "boolean":return a;case "function":return a.toString();case "array":return z(a,P);case "object":a=a;if("tagName"in a&&"nodeType"in a&&a.nodeType==1){var b={};b.ELEMENT=Q(a);return b}if(h(a))return z(a,P);a=n(a,function(c,d){return typeof d=="number"||typeof d=="string"});return o(a,P);default:return null}}
+function R(a,b){if(g(a)=="array")return z(a,function(c){return R(c,b)});else if(i(a))return"ELEMENT"in a?S(a.ELEMENT,b):o(a,function(c){return R(c,b)});return a}function T(a){a=a||document;var b=a.$wdc_;if(!b){b=a.$wdc_={};b.g=j()}return b}function Q(a){var b=T(a.ownerDocument),c=p(b,function(d){return d==a});if(!c){c=":wdc:"+b.g++;b[c]=a}return c}
+function S(a,b){a=decodeURIComponent(a);var c=b||document,d=T(c);if(!(a in d))throw new q(10,"Element does not exist in cache");var e=d[a];for(var f=e;f;){if(f==c.documentElement)return e;f=f.parentNode}delete d[a];throw new q(10,"Element is no longer attached to the DOM");};function U(a){var b=J;a=[a];var c;try{if(typeof b=="string")b=new Function(b);var d=R(a),e=b.apply(null,d);c={status:0,value:P(e)}}catch(f){c={status:"code"in f?f.code:13,value:{message:f.message}}}b=[];L(new K,c,b);return b.join("")}var V="_".split("."),W=this;!(V[0]in W)&&W.execScript&&W.execScript("var "+V[0]);for(var X;V.length&&(X=V.shift());)if(!V.length&&U!==undefined)W[X]=U;else W=W[X]?W[X]:W[X]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}
diff --git a/core/res/res/raw/get_text_android.js b/core/res/res/raw/get_text_android.js
new file mode 100644
index 0000000..75c3fed
--- /dev/null
+++ b/core/res/res/raw/get_text_android.js
@@ -0,0 +1,19 @@
+function(){return function(){function g(a){var b=typeof a;if(b=="object")if(a){if(a instanceof Array)return"array";else if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if(c=="[object Window]")return"object";if(c=="[object Array]"||typeof a.length=="number"&&typeof a.splice!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("splice"))return"array";if(c=="[object Function]"||typeof a.call!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
+else if(b=="function"&&typeof a.call=="undefined")return"object";return b}function i(a){var b=g(a);return b=="array"||b=="object"&&typeof a.length=="number"}function j(a){return typeof a=="string"}function aa(a){a=g(a);return a=="object"||a=="array"||a=="function"}Math.floor(Math.random()*2147483648).toString(36);var ba=Date.now||function(){return+new Date};function k(a,b){function c(){}c.prototype=b.prototype;a.h=b.prototype;a.prototype=new c};function l(a){this.stack=Error().stack||"";if(a)this.message=String(a)}k(l,Error);l.prototype.name="CustomError";function ca(a,b,c){var d={};for(var e in a)if(b.call(c,a[e],e,a))d[e]=a[e];return d}function m(a,b,c){var d={};for(var e in a)d[e]=b.call(c,a[e],e,a);return d}function da(a,b,c){for(var d in a)if(b.call(c,a[d],d,a))return d};function n(a,b){l.call(this,b);this.code=a;this.name=o[a]||o[13]}k(n,l);var o,p={NoSuchElementError:7,NoSuchFrameError:8,UnknownCommandError:9,StaleElementReferenceError:10,ElementNotVisibleError:11,InvalidElementStateError:12,UnknownError:13,ElementNotSelectableError:15,XPathLookupError:19,NoSuchWindowError:23,InvalidCookieDomainError:24,UnableToSetCookieError:25,ModalDialogOpenedError:26,ModalDialogOpenError:27,ScriptTimeoutError:28},q={};for(var r in p)q[p[r]]=r;o=q;
+n.prototype.toString=function(){return"["+this.name+"] "+this.message};var ea=window;function fa(a){for(var b=1;b<arguments.length;b++){var c=String(arguments[b]).replace(/\$/g,"$$$$");a=a.replace(/\%s/,c)}return a}function s(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")}var t={};function ga(a){return t[a]||(t[a]=String(a).replace(/\-([a-z])/g,function(b,c){return c.toUpperCase()}))};function u(a,b){b.unshift(a);l.call(this,fa.apply(null,b));b.shift();this.i=a}k(u,l);u.prototype.name="AssertionError";function v(a,b){if(!a){var c=Array.prototype.slice.call(arguments,2),d="Assertion failed";if(b){d+=": "+b;var e=c}throw new u(""+d,e||[]);}return a};var w=Array.prototype,ha=w.indexOf?function(a,b,c){v(a.length!=null);return w.indexOf.call(a,b,c)}:function(a,b,c){c=c==null?0:c<0?Math.max(0,a.length+c):c;if(j(a)){if(!j(b)||b.length!=1)return-1;return a.indexOf(b,c)}for(c=c;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},ia=w.forEach?function(a,b,c){v(a.length!=null);w.forEach.call(a,b,c)}:function(a,b,c){var d=a.length,e=j(a)?a.split(""):a;for(var f=0;f<d;f++)f in e&&b.call(c,e[f],f,a)},x=w.map?function(a,b,c){v(a.length!=null);return w.map.call(a,
+b,c)}:function(a,b,c){var d=a.length,e=Array(d),f=j(a)?a.split(""):a;for(var h=0;h<d;h++)if(h in f)e[h]=b.call(c,f[h],h,a);return e},ja=w.some?function(a,b,c){v(a.length!=null);return w.some.call(a,b,c)}:function(a,b,c){var d=a.length,e=j(a)?a.split(""):a;for(var f=0;f<d;f++)if(f in e&&b.call(c,e[f],f,a))return true;return false};var y=true,ka="",z;if(y)z=/WebKit\/(\S+)/;if(z){var A=z.exec(this.navigator?this.navigator.userAgent:null);ka=A?A[1]:""};function B(a,b){this.width=a;this.height=b}B.prototype.toString=function(){return"("+this.width+" x "+this.height+")"};B.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};function C(a){return a.nodeType==9?a:a.ownerDocument||a.document}function la(a,b){var c=[];return D(a,b,c,true)?c[0]:undefined}function D(a,b,c,d){if(a!=null){var e=0;for(var f;f=a.childNodes[e];e++){if(b(f)){c.push(f);if(d)return true}if(D(f,b,c,d))return true}}return false}function E(a,b,c,d){if(!c)a=a.parentNode;c=d==null;for(var e=0;a&&(c||e<=d);){if(b(a))return a;a=a.parentNode;e++}return null};function ma(a,b){try{var c;if(typeof b.selectSingleNode!="undefined"){var d=C(b);typeof d.setProperty!="undefined"&&d.setProperty("SelectionLanguage","XPath");c=b.selectSingleNode(a)}else if(document.implementation.hasFeature("XPath","3.0")){d=C(b);var e=d.createNSResolver(d.documentElement);c=d.evaluate(a,b,e,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue}else c=null}catch(f){return null}if(!c)return null;if(c.nodeType!=1)throw Error("Returned node is not an element: "+a);return c};var F="StopIteration"in this?this.StopIteration:Error("StopIteration");function G(){}G.prototype.next=function(){throw F;};function H(a,b,c,d,e){this.a=!!b;a&&I(this,a,d);this.d=e!=undefined?e:this.c||0;if(this.a)this.d*=-1;this.f=!c}k(H,G);H.prototype.b=null;H.prototype.c=0;H.prototype.e=false;function I(a,b,c,d){if(a.b=b)a.c=typeof c=="number"?c:a.b.nodeType!=1?0:a.a?-1:1;if(typeof d=="number")a.d=d}
+H.prototype.next=function(){var a;if(this.e){if(!this.b||this.f&&this.d==0)throw F;a=this.b;var b=this.a?-1:1;if(this.c==b){var c=this.a?a.lastChild:a.firstChild;c?I(this,c):I(this,a,b*-1)}else(c=this.a?a.previousSibling:a.nextSibling)?I(this,c):I(this,a.parentNode,b*-1);this.d+=this.c*(this.a?-1:1)}else this.e=true;a=this.b;if(!this.b)throw F;return a};
+H.prototype.splice=function(){var a=this.b,b=this.a?1:-1;if(this.c==b){this.c=b*-1;this.d+=this.c*(this.a?-1:1)}this.a=!this.a;H.prototype.next.call(this);this.a=!this.a;b=i(arguments[0])?arguments[0]:arguments;for(var c=b.length-1;c>=0;c--)a.parentNode&&a.parentNode.insertBefore(b[c],a.nextSibling);a&&a.parentNode&&a.parentNode.removeChild(a)};function J(a,b,c,d){H.call(this,a,b,c,null,d)}k(J,H);J.prototype.next=function(){do J.h.next.call(this);while(this.c==-1);return this.b};function K(a,b){var c=C(a);if(c.defaultView&&c.defaultView.getComputedStyle)if(c=c.defaultView.getComputedStyle(a,null))return c[b]||c.getPropertyValue(b);return""};function L(a,b){return!!a&&a.nodeType==1&&(!b||a.tagName.toUpperCase()==b)}
+var na=["async","autofocus","autoplay","checked","compact","complete","controls","declare","defaultchecked","defaultselected","defer","disabled","draggable","ended","formnovalidate","hidden","indeterminate","iscontenteditable","ismap","itemscope","loop","multiple","muted","nohref","noresize","noshade","novalidate","nowrap","open","paused","pubdate","readonly","required","reversed","scoped","seamless","seeking","selected","spellcheck","truespeed","willvalidate"];
+function oa(a,b){if(8==a.nodeType)return null;b=b.toLowerCase();if(b=="style"){var c=s(a.style.cssText).toLowerCase();return c.charAt(c.length-1)==";"?c:c+";"}c=a.getAttributeNode(b);if(!c)return null;if(ha(na,b)>=0)return"true";return c.specified?c.value:null}function M(a){for(a=a.parentNode;a&&a.nodeType!=1&&a.nodeType!=9&&a.nodeType!=11;)a=a.parentNode;return L(a)?a:null}function N(a,b){b=ga(String(b));return K(a,b)||O(a,b)}
+function O(a,b){var c=(a.currentStyle||a.style)[b];if(c!="inherit")return c!==undefined?c:null;return(c=M(a))?O(c,b):null}
+function pa(a){if(g(a.getBBox)=="function")return a.getBBox();var b;if((K(a,"display")||(a.currentStyle?a.currentStyle.display:null)||a.style.display)!="none")b=new B(a.offsetWidth,a.offsetHeight);else{b=a.style;var c=b.display,d=b.visibility,e=b.position;b.visibility="hidden";b.position="absolute";b.display="inline";var f;f=a.offsetWidth;a=a.offsetHeight;b.display=c;b.position=e;b.visibility=d;b=new B(f,a)}return b}
+function P(a,b){function c(f){if(N(f,"display")=="none")return false;f=M(f);return!f||c(f)}function d(f){var h=pa(f);if(h.height>0&&h.width>0)return true;if(f.innerText||f.textContent)if(qa.test(f.innerText||f.textContent))return true;return y&&ja(f.childNodes,function(X){return L(X)&&d(X)})}if(!L(a))throw Error("Argument to isShown must be of type Element");if(L(a,"TITLE"))return(C(a)?C(a).parentWindow||C(a).defaultView:window)==ea;if(L(a,"OPTION")||L(a,"OPTGROUP")){var e=E(a,function(f){return L(f,
+"SELECT")});return!!e&&P(e)}if(L(a,"MAP")){if(!a.name)return false;e=C(a);e=e.evaluate?ma('/descendant::*[@usemap = "#'+a.name+'"]',e):la(e,function(f){return L(f)&&oa(f,"usemap")=="#"+a.name});return!!e&&P(e)}if(L(a,"AREA")){e=E(a,function(f){return L(f,"MAP")});return!!e&&P(e)}if(L(a,"INPUT")&&a.type.toLowerCase()=="hidden")return false;if(N(a,"visibility")=="hidden")return false;if(!c(a))return false;if(!b&&Q(a)==0)return false;if(!d(a))return false;return true}
+function ra(a){var b=[""];R(a,b);b=x(b,s);return s(b.join("\n"))}function R(a,b){if(L(a,"BR"))b.push("");else{var c=sa(a);c&&b[b.length-1]&&b.push("");ia(a.childNodes,function(d){if(d.nodeType==3){var e=M(d);if(e){P(e);if(e&&P(e)){d=d.nodeValue.replace(ta," ");e=b.pop();var f=e.length-1;if(f>=0&&e.indexOf(" ",f)==f&&d.lastIndexOf(" ",0)==0)d=d.substr(1);b.push(e+d)}}}else L(d)&&R(d,b)});c&&b[b.length-1]&&b.push("")}}function sa(a){a=N(a,"display");return a=="block"||a=="inline-block"}
+var S="[\\s\\xa0"+String.fromCharCode(160)+"]+",ta=RegExp(S,"g"),qa=RegExp("^"+S+"$");function Q(a){var b=1,c=N(a,"opacity");if(c)b=Number(c);if(a=M(a))b*=Q(a);return b};function ua(){}
+function T(a,b,c){switch(typeof b){case "string":va(a,b,c);break;case "number":c.push(isFinite(b)&&!isNaN(b)?b:"null");break;case "boolean":c.push(b);break;case "undefined":c.push("null");break;case "object":if(b==null){c.push("null");break}if(g(b)=="array"){var d=b.length;c.push("[");var e="";for(var f=0;f<d;f++){c.push(e);T(a,b[f],c);e=","}c.push("]");break}c.push("{");d="";for(e in b)if(Object.prototype.hasOwnProperty.call(b,e)){f=b[e];if(typeof f!="function"){c.push(d);va(a,e,c);c.push(":");T(a,
+f,c);d=","}}c.push("}");break;case "function":break;default:throw Error("Unknown type: "+typeof b);}}var U={'"':'\\"',"\\":"\\\\","/":"\\/","\u0008":"\\b","\u000c":"\\f","\n":"\\n","\r":"\\r","\t":"\\t","\u000b":"\\u000b"},wa=/\uffff/.test("\uffff")?/[\\\"\x00-\x1f\x7f-\uffff]/g:/[\\\"\x00-\x1f\x7f-\xff]/g;
+function va(a,b,c){c.push('"',b.replace(wa,function(d){if(d in U)return U[d];var e=d.charCodeAt(0),f="\\u";if(e<16)f+="000";else if(e<256)f+="00";else if(e<4096)f+="0";return U[d]=f+e.toString(16)}),'"')};function V(a){switch(g(a)){case "string":case "number":case "boolean":return a;case "function":return a.toString();case "array":return x(a,V);case "object":a=a;if("tagName"in a&&"nodeType"in a&&a.nodeType==1){var b={};b.ELEMENT=xa(a);return b}if(i(a))return x(a,V);a=ca(a,function(c,d){return typeof d=="number"||j(d)});return m(a,V);default:return null}}
+function W(a,b){if(g(a)=="array")return x(a,function(c){return W(c,b)});else if(aa(a))return"ELEMENT"in a?ya(a.ELEMENT,b):m(a,function(c){return W(c,b)});return a}function za(a){a=a||document;var b=a.$wdc_;if(!b){b=a.$wdc_={};b.g=ba()}return b}function xa(a){var b=za(a.ownerDocument),c=da(b,function(d){return d==a});if(!c){c=":wdc:"+b.g++;b[c]=a}return c}
+function ya(a,b){a=decodeURIComponent(a);var c=b||document,d=za(c);if(!(a in d))throw new n(10,"Element does not exist in cache");var e=d[a];for(var f=e;f;){if(f==c.documentElement)return e;f=f.parentNode}delete d[a];throw new n(10,"Element is no longer attached to the DOM");};function Aa(a){var b=ra;a=[a];var c;try{if(j(b))b=new Function(b);var d=W(a),e=b.apply(null,d);c={status:0,value:V(e)}}catch(f){c={status:"code"in f?f.code:13,value:{message:f.message}}}b=[];T(new ua,c,b);return b.join("")}var Y="_".split("."),Z=this;!(Y[0]in Z)&&Z.execScript&&Z.execScript("var "+Y[0]);for(var $;Y.length&&($=Y.shift());)if(!Y.length&&Aa!==undefined)Z[$]=Aa;else Z=Z[$]?Z[$]:Z[$]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}
diff --git a/core/res/res/raw/get_top_left_coordinates_android.js b/core/res/res/raw/get_top_left_coordinates_android.js
new file mode 100644
index 0000000..23f96afe
--- /dev/null
+++ b/core/res/res/raw/get_top_left_coordinates_android.js
@@ -0,0 +1,18 @@
+function(){return function(){var h=this;
+function j(a){var b=typeof a;if(b=="object")if(a){if(a instanceof Array)return"array";else if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if(c=="[object Window]")return"object";if(c=="[object Array]"||typeof a.length=="number"&&typeof a.splice!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("splice"))return"array";if(c=="[object Function]"||typeof a.call!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("call"))return"function"}else return"null";else if(b==
+"function"&&typeof a.call=="undefined")return"object";return b}function l(a){var b=j(a);return b=="array"||b=="object"&&typeof a.length=="number"}function aa(a){a=j(a);return a=="object"||a=="array"||a=="function"}Math.floor(Math.random()*2147483648).toString(36);var ba=Date.now||function(){return+new Date};function m(a,b){function c(){}c.prototype=b.prototype;a.j=b.prototype;a.prototype=new c};function n(a){this.stack=Error().stack||"";if(a)this.message=String(a)}m(n,Error);n.prototype.name="CustomError";function ca(a,b,c){var d={};for(var f in a)if(b.call(c,a[f],f,a))d[f]=a[f];return d}function o(a,b,c){var d={};for(var f in a)d[f]=b.call(c,a[f],f,a);return d}function da(a,b,c){for(var d in a)if(b.call(c,a[d],d,a))return d};function p(a,b){n.call(this,b);this.code=a;this.name=q[a]||q[13]}m(p,n);var q,r={NoSuchElementError:7,NoSuchFrameError:8,UnknownCommandError:9,StaleElementReferenceError:10,ElementNotVisibleError:11,InvalidElementStateError:12,UnknownError:13,ElementNotSelectableError:15,XPathLookupError:19,NoSuchWindowError:23,InvalidCookieDomainError:24,UnableToSetCookieError:25,ModalDialogOpenedError:26,ModalDialogOpenError:27,ScriptTimeoutError:28},s={};for(var t in r)s[r[t]]=t;q=s;
+p.prototype.toString=function(){return"["+this.name+"] "+this.message};function ea(a){for(var b=1;b<arguments.length;b++){var c=String(arguments[b]).replace(/\$/g,"$$$$");a=a.replace(/\%s/,c)}return a}function y(a,b){if(a<b)return-1;else if(a>b)return 1;return 0};function z(a,b){b.unshift(a);n.call(this,ea.apply(null,b));b.shift();this.k=a}m(z,n);z.prototype.name="AssertionError";function fa(a,b){if(!a){var c=Array.prototype.slice.call(arguments,2),d="Assertion failed";if(b){d+=": "+b;var f=c}throw new z(""+d,f||[]);}return a};var A=Array.prototype,B=A.map?function(a,b,c){fa(a.length!=null);return A.map.call(a,b,c)}:function(a,b,c){var d=a.length,f=Array(d),e=typeof a=="string"?a.split(""):a;for(var g=0;g<d;g++)if(g in e)f[g]=b.call(c,e[g],g,a);return f};var C,F="",G;if(G=/WebKit\/(\S+)/){var H=G.exec(h.navigator?h.navigator.userAgent:null);F=H?H[1]:""}C=F;var ga={};var ha;function I(a,b){this.x=a!==undefined?a:0;this.y=b!==undefined?b:0}I.prototype.toString=function(){return"("+this.x+", "+this.y+")"};function J(a,b){this.width=a;this.height=b}J.prototype.toString=function(){return"("+this.width+" x "+this.height+")"};J.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};function K(a){return a?new ia(L(a)):ha||(ha=new ia)}function L(a){return a.nodeType==9?a:a.ownerDocument||a.document}function ia(a){this.e=a||h.document||document}function ja(a){a=a.e.body;return new I(a.scrollLeft,a.scrollTop)};var M="StopIteration"in h?h.StopIteration:Error("StopIteration");function ka(){}ka.prototype.next=function(){throw M;};function N(a,b,c,d,f){this.a=!!b;a&&O(this,a,d);this.d=f!=undefined?f:this.c||0;if(this.a)this.d*=-1;this.h=!c}m(N,ka);N.prototype.b=null;N.prototype.c=0;N.prototype.g=false;function O(a,b,c,d){if(a.b=b)a.c=typeof c=="number"?c:a.b.nodeType!=1?0:a.a?-1:1;if(typeof d=="number")a.d=d}
+N.prototype.next=function(){var a;if(this.g){if(!this.b||this.h&&this.d==0)throw M;a=this.b;var b=this.a?-1:1;if(this.c==b){var c=this.a?a.lastChild:a.firstChild;c?O(this,c):O(this,a,b*-1)}else(c=this.a?a.previousSibling:a.nextSibling)?O(this,c):O(this,a.parentNode,b*-1);this.d+=this.c*(this.a?-1:1)}else this.g=true;a=this.b;if(!this.b)throw M;return a};
+N.prototype.splice=function(){var a=this.b,b=this.a?1:-1;if(this.c==b){this.c=b*-1;this.d+=this.c*(this.a?-1:1)}this.a=!this.a;N.prototype.next.call(this);this.a=!this.a;b=l(arguments[0])?arguments[0]:arguments;for(var c=b.length-1;c>=0;c--)a.parentNode&&a.parentNode.insertBefore(b[c],a.nextSibling);a&&a.parentNode&&a.parentNode.removeChild(a)};function P(a,b,c,d){N.call(this,a,b,c,null,d)}m(P,N);P.prototype.next=function(){do P.j.next.call(this);while(this.c==-1);return this.b};function la(a,b,c,d){this.top=a;this.right=b;this.bottom=c;this.left=d}la.prototype.toString=function(){return"("+this.top+"t, "+this.right+"r, "+this.bottom+"b, "+this.left+"l)"};function Q(a,b,c,d){this.left=a;this.top=b;this.width=c;this.height=d}Q.prototype.toString=function(){return"("+this.left+", "+this.top+" - "+this.width+"w x "+this.height+"h)"};function R(a,b){var c=L(a);if(c.defaultView&&c.defaultView.getComputedStyle)if(c=c.defaultView.getComputedStyle(a,null))return c[b]||c.getPropertyValue(b);return""}function S(a,b){return R(a,b)||(a.currentStyle?a.currentStyle[b]:null)||a.style[b]}
+function ma(a){var b=L(a),c=S(a,"position"),d=c=="fixed"||c=="absolute";for(a=a.parentNode;a&&a!=b;a=a.parentNode){c=S(a,"position");d=d&&c=="static"&&a!=b.documentElement&&a!=b.body;if(!d&&(a.scrollWidth>a.clientWidth||a.scrollHeight>a.clientHeight||c=="fixed"||c=="absolute"))return a}return null}
+function T(a){var b=L(a),c=S(a,"position"),d=new I(0,0),f=(b?b.nodeType==9?b:L(b):document).documentElement;if(a==f)return d;if(a.getBoundingClientRect){a=a.getBoundingClientRect();b=ja(K(b));d.x=a.left+b.x;d.y=a.top+b.y}else if(b.getBoxObjectFor){a=b.getBoxObjectFor(a);b=b.getBoxObjectFor(f);d.x=a.screenX-b.screenX;d.y=a.screenY-b.screenY}else{var e=a;do{d.x+=e.offsetLeft;d.y+=e.offsetTop;if(e!=a){d.x+=e.clientLeft||0;d.y+=e.clientTop||0}if(S(e,"position")=="fixed"){d.x+=b.body.scrollLeft;d.y+=b.body.scrollTop;
+break}e=e.offsetParent}while(e&&e!=a);if(c=="absolute")d.y-=b.body.offsetTop;for(e=a;(e=ma(e))&&e!=b.body&&e!=f;){d.x-=e.scrollLeft;d.y-=e.scrollTop}}return d};String.fromCharCode(160);function na(a,b){b.scrollLeft+=Math.min(a.left,Math.max(a.left-a.width,0));b.scrollTop+=Math.min(a.top,Math.max(a.top-a.height,0))}
+function oa(a,b){var c;c=b?new Q(b.left,b.top,b.width,b.height):new Q(0,0,a.offsetWidth,a.offsetHeight);var d=L(a);for(var f=a.parentNode;f&&f!=d.body&&f!=d.documentElement;){var e=c,g=f,u=T(a),v=T(g),i=void 0;i=void 0;var k=void 0,D=void 0,E=void 0;E=R(g,"borderLeftWidth");D=R(g,"borderRightWidth");k=R(g,"borderTopWidth");i=R(g,"borderBottomWidth");i=new la(parseFloat(k),parseFloat(D),parseFloat(i),parseFloat(E));na(new Q(u.x+e.left-v.x-i.left,u.y+e.top-v.y-i.top,g.clientWidth-e.width,g.clientHeight-
+e.height),g);f=f.parentNode}f=T(a);e=K(d);e=(e.e.parentWindow||e.e.defaultView||window).document;if(!ga["500"]){g=0;u=String(C).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split(".");v=String("500").replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split(".");i=Math.max(u.length,v.length);for(k=0;g==0&&k<i;k++){D=u[k]||"";E=v[k]||"";var sa=RegExp("(\\d*)(\\D*)","g"),ta=RegExp("(\\d*)(\\D*)","g");do{var w=sa.exec(D)||["","",""],x=ta.exec(E)||["","",""];if(w[0].length==0&&x[0].length==0)break;g=y(w[1].length==0?0:parseInt(w[1],
+10),x[1].length==0?0:parseInt(x[1],10))||y(w[2].length==0,x[2].length==0)||y(w[2],x[2])}while(g==0)}ga["500"]=g>=0}e=e.compatMode=="CSS1Compat"?e.documentElement:e.body;e=new J(e.clientWidth,e.clientHeight);na(new Q(f.x+c.left-d.body.scrollLeft,f.y+c.top-d.body.scrollTop,e.width-c.width,e.height-c.height),d.body);d=new I;if(a.nodeType==1)if(a.getBoundingClientRect){f=a.getBoundingClientRect();d.x=f.left;d.y=f.top}else{f=ja(K(a));e=T(a);d.x=e.x-f.x;d.y=e.y-f.y}else{f=j(a.f)=="function";e=a;if(a.targetTouches)e=
+a.targetTouches[0];else if(f&&a.f().targetTouches)e=a.f().targetTouches[0];d.x=e.clientX;d.y=e.clientY}return new I(d.x+c.left,d.y+c.top)};function pa(){}
+function U(a,b,c){switch(typeof b){case "string":qa(a,b,c);break;case "number":c.push(isFinite(b)&&!isNaN(b)?b:"null");break;case "boolean":c.push(b);break;case "undefined":c.push("null");break;case "object":if(b==null){c.push("null");break}if(j(b)=="array"){var d=b.length;c.push("[");var f="";for(var e=0;e<d;e++){c.push(f);U(a,b[e],c);f=","}c.push("]");break}c.push("{");d="";for(f in b)if(Object.prototype.hasOwnProperty.call(b,f)){e=b[f];if(typeof e!="function"){c.push(d);qa(a,f,c);c.push(":");U(a,
+e,c);d=","}}c.push("}");break;case "function":break;default:throw Error("Unknown type: "+typeof b);}}var V={'"':'\\"',"\\":"\\\\","/":"\\/","\u0008":"\\b","\u000c":"\\f","\n":"\\n","\r":"\\r","\t":"\\t","\u000b":"\\u000b"},ra=/\uffff/.test("\uffff")?/[\\\"\x00-\x1f\x7f-\uffff]/g:/[\\\"\x00-\x1f\x7f-\xff]/g;
+function qa(a,b,c){c.push('"',b.replace(ra,function(d){if(d in V)return V[d];var f=d.charCodeAt(0),e="\\u";if(f<16)e+="000";else if(f<256)e+="00";else if(f<4096)e+="0";return V[d]=e+f.toString(16)}),'"')};function W(a){switch(j(a)){case "string":case "number":case "boolean":return a;case "function":return a.toString();case "array":return B(a,W);case "object":a=a;if("tagName"in a&&"nodeType"in a&&a.nodeType==1){var b={};b.ELEMENT=ua(a);return b}if(l(a))return B(a,W);a=ca(a,function(c,d){return typeof d=="number"||typeof d=="string"});return o(a,W);default:return null}}
+function X(a,b){if(j(a)=="array")return B(a,function(c){return X(c,b)});else if(aa(a))return"ELEMENT"in a?va(a.ELEMENT,b):o(a,function(c){return X(c,b)});return a}function wa(a){a=a||document;var b=a.$wdc_;if(!b){b=a.$wdc_={};b.i=ba()}return b}function ua(a){var b=wa(a.ownerDocument),c=da(b,function(d){return d==a});if(!c){c=":wdc:"+b.i++;b[c]=a}return c}
+function va(a,b){a=decodeURIComponent(a);var c=b||document,d=wa(c);if(!(a in d))throw new p(10,"Element does not exist in cache");var f=d[a];for(var e=f;e;){if(e==c.documentElement)return f;e=e.parentNode}delete d[a];throw new p(10,"Element is no longer attached to the DOM");};function xa(a){var b=oa;a=[a];var c;try{if(typeof b=="string")b=new Function(b);var d=X(a),f=b.apply(null,d);c={status:0,value:W(f)}}catch(e){c={status:"code"in e?e.code:13,value:{message:e.message}}}b=[];U(new pa,c,b);return b.join("")}var Y="_".split("."),Z=h;!(Y[0]in Z)&&Z.execScript&&Z.execScript("var "+Y[0]);for(var $;Y.length&&($=Y.shift());)if(!Y.length&&xa!==undefined)Z[$]=xa;else Z=Z[$]?Z[$]:Z[$]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}
diff --git a/core/res/res/raw/get_value_of_css_property_android.js b/core/res/res/raw/get_value_of_css_property_android.js
new file mode 100644
index 0000000..c156dec
--- /dev/null
+++ b/core/res/res/raw/get_value_of_css_property_android.js
@@ -0,0 +1,10 @@
+function(){return function(){function g(a){var c=typeof a;if(c=="object")if(a){if(a instanceof Array)return"array";else if(a instanceof Object)return c;var b=Object.prototype.toString.call(a);if(b=="[object Window]")return"object";if(b=="[object Array]"||typeof a.length=="number"&&typeof a.splice!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("splice"))return"array";if(b=="[object Function]"||typeof a.call!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
+else if(c=="function"&&typeof a.call=="undefined")return"object";return c}function i(a){var c=g(a);return c=="array"||c=="object"&&typeof a.length=="number"}function j(a){a=g(a);return a=="object"||a=="array"||a=="function"}Math.floor(Math.random()*2147483648).toString(36);var k=Date.now||function(){return+new Date};function l(a,c){function b(){}b.prototype=c.prototype;a.h=c.prototype;a.prototype=new b};function m(a){this.stack=Error().stack||"";if(a)this.message=String(a)}l(m,Error);m.prototype.name="CustomError";function n(a,c,b){var d={};for(var e in a)if(c.call(b,a[e],e,a))d[e]=a[e];return d}function o(a,c,b){var d={};for(var e in a)d[e]=c.call(b,a[e],e,a);return d}function p(a,c,b){for(var d in a)if(c.call(b,a[d],d,a))return d};function q(a,c){m.call(this,c);this.code=a;this.name=r[a]||r[13]}l(q,m);var r,s={NoSuchElementError:7,NoSuchFrameError:8,UnknownCommandError:9,StaleElementReferenceError:10,ElementNotVisibleError:11,InvalidElementStateError:12,UnknownError:13,ElementNotSelectableError:15,XPathLookupError:19,NoSuchWindowError:23,InvalidCookieDomainError:24,UnableToSetCookieError:25,ModalDialogOpenedError:26,ModalDialogOpenError:27,ScriptTimeoutError:28},t={};for(var u in s)t[s[u]]=u;r=t;
+q.prototype.toString=function(){return"["+this.name+"] "+this.message};function v(a){for(var c=1;c<arguments.length;c++){var b=String(arguments[c]).replace(/\$/g,"$$$$");a=a.replace(/\%s/,b)}return a};function w(a,c){c.unshift(a);m.call(this,v.apply(null,c));c.shift();this.i=a}l(w,m);w.prototype.name="AssertionError";function x(a,c){if(!a){var b=Array.prototype.slice.call(arguments,2),d="Assertion failed";if(c){d+=": "+c;var e=b}throw new w(""+d,e||[]);}return a};var y=Array.prototype,A=y.map?function(a,c,b){x(a.length!=null);return y.map.call(a,c,b)}:function(a,c,b){var d=a.length,e=Array(d),f=typeof a=="string"?a.split(""):a;for(var h=0;h<d;h++)if(h in f)e[h]=c.call(b,f[h],h,a);return e};var B="",C;if(C=/WebKit\/(\S+)/){var D=C.exec(this.navigator?this.navigator.userAgent:null);B=D?D[1]:""};var E="StopIteration"in this?this.StopIteration:Error("StopIteration");function F(){}F.prototype.next=function(){throw E;};function G(a,c,b,d,e){this.a=!!c;a&&H(this,a,d);this.d=e!=undefined?e:this.c||0;if(this.a)this.d*=-1;this.f=!b}l(G,F);G.prototype.b=null;G.prototype.c=0;G.prototype.e=false;function H(a,c,b,d){if(a.b=c)a.c=typeof b=="number"?b:a.b.nodeType!=1?0:a.a?-1:1;if(typeof d=="number")a.d=d}
+G.prototype.next=function(){var a;if(this.e){if(!this.b||this.f&&this.d==0)throw E;a=this.b;var c=this.a?-1:1;if(this.c==c){var b=this.a?a.lastChild:a.firstChild;b?H(this,b):H(this,a,c*-1)}else(b=this.a?a.previousSibling:a.nextSibling)?H(this,b):H(this,a.parentNode,c*-1);this.d+=this.c*(this.a?-1:1)}else this.e=true;a=this.b;if(!this.b)throw E;return a};
+G.prototype.splice=function(){var a=this.b,c=this.a?1:-1;if(this.c==c){this.c=c*-1;this.d+=this.c*(this.a?-1:1)}this.a=!this.a;G.prototype.next.call(this);this.a=!this.a;c=i(arguments[0])?arguments[0]:arguments;for(var b=c.length-1;b>=0;b--)a.parentNode&&a.parentNode.insertBefore(c[b],a.nextSibling);a&&a.parentNode&&a.parentNode.removeChild(a)};function I(a,c,b,d){G.call(this,a,c,b,null,d)}l(I,G);I.prototype.next=function(){do I.h.next.call(this);while(this.c==-1);return this.b};function J(a,c){var b=(a.currentStyle||a.style)[c];if(b!="inherit")return b!==undefined?b:null;for(b=a.parentNode;b&&b.nodeType!=1&&b.nodeType!=9&&b.nodeType!=11;)b=b.parentNode;return(b=b&&b.nodeType==1&&1?b:null)?J(b,c):null}String.fromCharCode(160);function K(){}
+function L(a,c,b){switch(typeof c){case "string":M(a,c,b);break;case "number":b.push(isFinite(c)&&!isNaN(c)?c:"null");break;case "boolean":b.push(c);break;case "undefined":b.push("null");break;case "object":if(c==null){b.push("null");break}if(g(c)=="array"){var d=c.length;b.push("[");var e="";for(var f=0;f<d;f++){b.push(e);L(a,c[f],b);e=","}b.push("]");break}b.push("{");d="";for(e in c)if(Object.prototype.hasOwnProperty.call(c,e)){f=c[e];if(typeof f!="function"){b.push(d);M(a,e,b);b.push(":");L(a,
+f,b);d=","}}b.push("}");break;case "function":break;default:throw Error("Unknown type: "+typeof c);}}var N={'"':'\\"',"\\":"\\\\","/":"\\/","\u0008":"\\b","\u000c":"\\f","\n":"\\n","\r":"\\r","\t":"\\t","\u000b":"\\u000b"},O=/\uffff/.test("\uffff")?/[\\\"\x00-\x1f\x7f-\uffff]/g:/[\\\"\x00-\x1f\x7f-\xff]/g;
+function M(a,c,b){b.push('"',c.replace(O,function(d){if(d in N)return N[d];var e=d.charCodeAt(0),f="\\u";if(e<16)f+="000";else if(e<256)f+="00";else if(e<4096)f+="0";return N[d]=f+e.toString(16)}),'"')};function P(a){switch(g(a)){case "string":case "number":case "boolean":return a;case "function":return a.toString();case "array":return A(a,P);case "object":a=a;if("tagName"in a&&"nodeType"in a&&a.nodeType==1){var c={};c.ELEMENT=Q(a);return c}if(i(a))return A(a,P);a=n(a,function(b,d){return typeof d=="number"||typeof d=="string"});return o(a,P);default:return null}}
+function R(a,c){if(g(a)=="array")return A(a,function(b){return R(b,c)});else if(j(a))return"ELEMENT"in a?S(a.ELEMENT,c):o(a,function(b){return R(b,c)});return a}function T(a){a=a||document;var c=a.$wdc_;if(!c){c=a.$wdc_={};c.g=k()}return c}function Q(a){var c=T(a.ownerDocument),b=p(c,function(d){return d==a});if(!b){b=":wdc:"+c.g++;c[b]=a}return b}
+function S(a,c){a=decodeURIComponent(a);var b=c||document,d=T(b);if(!(a in d))throw new q(10,"Element does not exist in cache");var e=d[a];for(var f=e;f;){if(f==b.documentElement)return e;f=f.parentNode}delete d[a];throw new q(10,"Element is no longer attached to the DOM");};function U(a,c){var b=J,d=[a,c],e;try{if(typeof b=="string")b=new Function(b);var f=R(d),h=b.apply(null,f);e={status:0,value:P(h)}}catch(z){e={status:"code"in z?z.code:13,value:{message:z.message}}}b=[];L(new K,e,b);return b.join("")}var V="_".split("."),W=this;!(V[0]in W)&&W.execScript&&W.execScript("var "+V[0]);for(var X;V.length&&(X=V.shift());)if(!V.length&&U!==undefined)W[X]=U;else W=W[X]?W[X]:W[X]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}
diff --git a/core/res/res/raw/is_enabled_android.js b/core/res/res/raw/is_enabled_android.js
new file mode 100644
index 0000000..3f799f3
--- /dev/null
+++ b/core/res/res/raw/is_enabled_android.js
@@ -0,0 +1,12 @@
+function(){return function(){function g(a){var b=typeof a;if(b=="object")if(a){if(a instanceof Array)return"array";else if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if(c=="[object Window]")return"object";if(c=="[object Array]"||typeof a.length=="number"&&typeof a.splice!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("splice"))return"array";if(c=="[object Function]"||typeof a.call!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
+else if(b=="function"&&typeof a.call=="undefined")return"object";return b}function h(a){var b=g(a);return b=="array"||b=="object"&&typeof a.length=="number"}function i(a){return typeof a=="string"}function k(a){a=g(a);return a=="object"||a=="array"||a=="function"}Math.floor(Math.random()*2147483648).toString(36);var l=Date.now||function(){return+new Date};function m(a,b){function c(){}c.prototype=b.prototype;a.h=b.prototype;a.prototype=new c};function n(a){this.stack=Error().stack||"";if(a)this.message=String(a)}m(n,Error);n.prototype.name="CustomError";function o(a,b,c){var d={};for(var e in a)if(b.call(c,a[e],e,a))d[e]=a[e];return d}function p(a,b,c){var d={};for(var e in a)d[e]=b.call(c,a[e],e,a);return d}function q(a,b,c){for(var d in a)if(b.call(c,a[d],d,a))return d};function r(a,b){n.call(this,b);this.code=a;this.name=s[a]||s[13]}m(r,n);var s,t={NoSuchElementError:7,NoSuchFrameError:8,UnknownCommandError:9,StaleElementReferenceError:10,ElementNotVisibleError:11,InvalidElementStateError:12,UnknownError:13,ElementNotSelectableError:15,XPathLookupError:19,NoSuchWindowError:23,InvalidCookieDomainError:24,UnableToSetCookieError:25,ModalDialogOpenedError:26,ModalDialogOpenError:27,ScriptTimeoutError:28},u={};for(var v in t)u[t[v]]=v;s=u;
+r.prototype.toString=function(){return"["+this.name+"] "+this.message};function w(a){for(var b=1;b<arguments.length;b++){var c=String(arguments[b]).replace(/\$/g,"$$$$");a=a.replace(/\%s/,c)}return a};function x(a,b){b.unshift(a);n.call(this,w.apply(null,b));b.shift();this.i=a}m(x,n);x.prototype.name="AssertionError";function y(a,b){if(!a){var c=Array.prototype.slice.call(arguments,2),d="Assertion failed";if(b){d+=": "+b;var e=c}throw new x(""+d,e||[]);}return a};var z=Array.prototype,A=z.indexOf?function(a,b,c){y(a.length!=null);return z.indexOf.call(a,b,c)}:function(a,b,c){c=c==null?0:c<0?Math.max(0,a.length+c):c;if(i(a)){if(!i(b)||b.length!=1)return-1;return a.indexOf(b,c)}for(c=c;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},B=z.map?function(a,b,c){y(a.length!=null);return z.map.call(a,b,c)}:function(a,b,c){var d=a.length,e=Array(d),f=i(a)?a.split(""):a;for(var j=0;j<d;j++)if(j in f)e[j]=b.call(c,f[j],j,a);return e};var C="",D;if(D=/WebKit\/(\S+)/){var E=D.exec(this.navigator?this.navigator.userAgent:null);C=E?E[1]:""};var F="StopIteration"in this?this.StopIteration:Error("StopIteration");function G(){}G.prototype.next=function(){throw F;};function H(a,b,c,d,e){this.a=!!b;a&&I(this,a,d);this.d=e!=undefined?e:this.c||0;if(this.a)this.d*=-1;this.f=!c}m(H,G);H.prototype.b=null;H.prototype.c=0;H.prototype.e=false;function I(a,b,c,d){if(a.b=b)a.c=typeof c=="number"?c:a.b.nodeType!=1?0:a.a?-1:1;if(typeof d=="number")a.d=d}
+H.prototype.next=function(){var a;if(this.e){if(!this.b||this.f&&this.d==0)throw F;a=this.b;var b=this.a?-1:1;if(this.c==b){var c=this.a?a.lastChild:a.firstChild;c?I(this,c):I(this,a,b*-1)}else(c=this.a?a.previousSibling:a.nextSibling)?I(this,c):I(this,a.parentNode,b*-1);this.d+=this.c*(this.a?-1:1)}else this.e=true;a=this.b;if(!this.b)throw F;return a};
+H.prototype.splice=function(){var a=this.b,b=this.a?1:-1;if(this.c==b){this.c=b*-1;this.d+=this.c*(this.a?-1:1)}this.a=!this.a;H.prototype.next.call(this);this.a=!this.a;b=h(arguments[0])?arguments[0]:arguments;for(var c=b.length-1;c>=0;c--)a.parentNode&&a.parentNode.insertBefore(b[c],a.nextSibling);a&&a.parentNode&&a.parentNode.removeChild(a)};function J(a,b,c,d){H.call(this,a,b,c,null,d)}m(J,H);J.prototype.next=function(){do J.h.next.call(this);while(this.c==-1);return this.b};var K=["async","autofocus","autoplay","checked","compact","complete","controls","declare","defaultchecked","defaultselected","defer","disabled","draggable","ended","formnovalidate","hidden","indeterminate","iscontenteditable","ismap","itemscope","loop","multiple","muted","nohref","noresize","noshade","novalidate","nowrap","open","paused","pubdate","readonly","required","reversed","scoped","seamless","seeking","selected","spellcheck","truespeed","willvalidate"];
+function L(a,b){if(8==a.nodeType)return null;b=b.toLowerCase();if(b=="style"){var c=a.style.cssText.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").toLowerCase();return c.charAt(c.length-1)==";"?c:c+";"}c=a.getAttributeNode(b);if(!c)return null;if(A(K,b)>=0)return"true";return c.specified?c.value:null}var M=["BUTTON","INPUT","OPTGROUP","OPTION","SELECT","TEXTAREA"];
+function N(a){var b=a.tagName.toUpperCase();if(!(A(M,b)>=0))return true;if(L(a,"disabled"))return false;if(a.parentNode&&a.parentNode.nodeType==1&&"OPTGROUP"==b||"OPTION"==b)return N(a.parentNode);return true}String.fromCharCode(160);function O(){}
+function P(a,b,c){switch(typeof b){case "string":Q(a,b,c);break;case "number":c.push(isFinite(b)&&!isNaN(b)?b:"null");break;case "boolean":c.push(b);break;case "undefined":c.push("null");break;case "object":if(b==null){c.push("null");break}if(g(b)=="array"){var d=b.length;c.push("[");var e="";for(var f=0;f<d;f++){c.push(e);P(a,b[f],c);e=","}c.push("]");break}c.push("{");d="";for(e in b)if(Object.prototype.hasOwnProperty.call(b,e)){f=b[e];if(typeof f!="function"){c.push(d);Q(a,e,c);c.push(":");P(a,
+f,c);d=","}}c.push("}");break;case "function":break;default:throw Error("Unknown type: "+typeof b);}}var R={'"':'\\"',"\\":"\\\\","/":"\\/","\u0008":"\\b","\u000c":"\\f","\n":"\\n","\r":"\\r","\t":"\\t","\u000b":"\\u000b"},S=/\uffff/.test("\uffff")?/[\\\"\x00-\x1f\x7f-\uffff]/g:/[\\\"\x00-\x1f\x7f-\xff]/g;
+function Q(a,b,c){c.push('"',b.replace(S,function(d){if(d in R)return R[d];var e=d.charCodeAt(0),f="\\u";if(e<16)f+="000";else if(e<256)f+="00";else if(e<4096)f+="0";return R[d]=f+e.toString(16)}),'"')};function T(a){switch(g(a)){case "string":case "number":case "boolean":return a;case "function":return a.toString();case "array":return B(a,T);case "object":a=a;if("tagName"in a&&"nodeType"in a&&a.nodeType==1){var b={};b.ELEMENT=U(a);return b}if(h(a))return B(a,T);a=o(a,function(c,d){return typeof d=="number"||i(d)});return p(a,T);default:return null}}
+function V(a,b){if(g(a)=="array")return B(a,function(c){return V(c,b)});else if(k(a))return"ELEMENT"in a?aa(a.ELEMENT,b):p(a,function(c){return V(c,b)});return a}function W(a){a=a||document;var b=a.$wdc_;if(!b){b=a.$wdc_={};b.g=l()}return b}function U(a){var b=W(a.ownerDocument),c=q(b,function(d){return d==a});if(!c){c=":wdc:"+b.g++;b[c]=a}return c}
+function aa(a,b){a=decodeURIComponent(a);var c=b||document,d=W(c);if(!(a in d))throw new r(10,"Element does not exist in cache");var e=d[a];for(var f=e;f;){if(f==c.documentElement)return e;f=f.parentNode}delete d[a];throw new r(10,"Element is no longer attached to the DOM");};function X(a){var b=N;a=[a];var c;try{if(i(b))b=new Function(b);var d=V(a),e=b.apply(null,d);c={status:0,value:T(e)}}catch(f){c={status:"code"in f?f.code:13,value:{message:f.message}}}b=[];P(new O,c,b);return b.join("")}var Y="_".split("."),Z=this;!(Y[0]in Z)&&Z.execScript&&Z.execScript("var "+Y[0]);for(var $;Y.length&&($=Y.shift());)if(!Y.length&&X!==undefined)Z[$]=X;else Z=Z[$]?Z[$]:Z[$]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}
diff --git a/core/res/res/raw/is_selected_android.js b/core/res/res/raw/is_selected_android.js
new file mode 100644
index 0000000..0cc53b2
--- /dev/null
+++ b/core/res/res/raw/is_selected_android.js
@@ -0,0 +1,10 @@
+function(){return function(){function g(a){var b=typeof a;if(b=="object")if(a){if(a instanceof Array)return"array";else if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if(c=="[object Window]")return"object";if(c=="[object Array]"||typeof a.length=="number"&&typeof a.splice!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("splice"))return"array";if(c=="[object Function]"||typeof a.call!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
+else if(b=="function"&&typeof a.call=="undefined")return"object";return b}function h(a){var b=g(a);return b=="array"||b=="object"&&typeof a.length=="number"}function i(a){return typeof a=="string"}function k(a){a=g(a);return a=="object"||a=="array"||a=="function"}Math.floor(Math.random()*2147483648).toString(36);var l=Date.now||function(){return+new Date};function m(a,b){function c(){}c.prototype=b.prototype;a.h=b.prototype;a.prototype=new c};function n(a){this.stack=Error().stack||"";if(a)this.message=String(a)}m(n,Error);n.prototype.name="CustomError";function o(a,b,c){var d={};for(var e in a)if(b.call(c,a[e],e,a))d[e]=a[e];return d}function p(a,b,c){var d={};for(var e in a)d[e]=b.call(c,a[e],e,a);return d}function q(a,b,c){for(var d in a)if(b.call(c,a[d],d,a))return d};function r(a,b){n.call(this,b);this.code=a;this.name=s[a]||s[13]}m(r,n);var s,t={NoSuchElementError:7,NoSuchFrameError:8,UnknownCommandError:9,StaleElementReferenceError:10,ElementNotVisibleError:11,InvalidElementStateError:12,UnknownError:13,ElementNotSelectableError:15,XPathLookupError:19,NoSuchWindowError:23,InvalidCookieDomainError:24,UnableToSetCookieError:25,ModalDialogOpenedError:26,ModalDialogOpenError:27,ScriptTimeoutError:28},u={};for(var v in t)u[t[v]]=v;s=u;
+r.prototype.toString=function(){return"["+this.name+"] "+this.message};function w(a){for(var b=1;b<arguments.length;b++){var c=String(arguments[b]).replace(/\$/g,"$$$$");a=a.replace(/\%s/,c)}return a};function x(a,b){b.unshift(a);n.call(this,w.apply(null,b));b.shift();this.i=a}m(x,n);x.prototype.name="AssertionError";function y(a,b){if(!a){var c=Array.prototype.slice.call(arguments,2),d="Assertion failed";if(b){d+=": "+b;var e=c}throw new x(""+d,e||[]);}return a};var z=Array.prototype,A=z.indexOf?function(a,b,c){y(a.length!=null);return z.indexOf.call(a,b,c)}:function(a,b,c){c=c==null?0:c<0?Math.max(0,a.length+c):c;if(i(a)){if(!i(b)||b.length!=1)return-1;return a.indexOf(b,c)}for(c=c;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},B=z.map?function(a,b,c){y(a.length!=null);return z.map.call(a,b,c)}:function(a,b,c){var d=a.length,e=Array(d),f=i(a)?a.split(""):a;for(var j=0;j<d;j++)if(j in f)e[j]=b.call(c,f[j],j,a);return e};var C="",D;if(D=/WebKit\/(\S+)/){var E=D.exec(this.navigator?this.navigator.userAgent:null);C=E?E[1]:""};var F="StopIteration"in this?this.StopIteration:Error("StopIteration");function G(){}G.prototype.next=function(){throw F;};function H(a,b,c,d,e){this.a=!!b;a&&I(this,a,d);this.d=e!=undefined?e:this.c||0;if(this.a)this.d*=-1;this.f=!c}m(H,G);H.prototype.b=null;H.prototype.c=0;H.prototype.e=false;function I(a,b,c,d){if(a.b=b)a.c=typeof c=="number"?c:a.b.nodeType!=1?0:a.a?-1:1;if(typeof d=="number")a.d=d}
+H.prototype.next=function(){var a;if(this.e){if(!this.b||this.f&&this.d==0)throw F;a=this.b;var b=this.a?-1:1;if(this.c==b){var c=this.a?a.lastChild:a.firstChild;c?I(this,c):I(this,a,b*-1)}else(c=this.a?a.previousSibling:a.nextSibling)?I(this,c):I(this,a.parentNode,b*-1);this.d+=this.c*(this.a?-1:1)}else this.e=true;a=this.b;if(!this.b)throw F;return a};
+H.prototype.splice=function(){var a=this.b,b=this.a?1:-1;if(this.c==b){this.c=b*-1;this.d+=this.c*(this.a?-1:1)}this.a=!this.a;H.prototype.next.call(this);this.a=!this.a;b=h(arguments[0])?arguments[0]:arguments;for(var c=b.length-1;c>=0;c--)a.parentNode&&a.parentNode.insertBefore(b[c],a.nextSibling);a&&a.parentNode&&a.parentNode.removeChild(a)};function J(a,b,c,d){H.call(this,a,b,c,null,d)}m(J,H);J.prototype.next=function(){do J.h.next.call(this);while(this.c==-1);return this.b};var K={"class":"className",readonly:"readOnly"},L=["checked","disabled","draggable","hidden"];String.fromCharCode(160);function M(a){var b;if(a&&a.nodeType==1&&a.tagName.toUpperCase()=="OPTION")b=true;else if(a&&a.nodeType==1&&a.tagName.toUpperCase()=="INPUT"){b=a.type.toLowerCase();b=b=="checkbox"||b=="radio"}else b=false;if(!b)throw new r(15,"Element is not selectable");b="selected";var c=a.type&&a.type.toLowerCase();if("checkbox"==c||"radio"==c)b="checked";b=K[b]||b;a=a[b];a=a===undefined&&A(L,b)>=0?false:a;return!!a};function N(){}
+function O(a,b,c){switch(typeof b){case "string":P(a,b,c);break;case "number":c.push(isFinite(b)&&!isNaN(b)?b:"null");break;case "boolean":c.push(b);break;case "undefined":c.push("null");break;case "object":if(b==null){c.push("null");break}if(g(b)=="array"){var d=b.length;c.push("[");var e="";for(var f=0;f<d;f++){c.push(e);O(a,b[f],c);e=","}c.push("]");break}c.push("{");d="";for(e in b)if(Object.prototype.hasOwnProperty.call(b,e)){f=b[e];if(typeof f!="function"){c.push(d);P(a,e,c);c.push(":");O(a,
+f,c);d=","}}c.push("}");break;case "function":break;default:throw Error("Unknown type: "+typeof b);}}var Q={'"':'\\"',"\\":"\\\\","/":"\\/","\u0008":"\\b","\u000c":"\\f","\n":"\\n","\r":"\\r","\t":"\\t","\u000b":"\\u000b"},R=/\uffff/.test("\uffff")?/[\\\"\x00-\x1f\x7f-\uffff]/g:/[\\\"\x00-\x1f\x7f-\xff]/g;
+function P(a,b,c){c.push('"',b.replace(R,function(d){if(d in Q)return Q[d];var e=d.charCodeAt(0),f="\\u";if(e<16)f+="000";else if(e<256)f+="00";else if(e<4096)f+="0";return Q[d]=f+e.toString(16)}),'"')};function S(a){switch(g(a)){case "string":case "number":case "boolean":return a;case "function":return a.toString();case "array":return B(a,S);case "object":a=a;if("tagName"in a&&"nodeType"in a&&a.nodeType==1){var b={};b.ELEMENT=T(a);return b}if(h(a))return B(a,S);a=o(a,function(c,d){return typeof d=="number"||i(d)});return p(a,S);default:return null}}
+function U(a,b){if(g(a)=="array")return B(a,function(c){return U(c,b)});else if(k(a))return"ELEMENT"in a?V(a.ELEMENT,b):p(a,function(c){return U(c,b)});return a}function W(a){a=a||document;var b=a.$wdc_;if(!b){b=a.$wdc_={};b.g=l()}return b}function T(a){var b=W(a.ownerDocument),c=q(b,function(d){return d==a});if(!c){c=":wdc:"+b.g++;b[c]=a}return c}
+function V(a,b){a=decodeURIComponent(a);var c=b||document,d=W(c);if(!(a in d))throw new r(10,"Element does not exist in cache");var e=d[a];for(var f=e;f;){if(f==c.documentElement)return e;f=f.parentNode}delete d[a];throw new r(10,"Element is no longer attached to the DOM");};function X(a){var b=M;a=[a];var c;try{if(i(b))b=new Function(b);var d=U(a),e=b.apply(null,d);c={status:0,value:S(e)}}catch(f){c={status:"code"in f?f.code:13,value:{message:f.message}}}b=[];O(new N,c,b);return b.join("")}var Y="_".split("."),Z=this;!(Y[0]in Z)&&Z.execScript&&Z.execScript("var "+Y[0]);for(var $;Y.length&&($=Y.shift());)if(!Y.length&&X!==undefined)Z[$]=X;else Z=Z[$]?Z[$]:Z[$]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}
diff --git a/core/res/res/raw/set_selected_android.js b/core/res/res/raw/set_selected_android.js
new file mode 100644
index 0000000..51774e0
--- /dev/null
+++ b/core/res/res/raw/set_selected_android.js
@@ -0,0 +1,26 @@
+function(){return function(){var l=this;
+function m(a){var b=typeof a;if(b=="object")if(a){if(a instanceof Array)return"array";else if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if(c=="[object Window]")return"object";if(c=="[object Array]"||typeof a.length=="number"&&typeof a.splice!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("splice"))return"array";if(c=="[object Function]"||typeof a.call!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("call"))return"function"}else return"null";else if(b==
+"function"&&typeof a.call=="undefined")return"object";return b}function o(a){var b=m(a);return b=="array"||b=="object"&&typeof a.length=="number"}function p(a){return typeof a=="string"}function q(a){a=m(a);return a=="object"||a=="array"||a=="function"}Math.floor(Math.random()*2147483648).toString(36);var aa=Date.now||function(){return+new Date};function r(a,b){function c(){}c.prototype=b.prototype;a.j=b.prototype;a.prototype=new c};function s(a){this.stack=Error().stack||"";if(a)this.message=String(a)}r(s,Error);s.prototype.name="CustomError";function ba(a,b,c){var d={};for(var f in a)if(b.call(c,a[f],f,a))d[f]=a[f];return d}function u(a,b,c){var d={};for(var f in a)d[f]=b.call(c,a[f],f,a);return d}function ca(a,b,c){for(var d in a)if(b.call(c,a[d],d,a))return d};function v(a,b){s.call(this,b);this.code=a;this.name=w[a]||w[13]}r(v,s);var w,da={NoSuchElementError:7,NoSuchFrameError:8,UnknownCommandError:9,StaleElementReferenceError:10,ElementNotVisibleError:11,InvalidElementStateError:12,UnknownError:13,ElementNotSelectableError:15,XPathLookupError:19,NoSuchWindowError:23,InvalidCookieDomainError:24,UnableToSetCookieError:25,ModalDialogOpenedError:26,ModalDialogOpenError:27,ScriptTimeoutError:28},ea={};for(var fa in da)ea[da[fa]]=fa;w=ea;
+v.prototype.toString=function(){return"["+this.name+"] "+this.message};var ga=window;function ha(a){for(var b=1;b<arguments.length;b++){var c=String(arguments[b]).replace(/\$/g,"$$$$");a=a.replace(/\%s/,c)}return a}var ia={};function ja(a){return ia[a]||(ia[a]=String(a).replace(/\-([a-z])/g,function(b,c){return c.toUpperCase()}))};function x(a,b){b.unshift(a);s.call(this,ha.apply(null,b));b.shift();this.m=a}r(x,s);x.prototype.name="AssertionError";function y(a,b){if(!a){var c=Array.prototype.slice.call(arguments,2),d="Assertion failed";if(b){d+=": "+b;var f=c}throw new x(""+d,f||[]);}return a};var z=Array.prototype,A=z.indexOf?function(a,b,c){y(a.length!=null);return z.indexOf.call(a,b,c)}:function(a,b,c){c=c==null?0:c<0?Math.max(0,a.length+c):c;if(p(a)){if(!p(b)||b.length!=1)return-1;return a.indexOf(b,c)}for(c=c;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},B=z.map?function(a,b,c){y(a.length!=null);return z.map.call(a,b,c)}:function(a,b,c){var d=a.length,f=Array(d),e=p(a)?a.split(""):a;for(var g=0;g<d;g++)if(g in e)f[g]=b.call(c,e[g],g,a);return f},ka=z.some?function(a,b,c){y(a.length!=
+null);return z.some.call(a,b,c)}:function(a,b,c){var d=a.length,f=p(a)?a.split(""):a;for(var e=0;e<d;e++)if(e in f&&b.call(c,f[e],e,a))return true;return false};var C=true,la="",D;if(C)D=/WebKit\/(\S+)/;if(D){var ma=D.exec(l.navigator?l.navigator.userAgent:null);la=ma?ma[1]:""};var E;function F(a,b){this.x=a!==undefined?a:0;this.y=b!==undefined?b:0}F.prototype.toString=function(){return"("+this.x+", "+this.y+")"};function G(a,b){this.width=a;this.height=b}G.prototype.toString=function(){return"("+this.width+" x "+this.height+")"};G.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};function H(a){return a.nodeType==9?a:a.ownerDocument||a.document}function na(a,b){var c=[];return oa(a,b,c,true)?c[0]:undefined}function oa(a,b,c,d){if(a!=null){var f=0;for(var e;e=a.childNodes[f];f++){if(b(e)){c.push(e);if(d)return true}if(oa(e,b,c,d))return true}}return false}function I(a,b,c,d){if(!c)a=a.parentNode;c=d==null;for(var f=0;a&&(c||f<=d);){if(b(a))return a;a=a.parentNode;f++}return null}function J(a){this.e=a||l.document||document}
+function pa(a){a=!C&&a.e.compatMode=="CSS1Compat"?a.e.documentElement:a.e.body;return new F(a.scrollLeft,a.scrollTop)};function qa(a,b){try{var c;if(typeof b.selectSingleNode!="undefined"){var d=H(b);typeof d.setProperty!="undefined"&&d.setProperty("SelectionLanguage","XPath");c=b.selectSingleNode(a)}else if(document.implementation.hasFeature("XPath","3.0")){d=H(b);var f=d.createNSResolver(d.documentElement);c=d.evaluate(a,b,f,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue}else c=null}catch(e){return null}if(!c)return null;if(c.nodeType!=1)throw Error("Returned node is not an element: "+a);return c};var K="StopIteration"in l?l.StopIteration:Error("StopIteration");function ra(){}ra.prototype.next=function(){throw K;};function L(a,b,c,d,f){this.a=!!b;a&&M(this,a,d);this.d=f!=undefined?f:this.c||0;if(this.a)this.d*=-1;this.h=!c}r(L,ra);L.prototype.b=null;L.prototype.c=0;L.prototype.g=false;function M(a,b,c,d){if(a.b=b)a.c=typeof c=="number"?c:a.b.nodeType!=1?0:a.a?-1:1;if(typeof d=="number")a.d=d}
+L.prototype.next=function(){var a;if(this.g){if(!this.b||this.h&&this.d==0)throw K;a=this.b;var b=this.a?-1:1;if(this.c==b){var c=this.a?a.lastChild:a.firstChild;c?M(this,c):M(this,a,b*-1)}else(c=this.a?a.previousSibling:a.nextSibling)?M(this,c):M(this,a.parentNode,b*-1);this.d+=this.c*(this.a?-1:1)}else this.g=true;a=this.b;if(!this.b)throw K;return a};
+L.prototype.splice=function(){var a=this.b,b=this.a?1:-1;if(this.c==b){this.c=b*-1;this.d+=this.c*(this.a?-1:1)}this.a=!this.a;L.prototype.next.call(this);this.a=!this.a;b=o(arguments[0])?arguments[0]:arguments;for(var c=b.length-1;c>=0;c--)a.parentNode&&a.parentNode.insertBefore(b[c],a.nextSibling);a&&a.parentNode&&a.parentNode.removeChild(a)};function N(a,b,c,d){L.call(this,a,b,c,null,d)}r(N,L);N.prototype.next=function(){do N.j.next.call(this);while(this.c==-1);return this.b};function sa(a,b){var c=H(a);if(c.defaultView&&c.defaultView.getComputedStyle)if(c=c.defaultView.getComputedStyle(a,null))return c[b]||c.getPropertyValue(b);return""}function O(a,b){return sa(a,b)||(a.currentStyle?a.currentStyle[b]:null)||a.style[b]}
+function ta(a){var b=H(a),c=O(a,"position"),d=c=="fixed"||c=="absolute";for(a=a.parentNode;a&&a!=b;a=a.parentNode){c=O(a,"position");d=d&&c=="static"&&a!=b.documentElement&&a!=b.body;if(!d&&(a.scrollWidth>a.clientWidth||a.scrollHeight>a.clientHeight||c=="fixed"||c=="absolute"))return a}return null};function P(a,b){return!!a&&a.nodeType==1&&(!b||a.tagName.toUpperCase()==b)}
+var ua={"class":"className",readonly:"readOnly"},va=["checked","disabled","draggable","hidden"],wa=["async","autofocus","autoplay","checked","compact","complete","controls","declare","defaultchecked","defaultselected","defer","disabled","draggable","ended","formnovalidate","hidden","indeterminate","iscontenteditable","ismap","itemscope","loop","multiple","muted","nohref","noresize","noshade","novalidate","nowrap","open","paused","pubdate","readonly","required","reversed","scoped","seamless","seeking",
+"selected","spellcheck","truespeed","willvalidate"];function xa(a,b){if(8==a.nodeType)return null;b=b.toLowerCase();if(b=="style"){var c=a.style.cssText.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").toLowerCase();return c.charAt(c.length-1)==";"?c:c+";"}c=a.getAttributeNode(b);if(!c)return null;if(A(wa,b)>=0)return"true";return c.specified?c.value:null}var ya=["BUTTON","INPUT","OPTGROUP","OPTION","SELECT","TEXTAREA"];
+function za(a){var b=a.tagName.toUpperCase();if(!(A(ya,b)>=0))return true;if(xa(a,"disabled"))return false;if(a.parentNode&&a.parentNode.nodeType==1&&"OPTGROUP"==b||"OPTION"==b)return za(a.parentNode);return true}function Q(a){for(a=a.parentNode;a&&a.nodeType!=1&&a.nodeType!=9&&a.nodeType!=11;)a=a.parentNode;return P(a)?a:null}function R(a,b){b=ja(String(b));return sa(a,b)||Aa(a,b)}
+function Aa(a,b){var c=(a.currentStyle||a.style)[b];if(c!="inherit")return c!==undefined?c:null;return(c=Q(a))?Aa(c,b):null}function Ba(a){if(m(a.getBBox)=="function")return a.getBBox();var b;if(O(a,"display")!="none")b=new G(a.offsetWidth,a.offsetHeight);else{b=a.style;var c=b.display,d=b.visibility,f=b.position;b.visibility="hidden";b.position="absolute";b.display="inline";var e;e=a.offsetWidth;a=a.offsetHeight;b.display=c;b.position=f;b.visibility=d;b=new G(e,a)}return b}
+function S(a,b){function c(e){if(R(e,"display")=="none")return false;e=Q(e);return!e||c(e)}function d(e){var g=Ba(e);if(g.height>0&&g.width>0)return true;if(e.innerText||e.textContent)if(Ca.test(e.innerText||e.textContent))return true;return C&&ka(e.childNodes,function(k){return P(k)&&d(k)})}if(!P(a))throw Error("Argument to isShown must be of type Element");if(P(a,"TITLE"))return(H(a)?H(a).parentWindow||H(a).defaultView:window)==ga;if(P(a,"OPTION")||P(a,"OPTGROUP")){var f=I(a,function(e){return P(e,
+"SELECT")});return!!f&&S(f)}if(P(a,"MAP")){if(!a.name)return false;f=H(a);f=f.evaluate?qa('/descendant::*[@usemap = "#'+a.name+'"]',f):na(f,function(e){return P(e)&&xa(e,"usemap")=="#"+a.name});return!!f&&S(f)}if(P(a,"AREA")){f=I(a,function(e){return P(e,"MAP")});return!!f&&S(f)}if(P(a,"INPUT")&&a.type.toLowerCase()=="hidden")return false;if(R(a,"visibility")=="hidden")return false;if(!c(a))return false;if(!b&&Da(a)==0)return false;if(!d(a))return false;return true}
+var Ea="[\\s\\xa0"+String.fromCharCode(160)+"]+",Ca=RegExp("^"+Ea+"$");function Da(a){var b=1,c=R(a,"opacity");if(c)b=Number(c);if(a=Q(a))b*=Da(a);return b};var Fa=["dragstart","dragexit","mouseover","mouseout"];
+function T(a,b,c){var d=H(a),f=d?d.parentWindow||d.defaultView:window,e=new F;if(a.nodeType==1)if(a.getBoundingClientRect){var g=a.getBoundingClientRect();e.x=g.left;e.y=g.top}else{g=pa(a?new J(H(a)):E||(E=new J));var k,h=H(a);k=O(a,"position");var i=new F(0,0),t=(h?h.nodeType==9?h:H(h):document).documentElement;if(a!=t)if(a.getBoundingClientRect){k=a.getBoundingClientRect();h=pa(h?new J(H(h)):E||(E=new J));i.x=k.left+h.x;i.y=k.top+h.y}else if(h.getBoxObjectFor){k=h.getBoxObjectFor(a);h=h.getBoxObjectFor(t);
+i.x=k.screenX-h.screenX;i.y=k.screenY-h.screenY}else{var j=a;do{i.x+=j.offsetLeft;i.y+=j.offsetTop;if(j!=a){i.x+=j.clientLeft||0;i.y+=j.clientTop||0}if(C&&O(j,"position")=="fixed"){i.x+=h.body.scrollLeft;i.y+=h.body.scrollTop;break}j=j.offsetParent}while(j&&j!=a);if(C&&k=="absolute")i.y-=h.body.offsetTop;for(j=a;(j=ta(j))&&j!=h.body&&j!=t;){i.x-=j.scrollLeft;i.y-=j.scrollTop}}e.x=i.x-g.x;e.y=i.y-g.y}else{g=m(a.f)=="function";i=a;if(a.targetTouches)i=a.targetTouches[0];else if(g&&a.f().targetTouches)i=
+a.f().targetTouches[0];e.x=i.clientX;e.y=i.clientY}var n=c||{};c=(n.x||0)+e.x;e=(n.y||0)+e.y;g=n.button||0;i=n.bubble||true;k=null;if(A(Fa,b)>=0)k=n.related||null;h=!!n.alt;t=!!n.control;j=!!n.shift;n=!!n.meta;if(a.fireEvent&&d&&d.createEventObject){a=d.createEventObject();a.altKey=h;a.k=t;a.metaKey=n;a.shiftKey=j;a.clientX=c;a.clientY=e;a.button=g;a.relatedTarget=k}else{a=d.createEvent("MouseEvents");if(a.initMouseEvent)a.initMouseEvent(b,i,true,f,1,0,0,c,e,t,h,j,n,g,k);else{a.initEvent(b,i,true);
+a.shiftKey=j;a.metaKey=n;a.altKey=h;a.ctrlKey=t;a.button=g}}return a}function U(a,b,c){var d=c||{};c=d.keyCode||0;var f=d.charCode||0,e=!!d.alt,g=!!d.ctrl,k=!!d.shift;d=!!d.meta;a=H(a).createEvent("Events");a.initEvent(b,true,true);a.charCode=f;a.keyCode=c;a.altKey=e;a.ctrlKey=g;a.metaKey=d;a.shiftKey=k;return a}
+function Ga(a,b,c){var d=H(a),f=c||{};c=f.bubble!==false;var e=!!f.alt,g=!!f.control,k=!!f.shift;f=!!f.meta;if(a.fireEvent&&d&&d.createEventObject){a=d.createEventObject();a.altKey=e;a.l=g;a.metaKey=f;a.shiftKey=k}else{a=d.createEvent("HTMLEvents");a.initEvent(b,c,true);a.shiftKey=k;a.metaKey=f;a.altKey=e;a.ctrlKey=g}return a}var V={};V.click=T;V.keydown=U;V.keypress=U;V.keyup=U;V.mousedown=T;V.mousemove=T;V.mouseout=T;V.mouseover=T;V.mouseup=T;
+function Ha(a,b,c){c=(V[b]||Ga)(a,b,c);if(m(a.fireEvent)=="function"||q(a.fireEvent)){try{(H(a)?H(a).parentWindow||H(a).defaultView:window).event=c}catch(d){}a=a.fireEvent("on"+b,c)}else a=a.dispatchEvent(c);return a};function Ia(a){var b;if(P(a,"OPTION"))b=true;else if(P(a,"INPUT")){b=a.type.toLowerCase();b=b=="checkbox"||b=="radio"}else b=false;if(!b)throw new v(15,"Element is not selectable");b="selected";var c=a.type&&a.type.toLowerCase();if("checkbox"==c||"radio"==c)b="checked";b=ua[b]||b;a=a[b];a=a===undefined&&A(va,b)>=0?false:a;return!!a}function Ja(a){return P(a,"SELECT")}
+function Ka(a,b){if(!za(a))throw new v(12,"Element is not currently enabled and may not be manipulated");if(!S(a,true))throw new v(11,"Element is not currently visible and may not be manipulated");if(P(a,"INPUT")){var c=a.type.toLowerCase();if(c=="checkbox"||c=="radio"){if(a.checked!=b){if(a.type=="radio"&&!b)throw new v(12,"You may not deselect a radio button");if(b!=Ia(a)){a.checked=b;Ha(a,"change")}}}else throw new v(15,"You may not select an unselectable input element: "+a.type);}else if(P(a,
+"OPTION")){c=I(a,Ja);if(!c.multiple&&!b)throw new v(15,"You may not deselect an option within a select that does not support multiple selections.");if(b!=Ia(a)){a.selected=b;Ha(c,"change")}}else throw new v(15,"You may not select an unselectable element: "+a.tagName);};function W(a){switch(m(a)){case "string":case "number":case "boolean":return a;case "function":return a.toString();case "array":return B(a,W);case "object":a=a;if("tagName"in a&&"nodeType"in a&&a.nodeType==1){var b={};b.ELEMENT=La(a);return b}if(o(a))return B(a,W);a=ba(a,function(c,d){return typeof d=="number"||p(d)});return u(a,W);default:return null}}
+function X(a,b){if(m(a)=="array")return B(a,function(c){return X(c,b)});else if(q(a))return"ELEMENT"in a?Ma(a.ELEMENT,b):u(a,function(c){return X(c,b)});return a}function Na(a){a=a||document;var b=a.$wdc_;if(!b){b=a.$wdc_={};b.i=aa()}return b}function La(a){var b=Na(a.ownerDocument),c=ca(b,function(d){return d==a});if(!c){c=":wdc:"+b.i++;b[c]=a}return c}
+function Ma(a,b){a=decodeURIComponent(a);var c=b||document,d=Na(c);if(!(a in d))throw new v(10,"Element does not exist in cache");var f=d[a];for(var e=f;e;){if(e==c.documentElement)return f;e=e.parentNode}delete d[a];throw new v(10,"Element is no longer attached to the DOM");};function Oa(a,b){var c=Ka,d=[a,b];try{if(p(c))c=new Function(c);var f=X(d),e=c.apply(null,f);W(e)}catch(g){}}var Y="_".split("."),Z=l;!(Y[0]in Z)&&Z.execScript&&Z.execScript("var "+Y[0]);for(var $;Y.length&&($=Y.shift());)if(!Y.length&&Oa!==undefined)Z[$]=Oa;else Z=Z[$]?Z[$]:Z[$]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}
diff --git a/core/res/res/raw/toggle_android.js b/core/res/res/raw/toggle_android.js
new file mode 100644
index 0000000..d4f4934
--- /dev/null
+++ b/core/res/res/raw/toggle_android.js
@@ -0,0 +1,29 @@
+function(){return function(){var l=this;
+function m(a){var b=typeof a;if(b=="object")if(a){if(a instanceof Array)return"array";else if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if(c=="[object Window]")return"object";if(c=="[object Array]"||typeof a.length=="number"&&typeof a.splice!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("splice"))return"array";if(c=="[object Function]"||typeof a.call!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("call"))return"function"}else return"null";else if(b==
+"function"&&typeof a.call=="undefined")return"object";return b}function aa(a){var b=m(a);return b=="array"||b=="object"&&typeof a.length=="number"}function o(a){return typeof a=="string"}function ba(a){a=m(a);return a=="object"||a=="array"||a=="function"}Math.floor(Math.random()*2147483648).toString(36);var ca=Date.now||function(){return+new Date};function p(a,b){function c(){}c.prototype=b.prototype;a.j=b.prototype;a.prototype=new c};function q(a){this.stack=Error().stack||"";if(a)this.message=String(a)}p(q,Error);q.prototype.name="CustomError";function da(a,b,c){var d={};for(var f in a)if(b.call(c,a[f],f,a))d[f]=a[f];return d}function ea(a,b,c){var d={};for(var f in a)d[f]=b.call(c,a[f],f,a);return d}function fa(a,b,c){for(var d in a)if(b.call(c,a[d],d,a))return d};function r(a,b){q.call(this,b);this.code=a;this.name=s[a]||s[13]}p(r,q);var s,ga={NoSuchElementError:7,NoSuchFrameError:8,UnknownCommandError:9,StaleElementReferenceError:10,ElementNotVisibleError:11,InvalidElementStateError:12,UnknownError:13,ElementNotSelectableError:15,XPathLookupError:19,NoSuchWindowError:23,InvalidCookieDomainError:24,UnableToSetCookieError:25,ModalDialogOpenedError:26,ModalDialogOpenError:27,ScriptTimeoutError:28},ha={};for(var ia in ga)ha[ga[ia]]=ia;s=ha;
+r.prototype.toString=function(){return"["+this.name+"] "+this.message};var ja=window;function ka(a){for(var b=1;b<arguments.length;b++){var c=String(arguments[b]).replace(/\$/g,"$$$$");a=a.replace(/\%s/,c)}return a}var la={};function ma(a){return la[a]||(la[a]=String(a).replace(/\-([a-z])/g,function(b,c){return c.toUpperCase()}))};function u(a,b){b.unshift(a);q.call(this,ka.apply(null,b));b.shift();this.m=a}p(u,q);u.prototype.name="AssertionError";function v(a,b){if(!a){var c=Array.prototype.slice.call(arguments,2),d="Assertion failed";if(b){d+=": "+b;var f=c}throw new u(""+d,f||[]);}return a};var w=Array.prototype,x=w.indexOf?function(a,b,c){v(a.length!=null);return w.indexOf.call(a,b,c)}:function(a,b,c){c=c==null?0:c<0?Math.max(0,a.length+c):c;if(o(a)){if(!o(b)||b.length!=1)return-1;return a.indexOf(b,c)}for(c=c;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},y=w.map?function(a,b,c){v(a.length!=null);return w.map.call(a,b,c)}:function(a,b,c){var d=a.length,f=Array(d),e=o(a)?a.split(""):a;for(var g=0;g<d;g++)if(g in e)f[g]=b.call(c,e[g],g,a);return f},na=w.some?function(a,b,c){v(a.length!=
+null);return w.some.call(a,b,c)}:function(a,b,c){var d=a.length,f=o(a)?a.split(""):a;for(var e=0;e<d;e++)if(e in f&&b.call(c,f[e],e,a))return true;return false};var z=true,oa="",A;if(z)A=/WebKit\/(\S+)/;if(A){var pa=A.exec(l.navigator?l.navigator.userAgent:null);oa=pa?pa[1]:""};var B;function C(a,b){this.x=a!==undefined?a:0;this.y=b!==undefined?b:0}C.prototype.toString=function(){return"("+this.x+", "+this.y+")"};function D(a,b){this.width=a;this.height=b}D.prototype.toString=function(){return"("+this.width+" x "+this.height+")"};D.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};function E(a){return a.nodeType==9?a:a.ownerDocument||a.document}function qa(a,b){var c=[];return ra(a,b,c,true)?c[0]:undefined}function ra(a,b,c,d){if(a!=null){var f=0;for(var e;e=a.childNodes[f];f++){if(b(e)){c.push(e);if(d)return true}if(ra(e,b,c,d))return true}}return false}function F(a,b,c,d){if(!c)a=a.parentNode;c=d==null;for(var f=0;a&&(c||f<=d);){if(b(a))return a;a=a.parentNode;f++}return null}function G(a){this.e=a||l.document||document}
+function sa(a){a=!z&&a.e.compatMode=="CSS1Compat"?a.e.documentElement:a.e.body;return new C(a.scrollLeft,a.scrollTop)};function ta(a,b){try{var c;if(typeof b.selectSingleNode!="undefined"){var d=E(b);typeof d.setProperty!="undefined"&&d.setProperty("SelectionLanguage","XPath");c=b.selectSingleNode(a)}else if(document.implementation.hasFeature("XPath","3.0")){d=E(b);var f=d.createNSResolver(d.documentElement);c=d.evaluate(a,b,f,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue}else c=null}catch(e){return null}if(!c)return null;if(c.nodeType!=1)throw Error("Returned node is not an element: "+a);return c};var H="StopIteration"in l?l.StopIteration:Error("StopIteration");function ua(){}ua.prototype.next=function(){throw H;};function I(a,b,c,d,f){this.a=!!b;a&&J(this,a,d);this.d=f!=undefined?f:this.c||0;if(this.a)this.d*=-1;this.h=!c}p(I,ua);I.prototype.b=null;I.prototype.c=0;I.prototype.g=false;function J(a,b,c,d){if(a.b=b)a.c=typeof c=="number"?c:a.b.nodeType!=1?0:a.a?-1:1;if(typeof d=="number")a.d=d}
+I.prototype.next=function(){var a;if(this.g){if(!this.b||this.h&&this.d==0)throw H;a=this.b;var b=this.a?-1:1;if(this.c==b){var c=this.a?a.lastChild:a.firstChild;c?J(this,c):J(this,a,b*-1)}else(c=this.a?a.previousSibling:a.nextSibling)?J(this,c):J(this,a.parentNode,b*-1);this.d+=this.c*(this.a?-1:1)}else this.g=true;a=this.b;if(!this.b)throw H;return a};
+I.prototype.splice=function(){var a=this.b,b=this.a?1:-1;if(this.c==b){this.c=b*-1;this.d+=this.c*(this.a?-1:1)}this.a=!this.a;I.prototype.next.call(this);this.a=!this.a;b=aa(arguments[0])?arguments[0]:arguments;for(var c=b.length-1;c>=0;c--)a.parentNode&&a.parentNode.insertBefore(b[c],a.nextSibling);a&&a.parentNode&&a.parentNode.removeChild(a)};function K(a,b,c,d){I.call(this,a,b,c,null,d)}p(K,I);K.prototype.next=function(){do K.j.next.call(this);while(this.c==-1);return this.b};function va(a,b){var c=E(a);if(c.defaultView&&c.defaultView.getComputedStyle)if(c=c.defaultView.getComputedStyle(a,null))return c[b]||c.getPropertyValue(b);return""}function L(a,b){return va(a,b)||(a.currentStyle?a.currentStyle[b]:null)||a.style[b]}
+function wa(a){var b=E(a),c=L(a,"position"),d=c=="fixed"||c=="absolute";for(a=a.parentNode;a&&a!=b;a=a.parentNode){c=L(a,"position");d=d&&c=="static"&&a!=b.documentElement&&a!=b.body;if(!d&&(a.scrollWidth>a.clientWidth||a.scrollHeight>a.clientHeight||c=="fixed"||c=="absolute"))return a}return null};function M(a,b){return!!a&&a.nodeType==1&&(!b||a.tagName.toUpperCase()==b)}
+var xa={"class":"className",readonly:"readOnly"},ya=["checked","disabled","draggable","hidden"],za=["async","autofocus","autoplay","checked","compact","complete","controls","declare","defaultchecked","defaultselected","defer","disabled","draggable","ended","formnovalidate","hidden","indeterminate","iscontenteditable","ismap","itemscope","loop","multiple","muted","nohref","noresize","noshade","novalidate","nowrap","open","paused","pubdate","readonly","required","reversed","scoped","seamless","seeking",
+"selected","spellcheck","truespeed","willvalidate"];function Aa(a,b){if(8==a.nodeType)return null;b=b.toLowerCase();if(b=="style"){var c=a.style.cssText.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").toLowerCase();return c.charAt(c.length-1)==";"?c:c+";"}c=a.getAttributeNode(b);if(!c)return null;if(x(za,b)>=0)return"true";return c.specified?c.value:null}var Ba=["BUTTON","INPUT","OPTGROUP","OPTION","SELECT","TEXTAREA"];
+function Ca(a){var b=a.tagName.toUpperCase();if(!(x(Ba,b)>=0))return true;if(Aa(a,"disabled"))return false;if(a.parentNode&&a.parentNode.nodeType==1&&"OPTGROUP"==b||"OPTION"==b)return Ca(a.parentNode);return true}function N(a){for(a=a.parentNode;a&&a.nodeType!=1&&a.nodeType!=9&&a.nodeType!=11;)a=a.parentNode;return M(a)?a:null}function O(a,b){b=ma(String(b));return va(a,b)||Da(a,b)}
+function Da(a,b){var c=(a.currentStyle||a.style)[b];if(c!="inherit")return c!==undefined?c:null;return(c=N(a))?Da(c,b):null}function Ea(a){if(m(a.getBBox)=="function")return a.getBBox();var b;if(L(a,"display")!="none")b=new D(a.offsetWidth,a.offsetHeight);else{b=a.style;var c=b.display,d=b.visibility,f=b.position;b.visibility="hidden";b.position="absolute";b.display="inline";var e;e=a.offsetWidth;a=a.offsetHeight;b.display=c;b.position=f;b.visibility=d;b=new D(e,a)}return b}
+function P(a,b){function c(e){if(O(e,"display")=="none")return false;e=N(e);return!e||c(e)}function d(e){var g=Ea(e);if(g.height>0&&g.width>0)return true;if(e.innerText||e.textContent)if(Fa.test(e.innerText||e.textContent))return true;return z&&na(e.childNodes,function(k){return M(k)&&d(k)})}if(!M(a))throw Error("Argument to isShown must be of type Element");if(M(a,"TITLE"))return(E(a)?E(a).parentWindow||E(a).defaultView:window)==ja;if(M(a,"OPTION")||M(a,"OPTGROUP")){var f=F(a,function(e){return M(e,
+"SELECT")});return!!f&&P(f)}if(M(a,"MAP")){if(!a.name)return false;f=E(a);f=f.evaluate?ta('/descendant::*[@usemap = "#'+a.name+'"]',f):qa(f,function(e){return M(e)&&Aa(e,"usemap")=="#"+a.name});return!!f&&P(f)}if(M(a,"AREA")){f=F(a,function(e){return M(e,"MAP")});return!!f&&P(f)}if(M(a,"INPUT")&&a.type.toLowerCase()=="hidden")return false;if(O(a,"visibility")=="hidden")return false;if(!c(a))return false;if(!b&&Ga(a)==0)return false;if(!d(a))return false;return true}
+var Ha="[\\s\\xa0"+String.fromCharCode(160)+"]+",Fa=RegExp("^"+Ha+"$");function Ga(a){var b=1,c=O(a,"opacity");if(c)b=Number(c);if(a=N(a))b*=Ga(a);return b};var Ia=["dragstart","dragexit","mouseover","mouseout"];
+function Q(a,b,c){var d=E(a),f=d?d.parentWindow||d.defaultView:window,e=new C;if(a.nodeType==1)if(a.getBoundingClientRect){var g=a.getBoundingClientRect();e.x=g.left;e.y=g.top}else{g=sa(a?new G(E(a)):B||(B=new G));var k,h=E(a);k=L(a,"position");var i=new C(0,0),t=(h?h.nodeType==9?h:E(h):document).documentElement;if(a!=t)if(a.getBoundingClientRect){k=a.getBoundingClientRect();h=sa(h?new G(E(h)):B||(B=new G));i.x=k.left+h.x;i.y=k.top+h.y}else if(h.getBoxObjectFor){k=h.getBoxObjectFor(a);h=h.getBoxObjectFor(t);
+i.x=k.screenX-h.screenX;i.y=k.screenY-h.screenY}else{var j=a;do{i.x+=j.offsetLeft;i.y+=j.offsetTop;if(j!=a){i.x+=j.clientLeft||0;i.y+=j.clientTop||0}if(z&&L(j,"position")=="fixed"){i.x+=h.body.scrollLeft;i.y+=h.body.scrollTop;break}j=j.offsetParent}while(j&&j!=a);if(z&&k=="absolute")i.y-=h.body.offsetTop;for(j=a;(j=wa(j))&&j!=h.body&&j!=t;){i.x-=j.scrollLeft;i.y-=j.scrollTop}}e.x=i.x-g.x;e.y=i.y-g.y}else{g=m(a.f)=="function";i=a;if(a.targetTouches)i=a.targetTouches[0];else if(g&&a.f().targetTouches)i=
+a.f().targetTouches[0];e.x=i.clientX;e.y=i.clientY}var n=c||{};c=(n.x||0)+e.x;e=(n.y||0)+e.y;g=n.button||0;i=n.bubble||true;k=null;if(x(Ia,b)>=0)k=n.related||null;h=!!n.alt;t=!!n.control;j=!!n.shift;n=!!n.meta;if(a.fireEvent&&d&&d.createEventObject){a=d.createEventObject();a.altKey=h;a.k=t;a.metaKey=n;a.shiftKey=j;a.clientX=c;a.clientY=e;a.button=g;a.relatedTarget=k}else{a=d.createEvent("MouseEvents");if(a.initMouseEvent)a.initMouseEvent(b,i,true,f,1,0,0,c,e,t,h,j,n,g,k);else{a.initEvent(b,i,true);
+a.shiftKey=j;a.metaKey=n;a.altKey=h;a.ctrlKey=t;a.button=g}}return a}function R(a,b,c){var d=c||{};c=d.keyCode||0;var f=d.charCode||0,e=!!d.alt,g=!!d.ctrl,k=!!d.shift;d=!!d.meta;a=E(a).createEvent("Events");a.initEvent(b,true,true);a.charCode=f;a.keyCode=c;a.altKey=e;a.ctrlKey=g;a.metaKey=d;a.shiftKey=k;return a}
+function Ja(a,b,c){var d=E(a),f=c||{};c=f.bubble!==false;var e=!!f.alt,g=!!f.control,k=!!f.shift;f=!!f.meta;if(a.fireEvent&&d&&d.createEventObject){a=d.createEventObject();a.altKey=e;a.l=g;a.metaKey=f;a.shiftKey=k}else{a=d.createEvent("HTMLEvents");a.initEvent(b,c,true);a.shiftKey=k;a.metaKey=f;a.altKey=e;a.ctrlKey=g}return a}var S={};S.click=Q;S.keydown=R;S.keypress=R;S.keyup=R;S.mousedown=Q;S.mousemove=Q;S.mouseout=Q;S.mouseover=Q;S.mouseup=Q;
+function Ka(a,b,c){c=(S[b]||Ja)(a,b,c);if(m(a.fireEvent)=="function"||ba(a.fireEvent)){try{(E(a)?E(a).parentWindow||E(a).defaultView:window).event=c}catch(d){}a=a.fireEvent("on"+b,c)}else a=a.dispatchEvent(c);return a};function T(a){var b;if(M(a,"OPTION"))b=true;else if(M(a,"INPUT")){b=a.type.toLowerCase();b=b=="checkbox"||b=="radio"}else b=false;if(!b)throw new r(15,"Element is not selectable");b="selected";var c=a.type&&a.type.toLowerCase();if("checkbox"==c||"radio"==c)b="checked";b=xa[b]||b;a=a[b];a=a===undefined&&x(ya,b)>=0?false:a;return!!a}function La(a){return M(a,"SELECT")}
+function Ma(a){if(M(a,"INPUT")&&"radio"==a.type)throw new r(12,"You may not toggle a radio button");var b=!T(a);if(!Ca(a))throw new r(12,"Element is not currently enabled and may not be manipulated");if(!P(a,true))throw new r(11,"Element is not currently visible and may not be manipulated");if(M(a,"INPUT")){var c=a.type.toLowerCase();if(c=="checkbox"||c=="radio"){if(a.checked!=b){if(a.type=="radio"&&!b)throw new r(12,"You may not deselect a radio button");if(b!=T(a)){a.checked=b;Ka(a,"change")}}}else throw new r(15,
+"You may not select an unselectable input element: "+a.type);}else if(M(a,"OPTION")){c=F(a,La);if(!c.multiple&&!b)throw new r(15,"You may not deselect an option within a select that does not support multiple selections.");if(b!=T(a)){a.selected=b;Ka(c,"change")}}else throw new r(15,"You may not select an unselectable element: "+a.tagName);return T(a)};function Na(){}
+function U(a,b,c){switch(typeof b){case "string":Oa(a,b,c);break;case "number":c.push(isFinite(b)&&!isNaN(b)?b:"null");break;case "boolean":c.push(b);break;case "undefined":c.push("null");break;case "object":if(b==null){c.push("null");break}if(m(b)=="array"){var d=b.length;c.push("[");var f="";for(var e=0;e<d;e++){c.push(f);U(a,b[e],c);f=","}c.push("]");break}c.push("{");d="";for(f in b)if(Object.prototype.hasOwnProperty.call(b,f)){e=b[f];if(typeof e!="function"){c.push(d);Oa(a,f,c);c.push(":");U(a,
+e,c);d=","}}c.push("}");break;case "function":break;default:throw Error("Unknown type: "+typeof b);}}var V={'"':'\\"',"\\":"\\\\","/":"\\/","\u0008":"\\b","\u000c":"\\f","\n":"\\n","\r":"\\r","\t":"\\t","\u000b":"\\u000b"},Pa=/\uffff/.test("\uffff")?/[\\\"\x00-\x1f\x7f-\uffff]/g:/[\\\"\x00-\x1f\x7f-\xff]/g;
+function Oa(a,b,c){c.push('"',b.replace(Pa,function(d){if(d in V)return V[d];var f=d.charCodeAt(0),e="\\u";if(f<16)e+="000";else if(f<256)e+="00";else if(f<4096)e+="0";return V[d]=e+f.toString(16)}),'"')};function W(a){switch(m(a)){case "string":case "number":case "boolean":return a;case "function":return a.toString();case "array":return y(a,W);case "object":a=a;if("tagName"in a&&"nodeType"in a&&a.nodeType==1){var b={};b.ELEMENT=Qa(a);return b}if(aa(a))return y(a,W);a=da(a,function(c,d){return typeof d=="number"||o(d)});return ea(a,W);default:return null}}
+function X(a,b){if(m(a)=="array")return y(a,function(c){return X(c,b)});else if(ba(a))return"ELEMENT"in a?Ra(a.ELEMENT,b):ea(a,function(c){return X(c,b)});return a}function Sa(a){a=a||document;var b=a.$wdc_;if(!b){b=a.$wdc_={};b.i=ca()}return b}function Qa(a){var b=Sa(a.ownerDocument),c=fa(b,function(d){return d==a});if(!c){c=":wdc:"+b.i++;b[c]=a}return c}
+function Ra(a,b){a=decodeURIComponent(a);var c=b||document,d=Sa(c);if(!(a in d))throw new r(10,"Element does not exist in cache");var f=d[a];for(var e=f;e;){if(e==c.documentElement)return f;e=e.parentNode}delete d[a];throw new r(10,"Element is no longer attached to the DOM");};function Ta(a){var b=Ma;a=[a];var c;try{if(o(b))b=new Function(b);var d=X(a),f=b.apply(null,d);c={status:0,value:W(f)}}catch(e){c={status:"code"in e?e.code:13,value:{message:e.message}}}b=[];U(new Na,c,b);return b.join("")}var Y="_".split("."),Z=l;!(Y[0]in Z)&&Z.execScript&&Z.execScript("var "+Y[0]);for(var $;Y.length&&($=Y.shift());)if(!Y.length&&Ta!==undefined)Z[$]=Ta;else Z=Z[$]?Z[$]:Z[$]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}
diff --git a/core/res/res/raw/webdriver_readme.txt b/core/res/res/raw/webdriver_readme.txt
new file mode 100644
index 0000000..0ab6603
--- /dev/null
+++ b/core/res/res/raw/webdriver_readme.txt
@@ -0,0 +1,37 @@
+The JavaScript files *_android.js are used in frameworks/base/core/java/android/webkit/webdriver/
+. Those files contain closure compiled JavaScript from
+http://selenium.googlecode.com. They are under the Apache 2.0 licence:
+/** @license
+Copyright 2010 WebDriver committers
+Copyright 2010 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+The licence is not included in the compiled code to minimize the size
+of JavaScript injected into web pages.
+
+Those files can be generated by doing the following:
+$svn checkout http://selenium.googlecode.com/svn/trunk/ .
+$./go //javascript/webdriver-atoms/inject:<js_fragment_name>
+
+Where <js_fragment_name> should be replaced by the actual name of the fragment to
+generate. For example to generate is_selected_android.js, execute:
+$./go //javascript/webdriver-atoms/inject:is_selected
+
+The build file for those rules is under the following:
+http://code.google.com/p/selenium/source/browse/trunk/javascript/webdriver-atoms/inject/build.desc
+Every js_fragment rule generates a JavaScript file containing the corresponding
+JavaScript code snippet.
+
+The current version of the files was generated using revision 11823.
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 3731aaa..b0bd0f4 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"للسماح للتطبيق باسترداد معلومات حول المهام المُشغلة الحالية والحديثة. قد يسمح ذلك للتطبيقات الضارة باكتشاف معلومات خاصة حول التطبيقات الأخرى."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"إعادة ترتيب التطبيقات التي قيد التشغيل"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"للسماح لتطبيق ما بنقل المهام إلى المقدمة والخلفية. قد تفرض التطبيقات الضارة نفسها إلى المقدمة بدون تحكم منك."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"تمكين تصحيح أخطاء التطبيق"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"للسماح لتطبيق ما بتشغيل تصحيح الأخطاء لتطبيق آخر. قد تستخدم التطبيقات الضارة ذلك لإنهاء التطبيقات الأخرى."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"تغيير إعدادات واجهة المستخدم"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 0173f45..2c2035e 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Разрешава на приложението да извлича информация за задачите, изпълнявани понастоящем и неотдавна. Може да позволи на злонамерените приложения да открият поверителна информация за други приложения."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"пренареждане на изпълняваните приложения"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Разрешава на приложението да прехвърля задачи на преден и на заден план. Злонамерените приложения могат сами да се изведат на преден план без ваша намеса."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"активиране на отстраняването на грешки в приложения"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Разрешава на приложението да включва отстраняването на грешки в друго приложение. Злонамерените приложения могат да използват това, за да прекратят други приложения."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"промяна на настройките ви за потребителския интерфейс"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index e94e7e5..46b3342 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Permet a l\'aplicació recuperar informació sobre les tasques que s\'executen actualment i que s\'han executat recentment. Pot permetre a les aplicacions malicioses descobrir informació privada sobre altres aplicacions."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"canviar l\'ordre de les aplicacions en execució"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Permet a una aplicació desplaçar tasques al primer i al segon terme. Les aplicacions malicioses poden aparèixer en primer terme sense que ho controleu."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"activar la depuració d\'aplicacions"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Permet a una aplicació activar la depuració per a una altra aplicació. Les aplicacions malicioses poden utilitzar-ho per destruir altres aplicacions."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"canviar la configuració de la IU"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 0102536..a25640a 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Umožňuje aplikaci načíst informace o aktuálně a nedávno spuštěných úlohách. Toto nastavení může škodlivým aplikacím umožnit odhalení soukromých informací o jiných aplikacích."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"změna uspořádání spuštěných aplikací"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Umožňuje aplikaci přesouvat úlohy do popředí či pozadí. Škodlivé aplikace mohou vynutit své přesunutí do popředí bez vašeho přičinění."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"povolit ladění aplikací"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Umožňuje aplikaci povolit ladění jiné aplikace. Škodlivé aplikace mohou pomocí tohoto nastavení ukončit jiné aplikace."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"změna vašeho nastavení uživatelského rozhraní"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 93b59aa..1017d81 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Tillader, at et program henter oplysninger om nuværende og for nyligt kørende opgaver. Tillader, at eventuelt ondsindede programmer finder private oplysninger om andre programmer."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"omorganiser kørende programmer"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Tillader, at et program flytter opgaver til forgrunden og baggrunden. Ondsindede programmer kan tvinge dem selv til forgrunden uden din kontrol."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"aktiver programfejlretning"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Tillader, at et program slår fejlretning af andet program til. Ondsindede programmer kan bruge dette til at standse andre programmer."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"skift indstillinger for brugergrænsefladen"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index c97eec3..d86fa07 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -161,7 +161,7 @@
     <string name="permgrouplab_personalInfo" msgid="3519163141070533474">"Ihre persönlichen Informationen"</string>
     <string name="permgroupdesc_personalInfo" product="tablet" msgid="6975389054186265786">"Direkter Zugriff auf die Kontakte und den Kalender Ihres Tablets"</string>
     <string name="permgroupdesc_personalInfo" product="default" msgid="5488050357388806068">"Direkter Zugriff auf die Kontakte und den Kalender Ihres Telefons"</string>
-    <string name="permgrouplab_location" msgid="635149742436692049">"Ihren Standort"</string>
+    <string name="permgrouplab_location" msgid="635149742436692049">"Meinen Standort"</string>
     <string name="permgroupdesc_location" msgid="2430258821648348660">"Ihren physischen Standort überwachen"</string>
     <string name="permgrouplab_network" msgid="5808983377727109831">"Netzwerkkommunikation"</string>
     <string name="permgroupdesc_network" msgid="5035763698958415998">"Ermöglicht Anwendungen den Zugriff auf verschiedene Netzwerkfunktionen"</string>
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Ermöglicht der Anwendung, Informationen zu aktuellen und kürzlich ausführten Aufgaben abzurufen. Schädliche Anwendungen können so eventuell geheime Informationen zu anderen Anwendungen entdecken."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"Laufende Anwendungen neu ordnen"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Ermöglicht einer Anwendung, Aufgaben in den Vorder- und Hintergrund zu verschieben. Schädliche Anwendungen können so ohne Ihr Zutun eine Anzeige im Vordergrund erzwingen."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"Fehlerbeseitigung für Anwendung aktivieren"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Ermöglicht einer Anwendung, die Fehlerbeseitigung für eine andere Anwendung zu aktivieren. Schädliche Anwendungen können so andere Anwendungen löschen."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"UI-Einstellungen ändern"</string>
@@ -966,7 +970,7 @@
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec-VPN mit vorinstalliertem Schlüssel"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"L2TP/IPSec-VPN mit Zertifikat"</string>
     <string name="upload_file" msgid="2897957172366730416">"Datei auswählen"</string>
-    <string name="no_file_chosen" msgid="6363648562170759465">"Keine Datei ausgewählt"</string>
+    <string name="no_file_chosen" msgid="6363648562170759465">"Keine ausgewählt"</string>
     <string name="reset" msgid="2448168080964209908">"Zurücksetzen"</string>
     <string name="submit" msgid="1602335572089911941">"Senden"</string>
     <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Automodus aktiviert"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 571dc43..a5d2927 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Επιτρέπει σε μια εφαρμογή να ανακτήσει πληροφορίες σχετικά με τις τρέχουσες εκτελούμενες εργασίες και στις εργασίες που έχουν πρόσφατα εκτελεστεί. Ενδέχεται να δώσει τη δυνατότητα σε κακόβουλες εφαρμογές να ανακαλύψουν ιδιωτικές πληροφορίες σχετικά με άλλες εφαρμογές."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"αναδιάταξη εκτελούμενων εφαρμογών"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Επιτρέπει σε μια εφαρμογή τη μετακίνηση εργασιών στο προσκήνιο και στο φόντο. Κακόβουλες εφαρμογές μπορούν να προωθηθούν στο προσκήνιο χωρίς να μπορείτε να τις ελέγξετε."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"ενεργοποίηση εντοπισμού σφαλμάτων εφαρμογής"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Επιτρέπει σε μια εφαρμογή να ενεργοποιήσει τον εντοπισμό σφαλμάτων για μια άλλη εφαρμογή. Κακόβουλες εφαρμογές μπορούν να το χρησιμοποιήσουν για να τερματίσουν άλλες εφαρμογές."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"αλλαγή των ρυθμίσεων του UI"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 7015d48..f6ed325 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Allows application to retrieve information about currently and recently running tasks. May allow malicious applications to discover private information about other applications."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"reorder applications running"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Allows an application to move tasks to the foreground and background. Malicious applications can force themselves to the front without your control."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"enable application debugging"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Allows an application to turn on debugging for another application. Malicious applications can use this to kill other applications."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"change your UI settings"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 0052b18..820f5e2 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Admite que la aplicación recupere información sobre tareas en ejecución actuales y recientes. Puede permitir que las aplicaciones maliciosas descubran información privada sobre otras aplicaciones."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"reorganizar aplicaciones en ejecución"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Admite una aplicación que mueve tareas hacia el frente y el fondo. Las aplicaciones maliciosas pueden provocar su propio movimiento hacia el frente sin tu control."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"activar la depuración de la aplicación"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Admite una aplicación que activa la depuración en otra aplicación. Las aplicaciones maliciosas pueden utilizarlo para suprimir otras aplicaciones."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"cambiar tu configuración de la interfaz de usuario"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index bdca91f..2e187a5 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Permite que la aplicación recupere información sobre tareas que se están ejecutando en este momento o que se han ejecutado recientemente. Puede permitir que las aplicaciones malintencionadas vean información privada sobre otras aplicaciones."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"reorganizar aplicaciones en ejecución"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Permite que una aplicación mueva tareas a segundo plano y a primer plano. Las aplicaciones malintencionadas pueden aparecer en primer plano sin tu control."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"habilitar depuración de aplicación"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Permite que una aplicación active la depuración de otra aplicación. Las aplicaciones malintencionadas pueden utilizar este permiso para desactivar otras aplicaciones."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"cambiar la configuración de la interfaz de usuario"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 1f3ca7e..726bbab 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"به برنامه کاربردی اجازه می دهد اطلاعات مربوط به کارهای در حال اجرای فعلی و کارهای اخیر را بازیابی کند. ممکن است برنامه های مضر بتوانند اطلاعات خصوصی مربوط به شما را در ارتباط به سایر برنامه ها مشاهده کنند."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"ترتیب بندی مجدد برنامه های در حال اجرا"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"به یک برنامه کاربردی اجازه می دهد تا وظایف را به پیش زمینه و پس زمینه منتقل کند. برنامه های مضر می توانند بدون کنترل شما خودشان را به پیش زمینه منتقل کنند."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"فعال کردن رفع عیب برنامه"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"به یک برنامه کاربردی اجازه می دهد رفع عیب را برای یک برنامه دیگر فعال کند. برنامه های مضر می توانند از این ویژگی برای از بین بردن سایر برنامه ها استفاده کنند."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"تغییر تنظیمات UI"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index c9b6826..1c99fc6 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Antaa sovelluksen noutaa tietoja käynnissä olevista ja äskettäin käynnissä olleista tehtävistä. Haitalliset sovellukset saattavat saada selville yksityisiä tietoja muista sovelluksista."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"muuttaa käynnissä olevien sovelluksien järjestystä"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Antaa sovelluksen siirtää tehtäviä etualalle ja taustalle. Haitalliset sovellukset voivat tunkeutua etualalle väkisin."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"ota sovelluksen vianetsintä käyttöön"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Antaa sovelluksen käynnistää toisen sovelluksen vianetsinnän. Haitalliset sovellukset saattavat sulkea muita sovelluksia."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"muuta käyttöliittymäsi asetuksia"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index b38ae93..ab33b5c 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Permet à l\'application de récupérer des informations sur des tâches en cours d\'exécution ou récemment utilisées. Des applications malveillantes peuvent ainsi obtenir des informations d\'ordre privé concernant d\'autres applications."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"Réorganisation des applications en cours d\'exécution"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Permet à une application de placer des tâches au premier plan ou en arrière-plan. Des applications malveillantes peuvent se placer inopinément au premier plan sans votre autorisation."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"Activation du débogage de l\'application"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Permet à une application d\'activer le mode de débogage d\'une autre application. Des applications malveillantes peuvent utiliser cette fonctionnalité pour interrompre d\'autres applications de façon inopinée."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"Modification des paramètres de l\'IU"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index d8c64f0..6954364 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Aplikaciji omogućuje dohvaćanje podataka o trenutačno ili nedavno pokrenutim zadacima. Zlonamjernim aplikacijama može omogućiti otkrivanje privatnih podataka o drugim aplikacijama."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"promjena redoslijeda pokrenutih aplikacija"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Aplikaciji omogućuje premještanje zadataka u prvi plan ili u pozadinu. Zlonamjerne aplikacije mogu se prisilno postaviti u prednji plan bez vašeg znanja."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"omogućavanje uklanjanja programskih pogrešaka u aplikacijama"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Aplikaciji omogućuje uključivanje značajke uklanjanja programske pogreške za drugu aplikaciju. Zlonamjerne aplikacije to mogu koristiti za uklanjanje drugih aplikacija."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"promjena postavki korisničkog sučelja"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index ea4f81f..55b7cc2 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Lehetővé teszi az alkalmazás számára a jelenleg és a nemrég futó feladatok adatainak lekérését. A rosszindulatú alkalmazások privát adatokhoz juthatnak más alkalmazásokból."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"futó alkalmazások átrendezése"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Lehetővé teszi egy alkalmazás számára, hogy feladatokat helyezzen át az előtérből a háttérbe és fordítva. A rosszindulatú alkalmazások az előtérbe helyezhetik magukat az Ön engedélye nélkül."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"hibakeresés engedélyezése alkalmazásoknál"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Lehetővé teszi egy alkalmazás számára, hogy hibakeresést végezzen egy másik alkalmazáson. A rosszindulatú alkalmazások ezzel leállíthatnak más alkalmazásokat."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"a felhasználói felület beállításainak módosítása"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 4d5352a..50a1aad9 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Mengizinkan aplikasi mengambil informasi tentang tugas yang sedang dan baru saja dijalankan. Aplikasi hasad dapat menemukan informasi bajakan tentang aplikasi lain."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"atur urutan aplikasi yang berjalan"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Mengizinkan aplikasi memindah tugas ke latar depan dan latar belakang. Aplikasi hasad dapat memaksa dirinya ke latar depan tanpa sepengetahuan Anda."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"aktifkan debugging aplikasi"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Mengizinkan aplikasi menghidupkan debug untuk aplikasi lain. Aplikasi hasad dapat menggunakan ini untuk menghentikan aplikasi penting lainnya."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"ubah setelan UI Anda"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 3196dbe..be7234e 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Consente all\'applicazione di recuperare informazioni sulle attività in esecuzione ed eseguite di recente. Le applicazioni dannose potrebbero essere in grado di scoprire informazioni riservate su altre applicazioni."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"riordinamento applicazioni in esecuz."</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Consente a un\'applicazione di spostare attività in primo e secondo piano. Le applicazioni dannose possono imporsi ponendosi automaticamente in primo piano."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"attivazione debug delle applicazioni"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Consente a un\'applicazione di attivare il debug per un\'altra applicazione. Le applicazioni dannose possono sfruttare questa possibilità per interrompere altre applicazioni."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"modifica impostazioni UI"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 07fab9e..e775182 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"מאפשר ליישום לאחזר מידע על משימות הפועלות כעת ושפעלו לאחרונה. עלול לאפשר ליישומים זדוניים לגלות מידע פרטי על יישומים אחרים."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"שנה את סדר היישומים הפועלים"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"מאפשר ליישום להעביר משימות לחזית ולרקע. יישומים זדוניים עלולים לאלץ את עצמם לחזית מבלי שתוכל לשלוט בהם."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"הפוך איתור באגים ביישום לפעיל"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"מאפשר ליישום להפעיל איתור באגים עבור יישום אחר. יישומים זדוניים יכולים להשתמש ביכולת זו כדי להשמיד יישומים אחרים."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"שנה את הגדרות ממשק המשתמש"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index c6a5f95..fb30252 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"現在実行中または最近実行したタスクに関する情報の取得をアプリケーションに許可します。悪意のあるアプリケーションが他のアプリケーションの非公開情報を取得する恐れがあります。"</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"実行中のアプリケーションの順序の変更"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"タスクをフォアグラウンドやバックグラウンドに移動することをアプリケーションに許可します。悪意のあるアプリケーションが優先されて、コントロールできなくなる恐れがあります。"</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"アプリケーションのデバッグを有効にする"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"別のアプリケーションをデバッグモードにすることをアプリケーションに許可します。悪意のあるアプリケーションが別のアプリケーションを終了させる恐れがあります。"</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"UI設定の変更"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 1b7051e..af83195 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"애플리케이션이 현재 실행 중이거나 최근에 실행된 작업에 대한 정보를 검색할 수 있도록 합니다. 이 경우 악성 애플리케이션이 다른 애플리케이션에 대한 개인 정보를 검색할 수 있습니다."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"실행 중인 애플리케이션 순서 재지정"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"애플리케이션이 작업을 포그라운드나 백그라운드로 이동할 수 있도록 합니다. 이 경우 악성 애플리케이션이 사용자의 조작 없이 앞으로 이동할 수 있습니다."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"애플리케이션 디버깅 사용"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"애플리케이션이 다른 애플리케이션에 대해 디버깅을 사용할 수 있도록 합니다. 이 경우 악성 애플리케이션이 다른 애플리케이션을 중지시킬 수 있습니다."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"UI 설정 변경"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 850a6c9..7d89830 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Leidžia programai nuskaityti informaciją apie dabar ir neseniai veikusias užduotis. Gali leisti kenkėjiškoms programoms atrasti privačią informaciją apie kitas programas."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"iš naujo užsakyti veikiančias programas"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Leidžia programai perkelti užduotis į aktyvųjį langą ir foną. Kenkėjiškos programos gali persikelti į priekį jums nieko nedarant."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"įgalinti programos derinimą"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Leidžia programai įjungti kitos programos derinimą. Kenkėjiškos programos tai gali naudoti, kad nutrauktų kitas programas."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"keisti UI nustatymus"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index a4936ac..ccc5fc1 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Ļauj lietojumprogrammai izgūt informāciju par pašlaik un nesen darbinātajiem uzdevumiem. Var atļaut lietojumprogrammām atklāt privātu informāciju par citām lietojumprogrammām."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"pārkārtot aktīvās lietojumprogrammas"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Ļauj lietojumprogrammai pārvietot uzdevumus priekšplānā vai fonā. Ļaunprātīgas lietojumprogrammas var tikt parādītas priekšplānā bez jūsu vadības."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"iespējot lietojumprogrammas atkļūdošanu"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Ļauj lietojumprogrammai ieslēgt citas lietojumprogrammas atkļūdošanu. Ļaunprātīgas lietojumprogrammas var to izmantot, lai pārtrauktu citu lietojumprogrammu darbību."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"mainīt lietotāja saskarnes iestatījumus"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 3b15f88..1c6d939 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Tillater applikasjonen å hente informasjon om aktive og nylig kjørte programmer. Kan tillate ondsinnede applikasjoner å oppdage privat informasjon om andre applikasjoner."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"omordne kjørende applikasjoner"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Tillater applikasjonen å flytte programmer til forgrunnen eller bakgrunnen. Ondsinnede applikasjoner kan tvinge seg selv til fronten."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"aktiver applikasjonsdebugging"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Lar applikasjonen skru på debugging for en annen applikasjon. Ondsinnede applikasjoner kan bruke dette til å drepe andre applikasjoner."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"endre innstillingene for brukergrensesnitt"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 5ca1e4d..3b612fa 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Hiermee kan een app informatie over huidige en recent uitgevoerde taken ophalen. Schadelijke apps kunnen op deze manier mogelijk privé-informatie over andere apps achterhalen."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"actieve toepassingen opnieuw indelen"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Hiermee kan een app taken naar de voor- en achtergrond verplaatsen. Schadelijke apps kunnen zichzelf op de voorgrond plaatsen zonder dat u hier iets aan kunt doen."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"foutopsporing in toepassingen inschakelen"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Hiermee kan een app de foutopsporing voor een andere app inschakelen. Schadelijke apps kunnen dit gebruiken om andere apps af te sluiten."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"uw UI-instellingen wijzigen"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 043faaf..d939ce6 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Umożliwia aplikacji pobieranie informacji na temat obecnie i ostatnio uruchomionych zadań. Może pozwolić szkodliwym aplikacjom na uzyskanie prywatnych informacji na temat innych aplikacji."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"zmienianie porządku uruchomionych aplikacji"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Pozwala aplikacji na przenoszenie zadań z tła na pierwszy plan. Szkodliwe aplikacje mogą wymusić działanie pierwszoplanowe bez kontroli użytkownika."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"włączenie debugowania aplikacji"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Pozwala aplikacji na włączenie debugowania innej aplikacji. Szkodliwe aplikacje mogą to wykorzystać do wyłączenia innych programów."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"zmienianie ustawień interfejsu użytkownika"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 1e2bd22..c49dc45 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Permite à aplicação obter informações sobre tarefas actualmente em execução e recentemente executadas. Pode permitir que aplicações maliciosas descubram informações privadas sobre outras aplicações."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"reordenar aplicações em execução"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Permite a uma aplicação mover tarefas do primeiro e do segundo planos.  Algumas aplicações maliciosas podem impor-se no primeiro plano sem o controlo do utilizador."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"activar depuração da aplicação"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Permite a uma aplicação activar a depuração para outra aplicação. Algumas aplicações maliciosas podem utilizar este item para eliminar outras aplicações."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"alterar definições da IU"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index fd3775a..32e3314 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Permite que o aplicativo recupere as informações sobre tarefas em execução no momento ou recentemente. Pode permitir que aplicativos maliciosos descubram informações particulares sobre outros aplicativos."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"reorganizar aplicativos em execução"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Permite que um aplicativo mova as tarefas para o primeiro e para o segundo planos. Aplicativos maliciosos podem se forçar à frente sem o seu controle."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"ativar depuração do aplicativo"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Permite que um aplicativo ative a depuração de outro aplicativo. Aplicativos maliciosos podem usar isso para encerrar outros aplicativos."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"alterar as suas configurações de UI"</string>
diff --git a/core/res/res/values-rm/strings.xml b/core/res/res/values-rm/strings.xml
index bc1e133..87796f7 100644
--- a/core/res/res/values-rm/strings.xml
+++ b/core/res/res/values-rm/strings.xml
@@ -208,6 +208,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Permetta a l\'applicaziun da recuperar infurmaziuns davart incumbensas che vegnan exequidas u èn gist vegnidas exequidas. Applicaziuns donnegiusas pon uschia obtegnair infurmaziuns privatas concernent autras applicaziuns."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"reorganisar las applicaziuns che vegnan exequidas"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Permetta ad ina applicaziun da spustar las incumbensas en il fund davant u en il fund davos. Applicaziuns donnegiusas pon sa mussar en il fund davant senza Vossa autorisaziun."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"activar il debugging da l\'applicaziun"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Permetta ad ina applicaziun dad activar il debugging per autras applicaziuns. Applicaziuns donnegiusas pon uschia serrar autras applicaziuns."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"modifitgar ils parameters da la UI"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index b388128..e07374e 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Permite aplicaţiei să regăsească informaţii despre activităţile rulate curent şi recent. Poate permite aplicaţiilor rău-intenţionate să descopere informaţii confidenţiale despre alte aplicaţii."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"reordonare aplicaţii aflate în derulare"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Permite unei aplicaţii să mute activităţile în prim-plan şi în fundal. Aplicaţiile rău-intenţionate ar putea să apară forţat în prim-plan, fără ca dvs. să puteţi controla acest lucru."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"activare depanare aplicaţie"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Permite unei aplicaţii să activeze depanarea pentru o altă aplicaţie. Aplicaţiile rău-intenţionate ar putea să utilizeze această permisiune pentru a închide alte aplicaţii."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"modificare setări UI"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 8e8f87f..7227a34 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Позволяет приложению получать сведения о последних и текущих задачах. Вредоносные приложения могут получить доступ к конфиденциальной информации о других приложениях."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"изменять порядок запущенных приложений"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Позволяет приложению переключать режим выполнения задачи с активного на фоновый. Вредоносные приложения могут установить для себя активный режим без уведомления."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"запускать отладку приложения"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Позволяет приложению запускать процесс отладки другого приложения. Вредоносные приложения могут использовать эту возможность для остановки других приложений."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"изменять настройки пользовательского интерфейса"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 8a37fd1..9a58c36 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Umožňuje aplikácii načítať informácie o aktuálne a nedávno spustených úlohách. Toto nastavenie môže škodlivým aplikáciám umožniť odhaliť súkromné informácie o iných aplikáciách."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"zmena usporiadania spustených aplikácií"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Umožňuje aplikácii presúvať úlohy do popredia alebo pozadia. Škodlivé aplikácie môžu vynútiť svoje presunutia do popredia bez vášho pričinenia."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"povoliť ladenie aplikácií"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Umožňuje aplikácii povoliť ladenie inej aplikácie. Škodlivé aplikácie môžu pomocou tohto nastavenia ukončiť iné aplikácie."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"zmeny vašich nastavení používateľského rozhrania"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 6ee4ac0..6c2830b 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Programu dovoljuje pridobivanje informacij o trenutnih in nedavno izvajajočih se opravilih. Zlonamerni programi lahko odkrijejo zasebne podatke o drugih programih."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"preurejanje programov, ki se izvajajo"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Programu dovoljuje premikanje opravil v ospredje in ozadje. Zlonamerni programi se lahko brez vašega nadzora vsilijo v ospredje."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"omogočanje iskanja in odpravljanja napak v programu"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Programu dovoljuje vklop funkcije iskanja in odpravljanja napak za drug program. Zlonamerni programi lahko to uporabijo za zapiranje drugih programov."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"spreminjanje nastavitev UV"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 6faf9da..f67979b 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Омогућава да апликација преузима информације о тренутно и недавно покренутим задацима. На тај начин злонамерне апликације могу да стекну увид у приватне информације о другим апликацијама."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"промена редоследа покретања апликација"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Омогућава да апликација премешта задатке у први план и позадину. Злонамерне апликације могу на тај начин да принудно пређу у први план, при чему ви нећете имати контролу над тим."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"омогућавање отклањања грешака у апликацији"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Омогућава да апликација укључи отклањање грешака за другу апликацију. Злонамерне апликације могу то да злоупотребе и искористе за онемогућавање других апликација."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"промена подешавања корисничког интерфејса"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index fcc1fdd..0413918 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Tillåter att program hämtar information om uppgifter som körs och har körts. Skadliga program kan upptäcka privat information om andra program."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"byt ordning på appar som körs"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Tillåter att ett program flyttar uppgifter till förgrunden eller bakgrunden. Skadliga program kan tvinga sig till förgrunden utan att du kan styra det."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"aktivera felsökning av appar"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Tillåter att ett program aktiverar felsökning för ett annat program. Skadliga program kan använda detta för att avsluta andra program."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"ändra dina gränssnittsinställningar"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index fae3853..aab60b5 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"อนุญาตให้แอปพลิเคชันเรียกดูข้อมูลเกี่ยวกับงานที่ทำเมื่อไม่นานมานี้และที่กำลังทำอยู่ วิธีนี้อาจทำให้แอปพลิเคชันที่เป็นอันตรายพบข้อมูลที่เป็นความลับเกี่ยวกับแอปพลิเคชันอื่นได้"</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"จัดลำดับแอปพลิเคชันที่ทำงานอยู่ใหม่"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"อนุญาตให้แอปพลิเคชันย้ายงานไปที่ด้านหน้าและพื้นหลัง แอปพลิเคชันที่เป็นอันตรายสามารถบังคับตัวเองให้อยู่ด้านหน้าได้โดยไม่ต้องให้คุณควบคุม"</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"เปิดใช้งานการแก้ไขข้อบกพร่องของแอปพลิเคชัน"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"อนุญาตให้แอปพลิเคชันเปิดการแก้ไขข้อบกพร่องสำหรับแอปพลิเคชันอื่น แอปพลิเคชันที่เป็นอันตรายอาจใช้วิธีนี้จบการทำงานแอปพลิเคชันอื่น"</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"เปลี่ยนการตั้งค่า UI ของคุณ"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 5d4a916..10c3438 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Pinapayagan ang application na ibalik ang impormasyon tungkol sa mga kasalukuyan at kamakailang tumatakbong gawain. Maaaring payagan ang mga nakakahamak na application na tuklasin ang pribadong impormasyon tungkol sa ibang mga application."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"muling pagsunud-sunurin ang mga tumatakbong application"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Pinapayagan ang isang application na ilipat ang mga gawain sa foreground at background. Mapupuwersa ng mga nakakahamak na application ang mga sarili nito sa harapan nang wala ng iyong kontrol."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"paganahin ang debugging ng application"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Pinapayagan ang isang application na i-on ang debugging para sa isa pang application. Magagamit ito ng mga nakakahamak na application upang alisin ang ibang mga application."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"baguhin ang iyong mga setting ng UI"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index ad4eee7..20ddef3 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Uygulamaların şu anda ve yakın geçmişte çalışmakta olan işlemler hakkında bilgi almasına izin verir. Kötü amaçlı uygulamaların diğer uygulamalar ile ilgili gizli bilgileri keşfetmesine izin verebilir."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"çalışan uygulamaları yeniden sırala"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Uygulamaların görevleri ön plana ve arka plana taşımasına izin verir. Kötü amaçlı uygulamalar kendilerini sizin denetiminiz dışında zorla ön plana çıkarabilir."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"uygulama hata ayıklamayı etkinleştir"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Bir uygulamanın başka bir uygulama için hata ayıklamayı çalıştırmasına izin verir. Kötü amaçlı uygulamalar bu işlevi başka uygulamaları kapatmak için kullanabilir."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"kullanıcı arayüzü ayarlarınızı değiştirin"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 60cea7b..b723004 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Дозволяє програмі отримувати інформацію про теперішні й останні завдання. Може дозволити шкідливим програмам дізнаватися приватну інформацію про інші програми."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"змін. порядок запущених програм"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Дозволяє програмі робити завдання активними та фоновими. Шкідливі програми можуть примусово ставати активними без контролю з вашого боку."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"увімк. налагодження програми"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Дозволяє програмі вмикати налагодження для іншої програми. Шкідливі програми можуть використовувати це для заверш. роботи ін. програм."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"змін. налашт. інтерф. кор."</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 8070767..f200fdd 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Cho phép ứng dụng truy xuất thông tin về các công việc hiện đang chạy. Có thể cho phép các ứng dụng độc hại phát hiện thông tin riêng tư về các ứng dụng khác."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"sắp xếp lại các ứng dụng đang chạy"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Cho phép ứng dụng di chuyển công việc lên trên nền và dưới nền. Các ứng dụng độc hại có thể tự hiện lên trước mà không cần sự kiểm soát của bạn."</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"cho phép gỡ lỗi ứng dụng"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Cho phép ứng dụng bật gỡ lỗi cho ứng dụng khác. Các ứng dụng độc hại có thể sử dụng quyền này đề loại bỏ các ứng dụng khác."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"thay đổi cài đặt giao diện người dùng của bạn"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index fce5fca..124bd8e 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"允许应用程序检索有关当前和最近运行的任务的信息。恶意应用程序可借此发现有关其他应用程序的保密信息。"</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"对正在运行的应用程序重新排序"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"允许应用程序将任务移至前端和后台。恶意应用程序可借此强行进入前端,而不受您的控制。"</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"启用应用程序调试"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"允许应用程序启动对其他应用程序的调试。恶意应用程序可借此终止其他应用程序。"</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"更改用户界面设置"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index f87c2f6..33825c7 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -204,6 +204,10 @@
     <string name="permdesc_getTasks" msgid="7048711358713443341">"允許應用程式取得最近執行任務的資訊。請注意:惡意程式可能利用此功能找出其他應用程式的隱私資訊。"</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"重新安排執行中的應用程式"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"允許應用程式將工作移至前端或背景作業。請注意:惡意程式可能使用此功能自行把自己拉到前端。"</string>
+    <!-- no translation found for permlab_removeTasks (4802740047161700683) -->
+    <skip />
+    <!-- no translation found for permdesc_removeTasks (2000332928514575461) -->
+    <skip />
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"啟用應用程式偵錯"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"允許應用程式為其他程式開啟偵錯功能。請注意:惡意程式可利用此功能終止其他應用程式。"</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"變更介面設定"</string>
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/AccessPointParserHelper.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/AccessPointParserHelper.java
index 3667c7b..d22356d 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/AccessPointParserHelper.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/AccessPointParserHelper.java
@@ -182,6 +182,7 @@
                 }
                 config.proxySettings = ProxySettings.NONE;
                 networks.add(config);
+                mLinkProperties = null;
             }
         }
 
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestActivity.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestActivity.java
index e138200..adf1883c8 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestActivity.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestActivity.java
@@ -242,10 +242,10 @@
 
         initializeNetworkStates();
 
-        if (mWifiManager.isWifiEnabled()) {
-            log("Clear Wifi before we start the test.");
-            removeConfiguredNetworksAndDisableWifi();
-        }
+        mWifiManager.setWifiEnabled(true);
+        log("Clear Wifi before we start the test.");
+        sleep(SHORT_TIMEOUT);
+        removeConfiguredNetworksAndDisableWifi();
         mWifiRegexs = mCM.getTetherableWifiRegexs();
      }
 
@@ -633,13 +633,13 @@
      * Disconnect from the current AP and remove configured networks.
      */
     public boolean disconnectAP() {
-        if (mWifiManager.isWifiEnabled()) {
-            // remove saved networks
-            List<WifiConfiguration> wifiConfigList = mWifiManager.getConfiguredNetworks();
-            for (WifiConfiguration wifiConfig: wifiConfigList) {
-                log("remove wifi configuration: " + wifiConfig.toString());
-                mWifiManager.forgetNetwork(wifiConfig.networkId);
-            }
+        // remove saved networks
+        List<WifiConfiguration> wifiConfigList = mWifiManager.getConfiguredNetworks();
+        log("size of wifiConfigList: " + wifiConfigList.size());
+        for (WifiConfiguration wifiConfig: wifiConfigList) {
+            log("remove wifi configuration: " + wifiConfig.networkId);
+            int netId = wifiConfig.networkId;
+            mWifiManager.forgetNetwork(netId);
         }
         return true;
     }
@@ -655,20 +655,23 @@
      * Remove configured networks and disable wifi
      */
     public boolean removeConfiguredNetworksAndDisableWifi() {
-            if (!disconnectAP()) {
-                return false;
-            }
-            // Disable Wifi
-            if (!mWifiManager.setWifiEnabled(false)) {
-                return false;
-            }
-            // Wait for the actions to be completed
-            try {
-                Thread.sleep(SHORT_TIMEOUT);
-            } catch (InterruptedException e) {}
+        if (!disconnectAP()) {
+           return false;
+        }
+        sleep(SHORT_TIMEOUT);
+        if (!mWifiManager.setWifiEnabled(false)) {
+            return false;
+        }
+        sleep(SHORT_TIMEOUT);
         return true;
     }
 
+    private void sleep(long sleeptime) {
+        try {
+            Thread.sleep(sleeptime);
+        } catch (InterruptedException e) {}
+    }
+
     /**
      * Set airplane mode
      */
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/WifiConnectionTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/WifiConnectionTest.java
index fe79e6c..22b1759 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/WifiConnectionTest.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/WifiConnectionTest.java
@@ -72,10 +72,8 @@
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        log("before we launch the test activity, we preserve all the configured networks.");
         mRunner = ((ConnectivityManagerTestRunner)getInstrumentation());
         mWifiManager = (WifiManager) mRunner.getContext().getSystemService(Context.WIFI_SERVICE);
-        enabledNetworks = getEnabledNetworks(mWifiManager.getConfiguredNetworks());
 
         mAct = getActivity();
         mWifiManager.asyncConnect(mAct, new WifiServiceHandler());
@@ -123,42 +121,9 @@
     public void tearDown() throws Exception {
         log("tearDown()");
         mAct.removeConfiguredNetworksAndDisableWifi();
-        reEnableNetworks(enabledNetworks);
         super.tearDown();
     }
 
-    private Set<WifiConfiguration> getEnabledNetworks(List<WifiConfiguration> configuredNetworks) {
-        Set<WifiConfiguration> networks = new HashSet<WifiConfiguration>();
-        for (WifiConfiguration wifiConfig : configuredNetworks) {
-            if (wifiConfig.status == Status.ENABLED || wifiConfig.status == Status.CURRENT) {
-                networks.add(wifiConfig);
-                log("remembering enabled network " + wifiConfig.SSID +
-                        " status is " + wifiConfig.status);
-            }
-        }
-        return networks;
-    }
-
-    private void reEnableNetworks(Set<WifiConfiguration> enabledNetworks) {
-        if (!mWifiManager.isWifiEnabled()) {
-            log("reEnableNetworks: enable Wifi");
-            mWifiManager.setWifiEnabled(true);
-            sleep(ConnectivityManagerTestActivity.SHORT_TIMEOUT,
-                    "interruped while waiting for wifi to be enabled");
-        }
-
-        for (WifiConfiguration config : enabledNetworks) {
-            if (DEBUG) {
-                log("recover wifi configuration: " + config.toString());
-            }
-            config.SSID = "\"" + config.SSID + "\"";
-            config.networkId = -1;
-            mWifiManager.connectNetwork(config);
-            sleep(ConnectivityManagerTestActivity.SHORT_TIMEOUT,
-                    "interruped while connecting to " + config.SSID);
-        }
-    }
-
     /**
      * Connect to the provided Wi-Fi network
      * @param config is the network configuration
diff --git a/docs/html/guide/practices/design/jni.jd b/docs/html/guide/practices/design/jni.jd
new file mode 100644
index 0000000..3e9ddc4
--- /dev/null
+++ b/docs/html/guide/practices/design/jni.jd
@@ -0,0 +1,715 @@
+page.title=JNI Tips
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+
+<h2>In this document</h2>
+<ol>
+  <li><a href="#what">What is JNI?</a></li>
+  <li><a href="#JavaVM_and_JNIEnv">JavaVM and JNIEnv</a></li>
+  <li><a href="#threads">Threads</a></li>
+  <li><a href="#jclass_jmethodID_and_jfieldID">jclass, jmethodID, and jfieldID</a></li>
+  <li><a href="#local_and_global_references">Local and Global References</a></li>
+  <li><a href="#UTF_8_and_UTF_16_strings">UTF-8 and UTF-16 Strings</a></li>
+  <li><a href="#arrays">Primitive Arrays</a></li>
+  <li><a href="#region_calls">Region Calls</a></li>
+  <li><a href="#exceptions">Exceptions</a></li>
+  <li><a href="#extended_checking">Extended Checking</a> </li>
+  <li><a href="#native_libraries">Native Libraries</a></li>
+  <li><a href="#64_bit">64-bit Considerations</a></li>
+  <li><a href="#unsupported">Unsupported Features</a></li>
+  <li><a href="#faq_ULE">FAQ: UnsatisfiedLinkError</a></li>
+  <li><a href="#faq_FindClass">FAQ: FindClass didn't find my class</a></li>
+  <li><a href="#faq_sharing">FAQ: Sharing raw data with native code</a></li>
+</ol>
+
+</div>
+</div>
+
+<a name="what_is_jni" id="what_is_jni"></a>
+<h2>What is JNI?</h2>
+
+<p>JNI is the Java Native Interface.  It defines a way for code written in the
+Java programming language to interact with native
+code, e.g. functions written in C/C++.  It's VM-neutral, has support for loading code from
+dynamic shared libraries, and while cumbersome at times is reasonably efficient.</p>
+
+<p>You really should read through the
+<a href="http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html">JNI spec for J2SE 6</a>
+to get a sense for how JNI works and what features are available.  Some
+aspects of the interface aren't immediately obvious on
+first reading, so you may find the next few sections handy.
+The more detailed <i>JNI Programmer's Guide and Specification</i> can be found
+<a href="http://java.sun.com/docs/books/jni/html/jniTOC.html">here</a>.</p>
+
+
+<a name="JavaVM_and_JNIEnv" id="JavaVM_and_JNIEnv"></a>
+<h2>JavaVM and JNIEnv</h2>
+
+<p>JNI defines two key data structures, "JavaVM" and "JNIEnv".  Both of these are essentially
+pointers to pointers to function tables.  (In the C++ version, they're classes with a
+pointer to a function table and a member function for each JNI function that indirects through
+the table.)  The JavaVM provides the "invocation interface" functions,
+which allow you to create and destroy the VM.  In theory you can have multiple VMs per process,
+but Android's VM only allows one.</p>
+
+<p>The JNIEnv provides most of the JNI functions.  Your native functions all receive a JNIEnv as
+the first argument.</p>
+
+<p>On some VMs, the JNIEnv is used for thread-local storage.  For this reason, <strong>you cannot share a JNIEnv between threads</strong>.
+If a piece of code has no other way to get its JNIEnv, you should share
+the JavaVM, and use JavaVM-&gt;GetEnv to discover the thread's JNIEnv. (Assuming it has one; see <code>AttachCurrentThread</code> below.)</p>
+
+<p>The C declarations of JNIEnv and JavaVM are different from the C++
+declarations.  "jni.h" provides different typedefs
+depending on whether it's included into ".c" or ".cpp".  For this reason it's a bad idea to
+include JNIEnv arguments in header files included by both languages.  (Put another way: if your
+header file requires "#ifdef __cplusplus", you may have to do some extra work if anything in
+that header refers to JNIEnv.)</p>
+
+<a name="threads" id="threads"></a>
+<h2>Threads</h2>
+
+<p>All VM threads are Linux threads, scheduled by the kernel.  They're usually
+started using Java language features (notably <code>Thread.start()</code>),
+but they can also be created elsewhere and then attached to the VM.  For
+example, a thread started with <code>pthread_create</code> can be attached
+with the JNI <code>AttachCurrentThread</code> or
+<code>AttachCurrentThreadAsDaemon</code> functions.  Until a thread is
+attached to the VM, it has no JNIEnv, and
+<strong>cannot make JNI calls</strong>.</p>
+
+<p>Attaching a natively-created thread causes the VM to allocate and initialize
+a <code>Thread</code> object, add it to the "main" <code>ThreadGroup</code>,
+and add the thread to the set that is visible to the debugger.  Calling
+<code>AttachCurrentThread</code> on an already-attached thread is a no-op.</p>
+
+<p>The Dalvik VM does not suspend threads executing native code.  If
+garbage collection is in progress, or the debugger has issued a suspend
+request, the VM will pause the thread the next time it makes a JNI call.</p>
+
+<p>Threads attached through JNI <strong>must call
+<code>DetachCurrentThread</code> before they exit</strong>.
+If coding this directly is awkward, in Android &gt;= 2.0 ("Eclair") you
+can use <code>pthread_key_create</code> to define a destructor
+function that will be called before the thread exits, and
+call <code>DetachCurrentThread</code> from there.  (Use that
+key with <code>pthread_setspecific</code> to store the JNIEnv in
+thread-local-storage; that way it'll be passed into your destructor as
+the argument.)</p>
+
+
+<a name="jclass_jmethodID_and_jfieldID" id="jclass_jmethodID_and_jfieldID"></a>
+<h2>jclass, jmethodID, and jfieldID</h2>
+
+<p>If you want to access an object's field from native code, you would do the following:</p>
+
+<ul>
+<li> Get the class object reference for the class with <code>FindClass</code></li>
+<li> Get the field ID for the field with <code>GetFieldID</code></li>
+<li> Get the contents of the field with something appropriate, e.g.
+<code>GetIntField</code></li>
+</ul>
+
+<p>Similarly, to call a method, you'd first get a class object reference and then a method ID.  The IDs are often just
+pointers to internal VM data structures.  Looking them up may require several string
+comparisons, but once you have them the actual call to get the field or invoke the method
+is very quick.</p>
+
+<p>If performance is important, it's useful to look the values up once and cache the results
+in your native code.  Because we are limiting ourselves to one VM per process, it's reasonable
+to store this data in a static local structure.</p>
+
+<p>The class references, field IDs, and method IDs are guaranteed valid until the class is unloaded.  Classes
+are only unloaded if all classes associated with a ClassLoader can be garbage collected,
+which is rare but will not be impossible in our system.  Note however that
+the <code>jclass</code>
+is a class reference and <strong>must be protected</strong> with a call
+to <code>NewGlobalRef</code> (see the next section).</p>
+
+<p>If you would like to cache the IDs when a class is loaded, and automatically re-cache them
+if the class is ever unloaded and reloaded, the correct way to initialize
+the IDs is to add a piece of code that looks like this to the appropriate class:</p>
+
+<pre>    /*
+     * We use a class initializer to allow the native code to cache some
+     * field offsets.
+     */
+
+    /*
+     * A native function that looks up and caches interesting
+     * class/field/method IDs for this class.  Returns false on failure.
+     */
+    native private static boolean nativeClassInit();
+
+    /*
+     * Invoke the native initializer when the class is loaded.
+     */
+    static {
+        if (!nativeClassInit())
+            throw new RuntimeException("native init failed");
+    }</pre>
+
+<p>Create a nativeClassInit method in your C/C++ code that performs the ID lookups.  The code
+will be executed once, when the class is initialized.  If the class is ever unloaded and
+then reloaded, it will be executed again.  (See the implementation of java.io.FileDescriptor
+for an example in our source tree.)</p>
+
+<a name="local_and_global_references" id="local_and_global_references"></a>
+<h2>Local and Global References</h2>
+
+<p>Every object that JNI returns is a "local reference".  This means that it's valid for the
+duration of the current native method in the current thread.
+<strong>Even if the object itself continues to live on after the native method returns, the reference is not valid.</strong>
+This applies to all sub-classes of <code>jobject</code>, including
+<code>jclass</code>, <code>jstring</code>, and <code>jarray</code>.
+(Dalvik VM will warn you about most reference mis-uses when extended JNI
+checks are enabled.)</p>
+
+<p>If you want to hold on to a reference for a longer period, you must use
+a "global" reference.  The <code>NewGlobalRef</code> function takes the
+local reference as an argument and returns a global one.
+The global reference is guaranteed to be valid until you call
+<code>DeleteGlobalRef</code>.</p>
+
+<p>This pattern is commonly used when caching copies of class objects obtained
+from <code>FindClass</code>, e.g.:</p>
+<pre>jclass* localClass = env-&gt;FindClass("MyClass");
+jclass* globalClass = (jclass*) env-&gt;NewGlobalRef(localClass);</pre>
+
+<p>All JNI methods accept both local and global references as arguments.
+It's possible for references to the same object to have different values;
+for example, the return values from consecutive calls to
+<code>NewGlobalRef</code> on the same object may be different.
+<strong>To see if two references refer to the same object,
+you must use the <code>IsSameObject</code> function.</strong>  Never compare
+references with "==" in native code.</p>
+
+<p>One consequence of this is that you
+<strong>must not assume object references are constant or unique</strong>
+in native code.  The 32-bit value representing an object may be different
+from one invocation of a method to the next, and it's possible that two
+different objects could have the same 32-bit value on consecutive calls.  Do
+not use <code>jobject</code> values as keys.</p>
+
+<p>Programmers are required to "not excessively allocate" local references.  In practical terms this means
+that if you're creating large numbers of local references, perhaps while running through an array of
+Objects, you should free them manually with
+<code>DeleteLocalRef</code> instead of letting JNI do it for you.  The
+VM is only required to reserve slots for
+16 local references, so if you need more than that you should either delete as you go or use
+<code>EnsureLocalCapacity</code> to reserve more.</p>
+
+<p>Note: method and field IDs are just 32-bit identifiers, not object
+references, and should not be passed to <code>NewGlobalRef</code>.  The raw data
+pointers returned by functions like <code>GetStringUTFChars</code>
+and <code>GetByteArrayElements</code> are also not objects.</p>
+
+<p>One unusual case deserves separate mention.  If you attach a native
+thread to the VM with AttachCurrentThread, the code you are running will
+never "return" to the VM until the thread detaches from the VM.  Any local
+references you create will have to be deleted manually unless you're going
+to detach the thread soon.</p>
+
+<a name="UTF_8_and_UTF_16_strings" id="UTF_8_and_UTF_16_strings"></a>
+<h2>UTF-8 and UTF-16 Strings</h2>
+
+<p>The Java programming language uses UTF-16.  For convenience, JNI provides methods that work with "modified UTF-8" encoding
+as well.  (Some VMs use the modified UTF-8 internally to store strings; ours do not.)  The
+modified encoding only supports the 8- and 16-bit forms, and stores ASCII NUL values in a 16-bit encoding.
+The nice thing about it is that you can count on having C-style zero-terminated strings,
+suitable for use with standard libc string functions.  The down side is that you cannot pass
+arbitrary UTF-8 data into the VM and expect it to work correctly.</p>
+
+<p>It's usually best to operate with UTF-16 strings.  With our current VMs, the
+<code>GetStringChars</code> method
+does not require a copy, whereas <code>GetStringUTFChars</code> requires a malloc and a UTF conversion.  Note that
+<strong>UTF-16 strings are not zero-terminated</strong>, and \u0000 is allowed,
+so you need to hang on to the string length as well as
+the string pointer.</p>
+
+<p><strong>Don't forget to Release the strings you Get</strong>.  The
+string functions return <code>jchar*</code> or <code>jbyte*</code>, which
+are C-style pointers to primitive data rather than local references.  They
+are guaranteed valid until Release is called, which means they are not
+released when the native method returns.</p>
+
+<p><strong>Data passed to NewStringUTF must be in "modified" UTF-8 format</strong>.  A
+common mistake is reading character data from a file or network stream
+and handing it to <code>NewStringUTF</code> without filtering it.
+Unless you know the data is 7-bit ASCII, you need to strip out high-ASCII
+characters or convert them to proper "modified" UTF-8 form.  If you don't,
+the UTF-16 conversion will likely not be what you expect.  The extended
+JNI checks will scan strings and warn you about invalid data, but they
+won't catch everything.</p>
+
+<a name="arrays" id="arrays"></a>
+<h2>Primitive Arrays</h2>
+
+<p>JNI provides functions for accessing the contents of array objects.
+While arrays of objects must be accessed one entry at a time, arrays of
+primitives can be read and written directly as if they were declared in C.</p>
+
+<p>To make the interface as efficient as possible without constraining
+the VM implementation,
+the <code>Get&lt;PrimitiveType&gt;ArrayElements</code> family of calls
+allows the VM to either return a pointer to the actual elements, or
+allocate some memory and make a copy.  Either way, the raw pointer returned
+is guaranteed to be valid until the corresponding <code>Release</code> call
+is issued (which implies that, if the data wasn't copied, the array object
+will be pinned down and can't be relocated as part of compacting the heap).
+<strong>You must Release every array you Get.</strong>  Also, if the Get
+call fails, you must ensure that your code doesn't try to Release a NULL
+pointer later.</p>
+
+<p>You can determine whether or not the data was copied by passing in a
+non-NULL pointer for the <code>isCopy</code> argument.  This is rarely
+useful.</p>
+
+<p>The <code>Release</code> call takes a <code>mode</code> argument that can
+have one of three values.  The actions performed by the VM depend upon
+whether it returned a pointer to the actual data or a copy of it:</p>
+
+<ul>
+    <li><code>0</code>
+    <ul>
+        <li>Actual: the array object is un-pinned.
+        <li>Copy: data is copied back.  The buffer with the copy is freed.
+    </ul>
+    <li><code>JNI_COMMIT</code>
+    <ul>
+        <li>Actual: does nothing.
+        <li>Copy: data is copied back.  The buffer with the copy
+        <strong>is not freed</strong>.
+    </ul>
+    <li><code>JNI_ABORT</code>
+    <ul>
+        <li>Actual: the array object is un-pinned.  Earlier
+        writes are <strong>not</strong> aborted.
+        <li>Copy: the buffer with the copy is freed; any changes to it are lost.
+    </ul>
+</ul>
+
+<p>One reason for checking the <code>isCopy</code> flag is to know if
+you need to call <code>Release</code> with <code>JNI_COMMIT</code>
+after making changes to an array &mdash; if you're alternating between making
+changes and executing code that uses the contents of the array, you may be
+able to
+skip the no-op commit.  Another possible reason for checking the flag is for
+efficient handling of <code>JNI_ABORT</code>.  For example, you might want
+to get an array, modify it in place, pass pieces to other functions, and
+then discard the changes.  If you know that JNI is making a new copy for
+you, there's no need to create another "editable" copy.  If JNI is passing
+you the original, then you do need to make your own copy.</p>
+
+<p>Some have asserted that you can skip the <code>Release</code> call if
+<code>*isCopy</code> is false.  This is not the case.  If no copy buffer was
+allocated, then the original memory must be pinned down and can't be moved by
+the garbage collector.</p>
+
+<p>Also note that the <code>JNI_COMMIT</code> flag does NOT release the array,
+and you will need to call <code>Release</code> again with a different flag
+eventually.</p>
+
+
+<a name="region_calls" id="region_calls"></a>
+<h2>Region Calls</h2>
+
+<p>There is an alternative to calls like <code>Get&lt;Type&gt;ArrayElements</code>
+and <code>GetStringChars</code> that may be very helpful when all you want
+to do is copy data in or out.  Consider the following:</p>
+
+<pre>
+    jbyte* data = env->GetByteArrayElements(array, NULL);
+    if (data != NULL) {
+        memcpy(buffer, data, len);
+        env->ReleaseByteArrayElements(array, data, JNI_ABORT);
+    }</pre>
+
+<p>This grabs the array, copies the first <code>len</code> byte
+elements out of it, and then releases the array.  Depending upon the VM
+policies the <code>Get</code> call will either pin or copy the array contents.
+We copy the data (for perhaps a second time), then call Release; in this case
+we use <code>JNI_ABORT</code> so there's no chance of a third copy.</p>
+
+<p>We can accomplish the same thing with this:</p>
+<pre>
+    env->GetByteArrayRegion(array, 0, len, buffer);</pre>
+
+<p>This has several advantages:</p>
+<ul>
+    <li>Requires one JNI call instead of 2, reducing overhead.
+    <li>Doesn't require pinning or extra data copies.
+    <li>Reduces the risk of programmer error &mdash; no risk of forgetting
+    to call <code>Release</code> after something fails.
+</ul>
+
+<p>Similarly, you can use the <code>Set&lt;Type&gt;ArrayRegion</code> call
+to copy data into an array, and <code>GetStringRegion</code> or
+<code>GetStringUTFRegion</code> to copy characters out of a
+<code>String</code>.
+
+
+<a name="exceptions" id="exceptions"></a>
+<h2>Exception</h2>
+
+<p><strong>You may not call most JNI functions while an exception is pending.</strong>
+Your code is expected to notice the exception (via the function's return value,
+<code>ExceptionCheck()</code>, or <code>ExceptionOccurred()</code>) and return,
+or clear the exception and handle it.</p>
+
+<p>The only JNI functions that you are allowed to call while an exception is
+pending are:</p>
+<ul>
+    <li>DeleteGlobalRef
+    <li>DeleteLocalRef
+    <li>DeleteWeakGlobalRef
+    <li>ExceptionCheck
+    <li>ExceptionClear
+    <li>ExceptionDescribe
+    <li>ExceptionOccurred
+    <li>MonitorExit
+    <li>PopLocalFrame
+    <li>PushLocalFrame
+    <li>Release&lt;PrimitiveType&gt;ArrayElements
+    <li>ReleasePrimitiveArrayCritical
+    <li>ReleaseStringChars
+    <li>ReleaseStringCritical
+    <li>ReleaseStringUTFChars
+</ul>
+
+<p>Many JNI calls can throw an exception, but often provide a simpler way
+of checking for failure.  For example, if <code>NewString</code> returns
+a non-NULL value, you don't need to check for an exception.  However, if
+you call a method (using a function like <code>CallObjectMethod</code>),
+you must always check for an exception, because the return value is not
+going to be valid if an exception was thrown.</p>
+
+<p>Note that exceptions thrown by interpreted code do not "leap over" native code,
+and C++ exceptions thrown by native code are not handled by Dalvik.
+The JNI <code>Throw</code> and <code>ThrowNew</code> instructions just
+set an exception pointer in the current thread.  Upon returning to the VM from
+native code, the exception will be noted and handled appropriately.</p>
+
+<p>Native code can "catch" an exception by calling <code>ExceptionCheck</code> or
+<code>ExceptionOccurred</code>, and clear it with
+<code>ExceptionClear</code>.  As usual,
+discarding exceptions without handling them can lead to problems.</p>
+
+<p>There are no built-in functions for manipulating the Throwable object
+itself, so if you want to (say) get the exception string you will need to
+find the Throwable class, look up the method ID for
+<code>getMessage "()Ljava/lang/String;"</code>, invoke it, and if the result
+is non-NULL use <code>GetStringUTFChars</code> to get something you can
+hand to printf or a LOG macro.</p>
+
+
+<a name="extended_checking" id="extended_checking"></a>
+<h2>Extended Checking</h2>
+
+<p>JNI does very little error checking.  Calling <code>SetIntField</code>
+on an Object field will succeed, even if the field is marked
+<code>private</code> and <code>final</code>.  The
+goal is to minimize the overhead on the assumption that, if you've written it in native code,
+you probably did it for performance reasons.</p>
+
+<p>In Dalvik, you can enable additional checks by setting the
+"<code>-Xcheck:jni</code>" flag.  If the flag is set, the VM directs
+the JavaVM and JNIEnv pointers to a different table of functions.
+These functions perform an extended series of checks before calling the
+standard implementation.</p>
+
+<p>The additional tests include:</p>
+
+<ul>
+<li> Check for null pointers where not allowed.</li>
+<li> Verify argument type correctness (jclass is a class object,
+jfieldID points to field data, jstring is a java.lang.String).</li>
+<li> Field type correctness, e.g. don't store a HashMap in a String field.</li>
+<li> Ensure jmethodID is appropriate when making a static or virtual
+method call.</li>
+<li> Check to see if an exception is pending on calls where pending exceptions are not legal.</li>
+<li> Check for calls to inappropriate functions between Critical get/release calls.</li>
+<li> Check that JNIEnv structs aren't being shared between threads.</li>
+<li> Make sure local references aren't used outside their allowed lifespan.</li>
+<li> UTF-8 strings contain only valid "modified UTF-8" data.</li>
+</ul>
+
+<p>Accessibility of methods and fields (i.e. public vs. private) is not
+checked.</p>
+
+<p>For a description of how to enable CheckJNI for Android apps, see
+<a href="embedded-vm-control.html">Controlling the Embedded VM</a>.
+It's currently enabled by default in the Android emulator and on
+"engineering" device builds.</p>
+
+<p>JNI checks can be modified with the <code>-Xjniopts</code> command-line
+flag.  Currently supported values include:</p>
+
+<dl>
+<dt>forcecopy
+<dd>When set, any function that can return a copy of the original data
+(array of primitive values, UTF-16 chars) will always do so.  The buffers
+are over-allocated and surrounded with a guard pattern to help identify
+code writing outside the buffer, and the contents are erased before the
+storage is freed to trip up code that uses the data after calling Release.
+This will have a noticeable performance impact on some applications.
+<dt>warnonly
+<dd>By default, JNI "warnings" cause the VM to abort.  With this flag
+it continues on.
+</dl>
+
+
+<a name="native_libraries" id="native_libraries"></a>
+<h2>Native Libraries</h2>
+
+<p>You can load native code from shared libraries with the standard
+<code>System.loadLibrary()</code> call.  The
+preferred way to get at your native code is:</p>
+
+<ul>
+<li> Call <code>System.loadLibrary()</code> from a static class
+initializer.  (See the earlier example, where one is used to call
+<code>nativeClassInit()</code>.)  The argument is the "undecorated"
+library name, e.g. to load "libfubar.so" you would pass in "fubar".</li>
+<li> Provide a native function: <code><strong>jint JNI_OnLoad(JavaVM* vm, void* reserved)</strong></code></li>
+<li>In <code>JNI_OnLoad</code>, register all of your native methods.  You
+should declare
+the methods "static" so the names don't take up space in the symbol table
+on the device.</li>
+</ul>
+
+<p>The <code>JNI_OnLoad</code> function should look something like this if
+written in C:</p>
+<pre>jint JNI_OnLoad(JavaVM* vm, void* reserved)
+{
+    JNIEnv* env;
+    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) != JNI_OK)
+        return -1;
+
+    /* get class with (*env)->FindClass */
+    /* register methods with (*env)->RegisterNatives */
+
+    return JNI_VERSION_1_6;
+}</pre>
+
+<p>You can also call <code>System.load()</code> with the full path name of the
+shared library.  For Android apps, you may find it useful to get the full
+path to the application's private data storage area from the context object.</p>
+
+<p>This is the recommended approach, but not the only approach.  The VM does
+not require explicit registration, nor that you provide a
+<code>JNI_OnLoad</code> function.
+You can instead use "discovery" of native methods that are named in a
+specific way (see <a href="http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/design.html#wp615">
+    the JNI spec</a> for details), though this is less desirable.
+It requires more space in the shared object symbol table,
+loading is slower because it requires string searches through all of the
+loaded shared libraries, and if a method signature is wrong you won't know
+about it until the first time the method is actually used.</p>
+
+<p>One other note about <code>JNI_OnLoad</code>: any <code>FindClass</code>
+calls you make from there will happen in the context of the class loader
+that was used to load the shared library.  Normally <code>FindClass</code>
+uses the loader associated with the method at the top of the interpreted
+stack, or if there isn't one (because the thread was just attached to
+the VM) it uses the "system" class loader.  This makes
+<code>JNI_OnLoad</code> a convenient place to look up and cache class
+object references.</p>
+
+
+<a name="64_bit" id="64_bit"></a>
+<h2>64-bit Considerations</h2>
+
+<p>Android is currently expected to run on 32-bit platforms.  In theory it
+could be built for a 64-bit system, but that is not a goal at this time.
+For the most part this isn't something that you will need to worry about
+when interacting with native code,
+but it becomes significant if you plan to store pointers to native
+structures in integer fields in an object.  To support architectures
+that use 64-bit pointers, <strong>you need to stash your native pointers in a
+<code>long</code> field rather than an <code>int</code></strong>.
+
+
+<a name="unsupported" id="unsupported"></a>
+<h2>Unsupported Features</h2>
+
+<p>All JNI 1.6 features are supported, with the following exceptions:</p>
+<ul>
+    <li><code>DefineClass</code> is not implemented.  Dalvik does not use
+    Java bytecodes or class files, so passing in binary class data
+    doesn't work.  Translation facilities may be added in a future
+    version of the VM.</li>
+    <li>"Weak global" references are implemented, but may only be passed
+    to <code>NewLocalRef</code>, <code>NewGlobalRef</code>, and
+    <code>DeleteWeakGlobalRef</code>.  (The spec strongly encourages
+    programmers to create hard references to weak globals before doing
+    anything with them, so this should not be at all limiting.)</li>
+    <li><code>GetObjectRefType</code> (new in JNI 1.6) is implemented but not fully
+    functional &mdash; it can't always tell the difference between "local" and
+    "global" references.</li>
+</ul>
+
+<p>For backward compatibility, you may need to be aware of:</p>
+<ul>
+    <li>Until Android 2.0 ("Eclair"), the '$' character was not properly
+    converted to "_00024" during searches for method names.  Working
+    around this requires using explicit registration or moving the
+    native methods out of inner classes.
+    <li>Until Android 2.0 ("Eclair"), it was not possible to use a <code>pthread_key_create</code>
+    destructor function to avoid the VM's "thread must be detached before
+    exit" check.  (The VM also uses a pthread key destructor function,
+    so it'd be a race to see which gets called first.)
+    <li>"Weak global" references were not implemented until Android 2.2 ("Froyo").
+    Older VMs will vigorously reject attempts to use them.  You can use
+    the Android platform version constants to test for support.
+</ul>
+
+
+<a name="faq_ULE" id="faq_ULE"></a>
+<h2>FAQ: UnsatisfiedLinkError</h2>
+
+<p>When working on native code it's not uncommon to see a failure like this:</p>
+<pre>java.lang.UnsatisfiedLinkError: Library foo not found</pre>
+
+<p>In some cases it means what it says &mdash; the library wasn't found.  In
+other cases the library exists but couldn't be opened by dlopen(), and
+the details of the failure can be found in the exception's detail message.</p>
+
+<p>Common reasons why you might encounter "library not found" exceptions:</p>
+<ul>
+    <li>The library doesn't exist or isn't accessible to the app.  Use
+    <code>adb shell ls -l &lt;path&gt;</code> to check its presence
+    and permissions.
+    <li>The library wasn't built with the NDK.  This can result in
+    dependencies on functions or libraries that don't exist on the device.
+</ul>
+
+<p>Another class of <code>UnsatisfiedLinkError</code> failures looks like:</p>
+<pre>java.lang.UnsatisfiedLinkError: myfunc
+        at Foo.myfunc(Native Method)
+        at Foo.main(Foo.java:10)</pre>
+
+<p>In logcat, you'll see:</p>
+<pre>W/dalvikvm(  880): No implementation found for native LFoo;.myfunc ()V</pre>
+
+<p>This means that the VM tried to find a matching method but was unsuccessful.
+Some common reasons for this are:</p>
+<ul>
+    <li>The library isn't getting loaded.  Check the logcat output for
+    messages about library loading.
+    <li>The method isn't being found due to a name or signature mismatch.  This
+    is commonly caused by:
+    <ul>
+        <li>For lazy method lookup, failing to declare C++ functions
+        with <code>extern C</code>.  You can use <code>arm-eabi-nm</code>
+        to see the symbols as they appear in the library; if they look
+        mangled (e.g. <code>_Z15Java_Foo_myfuncP7_JNIEnvP7_jclass</code>
+        rather than <code>Java_Foo_myfunc</code>) then you need to
+        adjust the declaration.
+        <li>For explicit registration, minor errors when entering the
+        method signature.  Make sure that what you're passing to the
+        registration call matches the signature in the log file.
+        Remember that 'B' is <code>byte</code> and 'Z' is <code>boolean</code>.
+        Class name components in signatures start with 'L', end with ';',
+        use '/' to separate package/class names, and use '$' to separate
+        inner-class names
+        (e.g. <code>Ljava/util/Map$Entry;</code>).
+    </ul>
+</ul>
+
+<p>Using <code>javah</code> to automatically generate JNI headers may help
+avoid some problems.
+
+
+<a name="faq_FindClass" id="faq_FindClass"></a>
+<h2>FAQ: FindClass didn't find my class</h2>
+
+<p>Make sure that the class name string has the correct format.  JNI class
+names start with the package name and are separated with slashes,
+e.g. <code>java/lang/String</code>.  If you're looking up an array class,
+you need to start with the appropriate number of square brackets and
+must also wrap the class with 'L' and ';', so a one-dimensional array of
+<code>String</code> would be <code>[Ljava/lang/String;</code>.</p>
+
+<p>If the class name looks right, you could be running into a class loader
+issue.  <code>FindClass</code> wants to start the class search in the
+class loader associated with your code.  It examines the VM call stack,
+which will look something like:
+<pre>    Foo.myfunc(Native Method)
+    Foo.main(Foo.java:10)
+    dalvik.system.NativeStart.main(Native Method)</pre>
+
+<p>The topmost method is <code>Foo.myfunc</code>.  <code>FindClass</code>
+finds the <code>ClassLoader</code> object associated with the <code>Foo</code>
+class and uses that.</p>
+
+<p>This usually does what you want.  You can get into trouble if you
+create a thread outside the VM (perhaps by calling <code>pthread_create</code>
+and then attaching it to the VM with <code>AttachCurrentThread</code>).
+Now the stack trace looks like this:</p>
+<pre>    dalvik.system.NativeStart.run(Native Method)</pre>
+
+<p>The topmost method is <code>NativeStart.run</code>, which isn't part of
+your application.  If you call <code>FindClass</code> from this thread, the
+VM will start in the "system" class loader instead of the one associated
+with your application, so attempts to find app-specific classes will fail.</p>
+
+<p>There are a few ways to work around this:</p>
+<ul>
+    <li>Do your <code>FindClass</code> lookups once, in
+    <code>JNI_OnLoad</code>, and cache the class references for later
+    use.  Any <code>FindClass</code> calls made as part of executing
+    <code>JNI_OnLoad</code> will use the class loader associated with
+    the function that called <code>System.loadLibrary</code> (this is a
+    special rule, provided to make library initialization more convenient).
+    If your app code is loading the library, <code>FindClass</code>
+    will use the correct class loader.
+    <li>Pass an instance of the class into the functions that need
+    it, e.g. declare your native method to take a Class argument and
+    then pass <code>Foo.class</code> in.
+    <li>Cache a reference to the <code>ClassLoader</code> object somewhere
+    handy, and issue <code>loadClass</code> calls directly.  This requires
+    some effort.
+</ul>
+
+
+<a name="faq_sharing" id="faq_sharing"></a>
+<h2>FAQ: Sharing raw data with native code</h2>
+
+<p>You may find yourself in a situation where you need to access a large
+buffer of raw data from code written in Java and C/C++.  Common examples
+include manipulation of bitmaps or sound samples.  There are two
+basic approaches.</p>
+
+<p>You can store the data in a <code>byte[]</code>.  This allows very fast
+access from code written in Java.  On the native side, however, you're
+not guaranteed to be able to access the data without having to copy it.  In
+some implementations, <code>GetByteArrayElements</code> and
+<code>GetPrimitiveArrayCritical</code> will return actual pointers to the
+raw data in the managed heap, but in others it will allocate a buffer
+on the native heap and copy the data over.</p>
+
+<p>The alternative is to store the data in a direct byte buffer.  These
+can be created with <code>java.nio.ByteBuffer.allocateDirect</code>, or
+the JNI <code>NewDirectByteBuffer</code> function.  Unlike regular
+byte buffers, the storage is not allocated on the managed heap, and can
+always be accessed directly from native code (get the address
+with <code>GetDirectBufferAddress</code>).  Depending on how direct
+byte buffer access is implemented in the VM, accessing the data from code
+written in Java can be very slow.</p>
+
+<p>The choice of which to use depends on two factors:</p>
+<ol>
+    <li>Will most of the data accesses happen from code written in Java
+    or in C/C++?
+    <li>If the data is eventually being passed to a system API, what form
+    must it be in?  (For example, if the data is eventually passed to a
+    function that takes a byte[], doing processing in a direct
+    <code>ByteBuffer</code> might be unwise.)
+</ol>
+
+<p>If there's no clear winner, use a direct byte buffer.  Support for them
+is built directly into JNI, and access to them from code written in
+Java can be made faster with VM improvements.</p>
diff --git a/docs/html/guide/practices/design/performance.jd b/docs/html/guide/practices/design/performance.jd
index fe69d7d..c41f971 100644
--- a/docs/html/guide/practices/design/performance.jd
+++ b/docs/html/guide/practices/design/performance.jd
@@ -375,6 +375,9 @@
 <p>Native code is primarily useful when you have an existing native codebase
 that you want to port to Android, not for "speeding up" parts of a Java app.</p>
 
+<p>If you do need to use native code, you should read our
+<a href="{@docRoot}guide/practices/design/jni.html">JNI Tips</a>.</p>
+
 <p>(See also <em>Effective Java</em> item 54.)</p>
 
 <a name="closing_notes" id="closing_notes"></a>
diff --git a/docs/html/guide/webapps/targeting.jd b/docs/html/guide/webapps/targeting.jd
index bdc2d5e..46f769c 100644
--- a/docs/html/guide/webapps/targeting.jd
+++ b/docs/html/guide/webapps/targeting.jd
@@ -368,14 +368,14 @@
 }
 
 &#64;media screen and (-webkit-device-pixel-ratio: 1.5) {
-    // CSS for high-density screens
+    /* CSS for high-density screens */
     #header {
         background:url(high-density-image.png);
     }
 }
 
 &#64;media screen and (-webkit-device-pixel-ratio: 0.75) {
-    // CSS for low-density screens
+    /* CSS for low-density screens */
     #header {
         background:url(low-density-image.png);
     }
@@ -426,7 +426,7 @@
 <pre>
 if (window.devicePixelRatio == 1.5) {
   alert("This is a high-density screen");
-} else if (window.devicePixelRation == 0.75) {
+} else if (window.devicePixelRatio == 0.75) {
   alert("This is a low-density screen");
 }
 </pre>
diff --git a/docs/html/sdk/download.jd b/docs/html/sdk/download.jd
index 81b4ff6..44fe5e4 100644
--- a/docs/html/sdk/download.jd
+++ b/docs/html/sdk/download.jd
@@ -1,4 +1,93 @@
-sdk.redirect=true
+page.title=Download an Archived Android SDK
+hide_license_footer=true
 
 @jd:body
 
+<script type="text/javascript">
+  function verify() {
+    document.getElementById('download-button').disabled =
+!document.getElementById('checkbox').checked;
+  }
+  function submit() {
+    var location = window.location.href;
+    if (location.indexOf('?v=') != -1) {
+      var filename = location.substring(location.indexOf('=')+1,location.length);
+      if (document.getElementById('checkbox').checked) {
+        document.location = "http://dl.google.com/android/" + filename;
+      }
+      document.getElementById('click-download').setAttribute("href", "http://dl.google.com/android/"
++ filename);
+      $("#terms-form").hide(500);
+      $("#next-steps").show(500);
+      document.getElementById('checkbox').disabled=true;
+      document.getElementById('download-button').disabled=true;
+    } else {
+      alert("You have not selected an SDK version. Please return to the SDK Archives page");
+    }
+  }
+</script>
+
+<div id="terms-form">
+    <p>Please carefully review the Android SDK License Agreement before downloading the SDK.
+The License Agreement constitutes a contract between you and Google with respect to your use of the
+SDK.</p>
+    <p class="note"><strong>Note:</strong> You must agree to this license agreement in order to
+download one of the archived SDKs, because these SDK packages contain Google software (whereas, the
+<a href="http://developer.android.com/sdk/index.html">current SDK</a> packages do not require a
+license agreement, because they contain only the open sourced SDK tools).</p>
+
+  <iframe id="terms" style="border:1px solid #888;margin:0 0 1em;height:400px;width:95%;"
+src="terms_body.html">
+  </iframe>
+
+  <p>
+    <input type="checkbox" id="checkbox" onclick="verify()" />
+    <label for="checkbox">I agree to the terms of the Android SDK License Agreement.</label>
+  </p>
+  <p>
+    <input type="submit" value="Download" id="download-button" disabled="disabled"
+onclick="submit()" />
+  </p>
+  <p>
+  <script language="javascript">
+    var loc = window.location.href;
+    if (loc.indexOf('?v=') != -1) {
+      var filename = loc.substring(loc.indexOf('=')+1,loc.length);
+      document.write("File: " + filename);
+    }
+  </script>
+  </p>
+</div><!-- end terms-form -->
+
+<noscript>
+  <p><strong>Please enable Javascript in your browser in order to agree to the terms and download
+the SDK.</strong></p>
+</noscript>
+
+<div class="special" id="next-steps" style="display:none">
+  <p>Your download should be underway. If not, <a id="click-download">click here to start the
+download</a>.</p>
+  <p>Beware that you've just downloaded a very old version of the Android SDK, which is not
+recommended. We no longer maintain documentation about how to install these archived SDKs nor
+support the tools contained within.</p>
+  <p>We recommend that you instead download the latest <a
+href="http://developer.android.com/sdk/index.html">Android SDK starter package</a>, which includes
+the latest SDK tools and allows you to develop against any version of the Android platform, back to
+Android 1.1.</p>
+</div>
+
+<script type="text/javascript">
+  var loc = window.location.href;
+  var filename = loc.substring(loc.indexOf('=')+1,loc.length);
+  version = filename.substring(filename.indexOf('.')-1,filename.lastIndexOf('.'));
+  $(".addVersionPath").each(function(i) {
+    var oldHref = $(this).attr("href");
+    $(this).attr({href: "/sdk/" + version + "/" + oldHref});
+  });
+</script>
+
+
+
+
+
+
diff --git a/docs/html/sdk/older_releases.jd b/docs/html/sdk/older_releases.jd
index 77f7e43..870ff04 100644
--- a/docs/html/sdk/older_releases.jd
+++ b/docs/html/sdk/older_releases.jd
@@ -47,7 +47,7 @@
     <td>Windows</td>
     <td>
   <a
-href="http://dl.google.com/android/archives/android-sdk-windows-1.6_r1.zip">android-sdk-
+href="{@docRoot}sdk/download.html?v=archives/android-sdk-windows-1.6_r1.zip">android-sdk-
 windows-1 .6_r1.zip</a>
     </td>
     <td>260529085 bytes</td>
@@ -57,7 +57,7 @@
     <td>Mac OS X (intel)</td>
     <td>
   <a
-href="http://dl.google.com/android/archives/android-sdk-mac_x86-1.6_r1.zip">android-sdk-
+href="{@docRoot}sdk/download.html?v=archives/android-sdk-mac_x86-1.6_r1.zip">android-sdk-
 mac_x86-1 .6_r1.zip</a>
     </td>
     <td>247412515 bytes</td>
@@ -67,7 +67,7 @@
     <td>Linux (i386)</td>
     <td>
   <a
-href="http://dl.google.com/android/archives/android-sdk-linux_x86-1.6_r1.tgz">android-
+href="{@docRoot}sdk/download.html?v=archives/android-sdk-linux_x86-1.6_r1.tgz">android-
 sdk- linux_x86-1.6_r1.tgz</a>
     </td>
     <td>238224860 bytes</td>
@@ -92,7 +92,7 @@
     <td>Windows</td>
     <td>
   <a
-href="http://dl.google.com/android/archives/android-sdk-windows-1.5_r3.zip">android-sdk-
+href="{@docRoot}sdk/download.html?v=archives/android-sdk-windows-1.5_r3.zip">android-sdk-
 windows-1 .5_r3.zip</a>
     </td>
     <td>191477853 bytes</td>
@@ -102,7 +102,7 @@
     <td>Mac OS X (intel)</td>
     <td>
   <a
-href="http://dl.google.com/android/archives/android-sdk-mac_x86-1.5_r3.zip">android-sdk-
+href="{@docRoot}sdk/download.html?v=archives/android-sdk-mac_x86-1.5_r3.zip">android-sdk-
 mac_x86-1 .5_r3.zip</a>
     </td>
     <td>183024673 bytes</td>
@@ -112,7 +112,7 @@
     <td>Linux (i386)</td>
     <td>
   <a
-href="http://dl.google.com/android/archives/android-sdk-linux_x86-1.5_r3.zip">android-
+href="{@docRoot}sdk/download.html?v=archives/android-sdk-linux_x86-1.5_r3.zip">android-
 sdk- linux_x86-1.5_r3.zip</a>
     </td>
     <td>178117561 bytes</td>
@@ -137,7 +137,7 @@
     <td>Windows</td>
     <td>
   <a
-href="http://dl.google.com/android/archives/android-sdk-windows-1.1_r1.zip">android-sdk-
+href="{@docRoot}sdk/download.html?v=archives/android-sdk-windows-1.1_r1.zip">android-sdk-
 windows-1
 .1_r1.zip</a>
     </td>
@@ -148,7 +148,7 @@
     <td>Mac OS X (intel)</td>
     <td>
   <a
-href="http://dl.google.com/android/archives/android-sdk-mac_x86-1.1_r1.zip">android-sdk-
+href="{@docRoot}sdk/download.html?v=archives/android-sdk-mac_x86-1.1_r1.zip">android-sdk-
 mac_x86-1
 .1_r1.zip</a>
     </td>
@@ -159,7 +159,7 @@
     <td>Linux (i386)</td>
     <td>
   <a
-href="http://dl.google.com/android/archives/android-sdk-linux_x86-1.1_r1.zip">android-
+href="{@docRoot}sdk/download.html?v=archives/android-sdk-linux_x86-1.1_r1.zip">android-
 sdk-
 linux_x86-1.1_r1.zip</a>
     </td>
@@ -185,7 +185,7 @@
     <td>Windows</td>
     <td>
   <a
-href="http://dl.google.com/android/archives/android-sdk-windows-1.0_r2.zip">android-sdk-
+href="{@docRoot}sdk/download.html?v=archives/android-sdk-windows-1.0_r2.zip">android-sdk-
 windows-1
 .0_r2.zip</a>
     </td>
@@ -196,7 +196,7 @@
     <td>Mac OS X (intel)</td>
     <td>
   <a
-href="http://dl.google.com/android/archives/android-sdk-mac_x86-1.0_r2.zip">android-sdk-
+href="{@docRoot}sdk/download.html?v=archives/android-sdk-mac_x86-1.0_r2.zip">android-sdk-
 mac_x86-1
 .0_r2.zip</a>
     </td>
@@ -207,7 +207,7 @@
     <td>Linux (i386)</td>
     <td>
   <a
-href="http://dl.google.com/android/archives/android-sdk-linux_x86-1.0_r2.zip">android-
+href="{@docRoot}sdk/download.html?v=archives/android-sdk-linux_x86-1.0_r2.zip">android-
 sdk-
 linux_x86-1.0_r2.zip</a>
     </td>
@@ -241,7 +241,7 @@
     <td>Windows</td>
     <td>
   <a
-href="http://dl.google.com/android/archives/android-sdk-windows-1.5_r2.zip">android-sdk-
+href="{@docRoot}sdk/download.html?v=archives/android-sdk-windows-1.5_r2.zip">android-sdk-
 windows-1 .5_r2.zip</a>
     </td>
     <td>178346828 bytes</td>
@@ -251,7 +251,7 @@
     <td>Mac OS X (intel)</td>
     <td>
   <a
-href="http://dl.google.com/android/archives/android-sdk-mac_x86-1.5_r2.zip">android-sdk-
+href="{@docRoot}sdk/download.html?v=archives/android-sdk-mac_x86-1.5_r2.zip">android-sdk-
 mac_x86-1 .5_r2.zip</a>
     </td>
     <td>169945128 bytes</td>
@@ -261,7 +261,7 @@
     <td>Linux (i386)</td>
     <td>
   <a
-href="http://dl.google.com/android/archives/android-sdk-linux_x86-1.5_r2.zip">android-
+href="{@docRoot}sdk/download.html?v=archives/android-sdk-linux_x86-1.5_r2.zip">android-
 sdk- linux_x86-1.5_r2.zip</a>
     </td>
     <td>165035130 bytes</td>
@@ -286,7 +286,7 @@
     <td>Windows</td>
     <td>
   <a
-href="http://dl.google.com/android/archives/android-sdk-windows-1.5_r1.zip">android-sdk-
+href="{@docRoot}sdk/download.html?v=archives/android-sdk-windows-1.5_r1.zip">android-sdk-
 windows-1 .5_r1.zip</a>
     </td>
     <td>176263368 bytes</td>
@@ -296,7 +296,7 @@
     <td>Mac OS X (intel)</td>
     <td>
   <a
-href="http://dl.google.com/android/archives/android-sdk-mac_x86-1.5_r1.zip">android-sdk-
+href="{@docRoot}sdk/download.html?v=archives/android-sdk-mac_x86-1.5_r1.zip">android-sdk-
 mac_x86-1 .5_r1.zip</a>
     </td>
     <td>167848675 bytes</td>
@@ -306,7 +306,7 @@
     <td>Linux (i386)</td>
     <td>
   <a
-href="http://dl.google.com/android/archives/android-sdk-linux_x86-1.5_r1.zip">android-
+href="{@docRoot}sdk/download.html?v=archives/android-sdk-linux_x86-1.5_r1.zip">android-
 sdk- linux_x86-1.5_r1.zip</a>
     </td>
     <td>162938845 bytes</td>
@@ -331,7 +331,7 @@
     <td>Windows</td>
     <td>
   <a
-href="http://dl.google.com/android/archives/android-sdk-windows-1.0_r1.zip">android-sdk-
+href="{@docRoot}sdk/download.html?v=archives/android-sdk-windows-1.0_r1.zip">android-sdk-
 windows-1 .0_r1.zip</a>
     </td>
     <td>89.7 MB bytes</td>
@@ -341,7 +341,7 @@
     <td>Mac OS X (intel)</td>
     <td>
   <a
-href="http://dl.google.com/android/archives/android-sdk-mac_x86-1.0_r1.zip">android-sdk-
+href="{@docRoot}sdk/download.html?v=archives/android-sdk-mac_x86-1.0_r1.zip">android-sdk-
 mac_x86-1 .0_r1.zip</a>
     </td>
     <td>87.5 MB bytes</td>
@@ -351,7 +351,7 @@
     <td>Linux (i386)</td>
     <td>
   <a
-href="http://dl.google.com/android/archives/android-sdk-linux_x86-1.0_r1.zip">android-
+href="{@docRoot}sdk/download.html?v=archives/android-sdk-linux_x86-1.0_r1.zip">android-
 sdk- linux_x86-1.0_r1.zip</a>
     </td>
     <td>87.8 MB bytes</td>
diff --git a/docs/html/sdk/terms_body.html b/docs/html/sdk/terms_body.html
new file mode 100644
index 0000000..8c55b37
--- /dev/null
+++ b/docs/html/sdk/terms_body.html
@@ -0,0 +1,336 @@
+
+ 
+<p>This is the Android Software Development Kit License Agreement.</p> 
+ 
+<h2> 
+	1. Introduction
+</h2> 
+<p> 
+	1.1 The Android Software Development Kit (referred to in this License Agreement as the "SDK"
+and specifically including the Android system files, packaged APIs, and Google APIs add-ons) is
+licensed to you subject to the terms of this License Agreement. This License Agreement forms a
+legally binding contract between you and Google in relation to your use of the SDK.
+ 
+</p> 
+<p> 
+	1.2 "Google" means Google Inc., a Delaware corporation with principal place of business at
+1600 Amphitheatre Parkway, Mountain View, CA 94043, United States.
+</p> 
+<h2> 
+	2. Accepting this License Agreement
+</h2> 
+<p> 
+	2.1 In order to use the SDK, you must first agree to this License Agreement. You may not use
+the SDK if you do not accept this License Agreement.
+</p> 
+<p> 
+	2.2 You can accept this License Agreement by:
+</p> 
+<p> 
+	(A) clicking to accept or agree to this License Agreement, where this option is made
+available to you; or
+</p> 
+<p> 
+	(B) by actually using the SDK. In this case, you agree that use of the SDK constitutes
+acceptance of the Licensing Agreement from that point onwards.
+</p> 
+<p> 
+	2.3 You may not use the SDK and may not accept the Licensing Agreement if you are a person
+barred from receiving the SDK under the laws of the United States or other countries including the
+country in which you are resident or from which you use the SDK.
+</p> 
+<p> 
+	2.4 If you are agreeing to be bound by this License Agreement on behalf of your employer or
+other entity, you represent and warrant that you have full legal authority to bind your employer or
+such entity to this License Agreement. If you do not have the requisite authority, you may not
+accept the Licensing Agreement or use the SDK on behalf of your employer or other entity.
+</p> 
+<h2> 
+	3. SDK License from Google
+</h2> 
+<p> 
+	3.1 Subject to the terms of this License Agreement, Google grants you a limited, worldwide,
+royalty-free, non- assignable and non-exclusive license to use the SDK solely to develop
+applications to run on the Android platform.
+</p> 
+<p> 
+	3.2 You agree that Google or third parties own all legal right, title and interest in and to
+the SDK, including any Intellectual Property Rights that subsist in the SDK. "Intellectual Property
+Rights" means any and all rights under patent law, copyright law, trade secret law, trademark law,
+and any and all other proprietary rights. Google reserves all rights not expressly granted to you. 
+ 
+</p> 
+<p> 
+	3.3 Except to the extent required by applicable third party licenses, you may not copy
+(except for backup purposes), modify, adapt, redistribute, decompile, reverse engineer, disassemble,
+or create derivative works of the SDK or any part of the SDK. Except to the extent required by
+applicable third party licenses, you may not load any part of the SDK onto a mobile handset or any
+other hardware device except a personal computer, combine any part of the SDK with other software,
+or distribute any software or device incorporating a part of the SDK. 
+</p> 
+<p> 
+	3.4 Use, reproduction and distribution of components of the SDK licensed under an open
+source software license are governed solely by the terms of that open source software license and
+not this License Agreement.
+</p> 
+<p> 
+	3.5 You agree that the form and nature of the SDK that Google provides may change without
+prior notice to you and that future versions of the SDK may be incompatible with applications
+developed on previous versions of the SDK. You agree that Google may stop (permanently or
+temporarily) providing the SDK (or any features within the SDK) to you or to users generally at
+Google's sole discretion, without prior notice to you.
+</p> 
+<p> 
+	3.6 Nothing in this License Agreement gives you a right to use any of Google's trade names,
+trademarks, service marks, logos, domain names, or other distinctive brand features.
+</p> 
+<p> 
+	3.7 You agree that you will not remove, obscure, or alter any proprietary rights notices
+(including copyright and trademark notices) that may be affixed to or contained within the SDK.
+</p> 
+<h2> 
+	4. Use of the SDK by You
+</h2> 
+<p> 
+	4.1 Google agrees that it obtains no right, title or interest from you (or your licensors)
+under this License Agreement in or to any software applications that you develop using the SDK,
+including any intellectual property rights that subsist in those applications. 
+</p> 
+<p> 
+	4.2 You agree to use the SDK and write applications only for purposes that are permitted by
+(a) this License Agreement and (b) any applicable law, regulation or generally accepted practices or
+guidelines in the relevant jurisdictions (including any laws regarding the export of data or
+software to and from the United States or other relevant countries).
+</p> 
+<p> 
+	4.3 You agree that if you use the SDK to develop applications for general public users, you
+will protect the privacy and legal rights of those users. If the users provide you with user names,
+passwords, or other login information or personal information, your must make the users aware that
+the information will be available to your application, and you must provide legally adequate privacy
+notice and protection for those users. If your application stores personal or sensitive information
+provided by users, it must do so securely. If the user provides your application with Google Account
+information, your application may only use that information to access the user's Google Account
+when, and for the limited purposes for which, the user has given you permission to do so.
+</p> 
+<p> 
+	4.4 You agree that you will not engage in any activity with the SDK, including the
+development or distribution of an application, that interferes with, disrupts, damages, or accesses
+in an unauthorized manner the servers, networks, or other properties or services of any third party
+including, but not limited to, Google or any mobile communications carrier.
+</p> 
+<p> 
+	4.5 You agree that you are solely responsible for (and that Google has no responsibility to
+you or to any third party for) any data, content, or resources that you create, transmit or display
+through the Android platform and/or applications for the Android platform, and for the consequences
+of your actions (including any loss or damage which Google may suffer) by doing so.
+</p> 
+<p> 
+	4.6 You agree that you are solely responsible for (and that Google has no responsibility to
+you or to any third party for) any breach of your obligations under this License Agreement, any
+applicable third party contract or Terms of Service, or any applicable law or regulation, and for
+the consequences (including any loss or damage which Google or any third party may suffer) of any
+such breach.
+</p> 
+<h2> 
+	5. Your Developer Credentials
+</h2> 
+<p> 
+	5.1 You agree that you are responsible for maintaining the confidentiality of any developer
+credentials that may be issued to you by Google or which you may choose yourself and that you will
+be solely responsible for all applications that are developed under your developer credentials.
+</p> 
+<h2> 
+	6. Privacy and Information
+</h2> 
+<p> 
+	6.1 In order to continually innovate and improve the SDK, Google may collect certain usage
+statistics from the software including but not limited to a unique identifier, associated IP
+address, version number of the software, and information on which tools and/or services in the SDK
+are being used and how they are being used. Before any of this information is collected, the SDK
+will notify you and seek your consent. If you withhold consent, the information will not be
+collected.
+</p> 
+<p> 
+	6.2 The data collected is examined in the aggregate to improve the SDK and is maintained in
+accordance with Google's Privacy Policy.
+</p> 
+<h2> 
+	7. Third Party Applications for the Android Platform
+</h2> 
+<p> 
+	7.1 If you use the SDK to run applications developed by a third party or that access data,
+content or resources provided by a third party, you agree that Google is not responsible for those
+applications, data, content, or resources. You understand that all data, content or resources which
+you may access through such third party applications are the sole responsibility of the person from
+which they originated and that Google is not liable for any loss or damage that you may experience
+as a result of the use or access of any of those third party applications, data, content, or
+resources.
+</p> 
+<p> 
+	7.2 You should be aware the data, content, and resources presented to you through such a
+third party application may be protected by intellectual property rights which are owned by the
+providers (or by other persons or companies on their behalf). You may not modify, rent, lease, loan,
+sell, distribute or create derivative works based on these data, content, or resources (either in
+whole or in part) unless you have been specifically given permission to do so by the relevant
+owners.
+</p> 
+<p> 
+	7.3 You acknowledge that your use of such third party applications, data, content, or
+resources may be subject to separate terms between you and the relevant third party. In that case,
+this License Agreement does not affect your legal relationship with these third parties.
+</p> 
+<h2> 
+	8. Using Android APIs
+</h2> 
+<p> 
+	8.1 Google Data APIs
+</p> 
+<p> 
+	8.1.1 If you use any API to retrieve data from Google, you acknowledge that the data may be
+protected by intellectual property rights which are owned by Google or those parties that provide
+the data (or by other persons or companies on their behalf). Your use of any such API may be subject
+to additional Terms of Service. You may not modify, rent, lease, loan, sell, distribute or create
+derivative works based on this data (either in whole or in part) unless allowed by the relevant
+Terms of Service.
+</p> 
+<p> 
+	8.1.2 If you use any API to retrieve a user's data from Google, you acknowledge and agree
+that you shall retrieve data only with the user's explicit consent and only when, and for the
+limited purposes for which, the user has given you permission to do so. 
+ 
+</p> 
+<h2> 
+	9. Terminating this License Agreement
+</h2> 
+<p> 
+	9.1 This License Agreement will continue to apply until terminated by either you or Google
+as set out below.
+</p> 
+<p> 
+	9.2 If you want to terminate this License Agreement, you may do so by ceasing your use of
+the SDK and any relevant developer credentials.
+</p> 
+<p> 
+	9.3 Google may at any time, terminate this License Agreement with you if:
+</p> 
+<p> 
+	(A) you have breached any provision of this License Agreement; or
+</p> 
+<p> 
+	(B) Google is required to do so by law; or
+</p> 
+<p> 
+	(C) the partner with whom Google offered certain parts of SDK (such as APIs) to you has
+terminated its relationship with Google or ceased to offer certain parts of the SDK to you; or
+</p> 
+<p> 
+	(D) Google decides to no longer providing the SDK or certain parts of the SDK to users in
+the country in which you are resident or from which you use the service, or the provision of the SDK
+or certain SDK services to you by Google is, in Google's sole discretion, no longer commercially
+viable.
+</p> 
+<p> 
+	9.4 When this License Agreement comes to an end, all of the legal rights, obligations and
+liabilities that you and Google have benefited from, been subject to (or which have accrued over
+time whilst this License Agreement has been in force) or which are expressed to continue
+indefinitely, shall be unaffected by this cessation, and the provisions of paragraph 14.7 shall
+continue to apply to such rights, obligations and liabilities indefinitely.
+</p> 
+<h2> 
+	10. DISCLAIMER OF WARRANTIES
+</h2> 
+<p> 
+	10.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT YOUR USE OF THE SDK IS AT YOUR SOLE RISK AND
+THAT THE SDK IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTY OF ANY KIND FROM GOOGLE.
+</p> 
+<p> 
+	10.2 YOUR USE OF THE SDK AND ANY MATERIAL DOWNLOADED OR OTHERWISE OBTAINED THROUGH THE USE
+OF THE SDK IS AT YOUR OWN DISCRETION AND RISK AND YOU ARE SOLELY RESPONSIBLE FOR ANY DAMAGE TO YOUR
+COMPUTER SYSTEM OR OTHER DEVICE OR LOSS OF DATA THAT RESULTS FROM SUCH USE.
+</p> 
+<p> 
+	10.3 GOOGLE FURTHER EXPRESSLY DISCLAIMS ALL WARRANTIES AND CONDITIONS OF ANY KIND, WHETHER
+EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES AND CONDITIONS OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
+</p> 
+<h2> 
+	11. LIMITATION OF LIABILITY
+</h2> 
+<p> 
+	11.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT GOOGLE, ITS SUBSIDIARIES AND AFFILIATES, AND
+ITS LICENSORS SHALL NOT BE LIABLE TO YOU UNDER ANY THEORY OF LIABILITY FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL CONSEQUENTIAL OR EXEMPLARY DAMAGES THAT MAY BE INCURRED BY YOU, INCLUDING ANY
+LOSS OF DATA, WHETHER OR NOT GOOGLE OR ITS REPRESENTATIVES HAVE BEEN ADVISED OF OR SHOULD HAVE BEEN
+AWARE OF THE POSSIBILITY OF ANY SUCH LOSSES ARISING.
+</p> 
+<h2> 
+	12. Indemnification
+</h2> 
+<p> 
+	12.1 To the maximum extent permitted by law, you agree to defend, indemnify and hold
+harmless Google, its affiliates and their respective directors, officers, employees and agents from
+and against any and all claims, actions, suits or proceedings, as well as any and all losses,
+liabilities, damages, costs and expenses (including reasonable attorneys fees) arising out of or
+accruing from (a) your use of the SDK, (b) any application you develop on the SDK that infringes any
+copyright, trademark, trade secret, trade dress, patent or other intellectual property right of any
+person or defames any person or violates their rights of publicity or privacy, and (c) any
+non-compliance by you with this License Agreement.
+</p> 
+<h2> 
+	13. Changes to the License Agreement
+</h2> 
+<p> 
+	13.1 Google may make changes to the License Agreement as it distributes new versions of the
+SDK. When these changes are made, Google will make a new version of the License Agreement available
+on the website where the SDK is made available.
+</p> 
+<h2> 
+	14. General Legal Terms
+</h2> 
+<p> 
+	14.1 This License Agreement constitute the whole legal agreement between you and Google and
+govern your use of the SDK (excluding any services which Google may provide to you under a separate
+written agreement), and completely replace any prior agreements between you and Google in relation
+to the SDK.
+</p> 
+<p> 
+	14.2 You agree that if Google does not exercise or enforce any legal right or remedy which
+is contained in this License Agreement (or which Google has the benefit of under any applicable
+law), this will not be taken to be a formal waiver of Google's rights and that those rights or
+remedies will still be available to Google.
+</p> 
+<p> 
+	14.3 If any court of law, having the jurisdiction to decide on this matter, rules that any
+provision of this License Agreement is invalid, then that provision will be removed from this
+License Agreement without affecting the rest of this License Agreement. The remaining provisions of
+this License Agreement will continue to be valid and enforceable.
+</p> 
+<p> 
+	14.4 You acknowledge and agree that each member of the group of companies of which Google is
+the parent shall be third party beneficiaries to this License Agreement and that such other
+companies shall be entitled to directly enforce, and rely upon, any provision of this License
+Agreement that confers a benefit on (or rights in favor of) them. Other than this, no other person
+or company shall be third party beneficiaries to this License Agreement.
+</p> 
+<p> 
+	14.5 EXPORT RESTRICTIONS. THE SDK IS SUBJECT TO UNITED STATES EXPORT LAWS AND REGULATIONS.
+YOU MUST COMPLY WITH ALL DOMESTIC AND INTERNATIONAL EXPORT LAWS AND REGULATIONS THAT APPLY TO THE
+SDK. THESE LAWS INCLUDE RESTRICTIONS ON DESTINATIONS, END USERS AND END USE.
+</p> 
+<p> 
+	14.6 The rights granted in this License Agreement may not be assigned or transferred by
+either you or Google without the prior written approval of the other party. Neither you nor Google
+shall be permitted to delegate their responsibilities or obligations under this License Agreement
+without the prior written approval of the other party.
+</p> 
+<p> 
+	14.7 This License Agreement, and your relationship with Google under this License Agreement,
+shall be governed by the laws of the State of California without regard to its conflict of laws
+provisions. You and Google agree to submit to the exclusive jurisdiction of the courts located
+within the county of Santa Clara, California to resolve any legal matter arising from this License
+Agreement. Notwithstanding this, you agree that Google shall still be allowed to apply for
+injunctive remedies (or an equivalent type of urgent legal relief) in any jurisdiction.
+</p> 
+<p> 
+	<em>April 10, 2009</em> 
+</p> 
\ No newline at end of file
diff --git a/include/android_runtime/AndroidRuntime.h b/include/android_runtime/AndroidRuntime.h
index de2d50b..b02a057 100644
--- a/include/android_runtime/AndroidRuntime.h
+++ b/include/android_runtime/AndroidRuntime.h
@@ -46,14 +46,10 @@
         const char* className, const JNINativeMethod* gMethods, int numMethods);
 
     /**
-     * Call a static Java function that takes no arguments and returns void.
-     */
-    status_t callStatic(const char* className, const char* methodName);
-
-    /**
      * Call a class's static main method with the given arguments,
      */
-    status_t callMain(const char* className, int argc, const char* const argv[]);
+    status_t callMain(const char* className, jclass clazz, int argc,
+        const char* const argv[]);
 
     /**
      * Find a class, with the input either of the form
@@ -69,6 +65,13 @@
     static AndroidRuntime* getRuntime();
 
     /**
+     * This gets called after the VM has been created, but before we
+     * run any code. Override it to make any FindClass calls that need
+     * to use CLASSPATH.
+     */
+    virtual void onVmCreated(JNIEnv* env);
+
+    /**
      * This gets called after the JavaVM has initialized.  Override it
      * with the system's native entry point.
      */
@@ -98,6 +101,9 @@
     /** return a pointer to the JNIEnv pointer for this thread */
     static JNIEnv* getJNIEnv();
 
+    /** return a new string corresponding to 'className' with all '.'s replaced by '/'s. */
+    static char* toSlashClassName(const char* className);
+
 private:
     static int startReg(JNIEnv* env);
     void parseExtraOpts(char* extraOptsBuf);
diff --git a/include/gui/SurfaceTexture.h b/include/gui/SurfaceTexture.h
index 585d288..340daaf 100644
--- a/include/gui/SurfaceTexture.h
+++ b/include/gui/SurfaceTexture.h
@@ -248,12 +248,6 @@
     // allocate new GraphicBuffer objects.
     sp<IGraphicBufferAlloc> mGraphicBufferAlloc;
 
-    // mAllocdBuffers is mirror of the list of buffers that SurfaceFlinger is
-    // referencing. This is kept so that gralloc implementations do not need to
-    // properly handle the case where SurfaceFlinger no longer holds a reference
-    // to a buffer, but other processes do.
-    Vector<sp<GraphicBuffer> > mAllocdBuffers;
-
     // mFrameAvailableListener is the listener object that will be called when a
     // new frame becomes available. If it is not NULL it will be called from
     // queueBuffer.
diff --git a/include/media/IMediaPlayerClient.h b/include/media/IMediaPlayerClient.h
index eee6c97..daec1c7 100644
--- a/include/media/IMediaPlayerClient.h
+++ b/include/media/IMediaPlayerClient.h
@@ -28,7 +28,7 @@
 public:
     DECLARE_META_INTERFACE(MediaPlayerClient);
 
-    virtual void notify(int msg, int ext1, int ext2) = 0;
+    virtual void notify(int msg, int ext1, int ext2, const Parcel *obj) = 0;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/include/media/MediaPlayerInterface.h b/include/media/MediaPlayerInterface.h
index 447942b..e1b6dd6 100644
--- a/include/media/MediaPlayerInterface.h
+++ b/include/media/MediaPlayerInterface.h
@@ -55,7 +55,8 @@
 
 
 // callback mechanism for passing messages to MediaPlayer object
-typedef void (*notify_callback_f)(void* cookie, int msg, int ext1, int ext2);
+typedef void (*notify_callback_f)(void* cookie,
+        int msg, int ext1, int ext2, const Parcel *obj);
 
 // abstract base class - use MediaPlayerInterface
 class MediaPlayerBase : public RefBase
@@ -159,9 +160,10 @@
         mCookie = cookie; mNotify = notifyFunc;
     }
 
-    void        sendEvent(int msg, int ext1=0, int ext2=0) {
+    void        sendEvent(int msg, int ext1=0, int ext2=0,
+                          const Parcel *obj=NULL) {
         Mutex::Autolock autoLock(mNotifyLock);
-        if (mNotify) mNotify(mCookie, msg, ext1, ext2);
+        if (mNotify) mNotify(mCookie, msg, ext1, ext2, obj);
     }
 
 private:
diff --git a/include/media/mediaplayer.h b/include/media/mediaplayer.h
index 528eeb9..748e489 100644
--- a/include/media/mediaplayer.h
+++ b/include/media/mediaplayer.h
@@ -37,6 +37,7 @@
     MEDIA_BUFFERING_UPDATE  = 3,
     MEDIA_SEEK_COMPLETE     = 4,
     MEDIA_SET_VIDEO_SIZE    = 5,
+    MEDIA_TIMED_TEXT        = 99,
     MEDIA_ERROR             = 100,
     MEDIA_INFO              = 200,
 };
@@ -129,7 +130,7 @@
 class MediaPlayerListener: virtual public RefBase
 {
 public:
-    virtual void notify(int msg, int ext1, int ext2) = 0;
+    virtual void notify(int msg, int ext1, int ext2, const Parcel *obj) = 0;
 };
 
 class MediaPlayer : public BnMediaPlayerClient,
@@ -166,7 +167,7 @@
             status_t        setLooping(int loop);
             bool            isLooping();
             status_t        setVolume(float leftVolume, float rightVolume);
-            void            notify(int msg, int ext1, int ext2);
+            void            notify(int msg, int ext1, int ext2, const Parcel *obj = NULL);
     static  sp<IMemory>     decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, int* pFormat);
     static  sp<IMemory>     decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, int* pFormat);
             status_t        invoke(const Parcel& request, Parcel *reply);
diff --git a/include/surfaceflinger/IGraphicBufferAlloc.h b/include/surfaceflinger/IGraphicBufferAlloc.h
index d996af7..01e4bd9 100644
--- a/include/surfaceflinger/IGraphicBufferAlloc.h
+++ b/include/surfaceflinger/IGraphicBufferAlloc.h
@@ -32,18 +32,10 @@
 public:
     DECLARE_META_INTERFACE(GraphicBufferAlloc);
 
-    /* Create a new GraphicBuffer for the client to use.  The server will
-     * maintain a reference to the newly created GraphicBuffer until
-     * freeAllGraphicBuffers is called.
+    /* Create a new GraphicBuffer for the client to use.
      */
     virtual sp<GraphicBuffer> createGraphicBuffer(uint32_t w, uint32_t h,
             PixelFormat format, uint32_t usage) = 0;
-
-    /* Free all but one of the GraphicBuffer objects that the server is
-     * currently referencing. If bufIndex is not a valid index of the buffers
-     * the server is referencing, then all buffers are freed.
-     */
-    virtual void freeAllGraphicBuffersExcept(int bufIndex) = 0;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/include/tts/TtsEngine.h b/include/tts/TtsEngine.h
deleted file mode 100644
index 998e353..0000000
--- a/include/tts/TtsEngine.h
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright (C) 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include <media/AudioSystem.h>
-
-// This header defines the interface used by the Android platform
-// to access Text-To-Speech functionality in shared libraries that implement
-// speech synthesis and the management of resources associated with the
-// synthesis.
-// An example of the implementation of this interface can be found in
-// FIXME: add path+name to implementation of default TTS engine
-// Libraries implementing this interface are used in:
-//  frameworks/base/tts/jni/android_tts_SpeechSynthesis.cpp
-
-namespace android {
-
-#define ANDROID_TTS_ENGINE_PROPERTY_CONFIG "engineConfig"
-#define ANDROID_TTS_ENGINE_PROPERTY_PITCH  "pitch"
-#define ANDROID_TTS_ENGINE_PROPERTY_RATE   "rate"
-#define ANDROID_TTS_ENGINE_PROPERTY_VOLUME "volume"
-
-
-enum tts_synth_status {
-    TTS_SYNTH_DONE              = 0,
-    TTS_SYNTH_PENDING           = 1
-};
-
-enum tts_callback_status {
-    TTS_CALLBACK_HALT           = 0,
-    TTS_CALLBACK_CONTINUE       = 1
-};
-
-// The callback is used by the implementation of this interface to notify its
-// client, the Android TTS service, that the last requested synthesis has been
-// completed. // TODO reword
-// The callback for synthesis completed takes:
-// @param [inout] void *&       - The userdata pointer set in the original
-//                                 synth call
-// @param [in]    uint32_t      - Track sampling rate in Hz
-// @param [in]    uint32_t      - The audio format
-// @param [in]    int           - The number of channels
-// @param [inout] int8_t *&     - A buffer of audio data only valid during the
-//                                execution of the callback
-// @param [inout] size_t  &     - The size of the buffer
-// @param [in] tts_synth_status - indicate whether the synthesis is done, or
-//                                 if more data is to be synthesized.
-// @return TTS_CALLBACK_HALT to indicate the synthesis must stop,
-//         TTS_CALLBACK_CONTINUE to indicate the synthesis must continue if
-//            there is more data to produce.
-typedef tts_callback_status (synthDoneCB_t)(void *&, uint32_t,
-        uint32_t, int, int8_t *&, size_t&, tts_synth_status);
-
-class TtsEngine;
-extern "C" TtsEngine* getTtsEngine();
-
-enum tts_result {
-    TTS_SUCCESS                 = 0,
-    TTS_FAILURE                 = -1,
-    TTS_FEATURE_UNSUPPORTED     = -2,
-    TTS_VALUE_INVALID           = -3,
-    TTS_PROPERTY_UNSUPPORTED    = -4,
-    TTS_PROPERTY_SIZE_TOO_SMALL = -5,
-    TTS_MISSING_RESOURCES       = -6
-};
-
-enum tts_support_result {
-    TTS_LANG_COUNTRY_VAR_AVAILABLE = 2,
-    TTS_LANG_COUNTRY_AVAILABLE = 1,
-    TTS_LANG_AVAILABLE = 0,
-    TTS_LANG_MISSING_DATA = -1,
-    TTS_LANG_NOT_SUPPORTED = -2
-};
-
-class TtsEngine
-{
-public:
-    virtual ~TtsEngine() {}
-
-    // Initialize the TTS engine and returns whether initialization succeeded.
-    // @param synthDoneCBPtr synthesis callback function pointer
-    // @return TTS_SUCCESS, or TTS_FAILURE
-    virtual tts_result init(synthDoneCB_t synthDoneCBPtr, const char *engineConfig);
-
-    // Shut down the TTS engine and releases all associated resources.
-    // @return TTS_SUCCESS, or TTS_FAILURE
-    virtual tts_result shutdown();
-
-    // Interrupt synthesis and flushes any synthesized data that hasn't been
-    // output yet. This will block until callbacks underway are completed.
-    // @return TTS_SUCCESS, or TTS_FAILURE
-    virtual tts_result stop();
-
-    // Returns the level of support for the language, country and variant.
-    // @return TTS_LANG_COUNTRY_VAR_AVAILABLE if the language, country and variant are supported,
-    //            and the corresponding resources are correctly installed
-    //         TTS_LANG_COUNTRY_AVAILABLE if the language and country are supported and the
-    //             corresponding resources are correctly installed, but there is no match for
-    //             the specified variant
-    //         TTS_LANG_AVAILABLE if the language is supported and the
-    //             corresponding resources are correctly installed, but there is no match for
-    //             the specified country and variant
-    //         TTS_LANG_MISSING_DATA if the required resources to provide any level of support
-    //             for the language are not correctly installed
-    //         TTS_LANG_NOT_SUPPORTED if the language is not supported by the TTS engine.
-    virtual tts_support_result isLanguageAvailable(const char *lang, const char *country,
-            const char *variant);
-
-    // Load the resources associated with the specified language. The loaded
-    // language will only be used once a call to setLanguage() with the same
-    // language value is issued. Language and country values are coded according to the ISO three
-    // letter codes for languages and countries, as can be retrieved from a java.util.Locale
-    // instance. The variant value is encoded as the variant string retrieved from a
-    // java.util.Locale instance built with that variant data.
-    // @param lang pointer to the ISO three letter code for the language
-    // @param country pointer to the ISO three letter code for the country
-    // @param variant pointer to the variant code
-    // @return TTS_SUCCESS, or TTS_FAILURE
-    virtual tts_result loadLanguage(const char *lang, const char *country, const char *variant);
-
-    // Load the resources associated with the specified language, country and Locale variant.
-    // The loaded language will only be used once a call to setLanguageFromLocale() with the same
-    // language value is issued. Language and country values are coded according to the ISO three
-    // letter codes for languages and countries, as can be retrieved from a java.util.Locale
-    // instance. The variant value is encoded as the variant string retrieved from a
-    // java.util.Locale instance built with that variant data.
-    // @param lang pointer to the ISO three letter code for the language
-    // @param country pointer to the ISO three letter code for the country
-    // @param variant pointer to the variant code
-    // @return TTS_SUCCESS, or TTS_FAILURE
-    virtual tts_result setLanguage(const char *lang, const char *country, const char *variant);
-
-    // Retrieve the currently set language, country and variant, or empty strings if none of
-    // parameters have been set. Language and country are represented by their 3-letter ISO code
-    // @param[out]   pointer to the retrieved 3-letter code language value
-    // @param[out]   pointer to the retrieved 3-letter code country value
-    // @param[out]   pointer to the retrieved variant value
-    // @return TTS_SUCCESS, or TTS_FAILURE
-    virtual tts_result getLanguage(char *language, char *country, char *variant);
-
-    // Notifies the engine what audio parameters should be used for the synthesis.
-    // This is meant to be used as a hint, the engine implementation will set the output values
-    // to those of the synthesis format, based on a given hint.
-    // @param[inout] encoding in: the desired audio sample format
-    //                         out: the format used by the TTS engine
-    // @param[inout] rate in: the desired audio sample rate
-    //                         out: the sample rate used by the TTS engine
-    // @param[inout] channels in: the desired number of audio channels
-    //                         out: the number of channels used by the TTS engine
-    // @return TTS_SUCCESS, or TTS_FAILURE
-    virtual tts_result setAudioFormat(AudioSystem::audio_format& encoding, uint32_t& rate,
-            int& channels);
-
-    // Set a property for the the TTS engine
-    // "size" is the maximum size of "value" for properties "property"
-    // @param property pointer to the property name
-    // @param value    pointer to the property value
-    // @param size     maximum size required to store this type of property
-    // @return         TTS_PROPERTY_UNSUPPORTED, or TTS_SUCCESS, or TTS_FAILURE,
-    //                  or TTS_VALUE_INVALID
-    virtual tts_result setProperty(const char *property, const char *value,
-            const size_t size);
-
-    // Retrieve a property from the TTS engine
-    // @param        property pointer to the property name
-    // @param[out]   value    pointer to the retrieved language value
-    // @param[inout] iosize   in: stores the size available to store the
-    //                          property value.
-    //                        out: stores the size required to hold the language
-    //                          value if getLanguage() returned
-    //                          TTS_PROPERTY_SIZE_TOO_SMALL, unchanged otherwise
-    // @return TTS_PROPERTY_UNSUPPORTED, or TTS_SUCCESS,
-    //         or TTS_PROPERTY_SIZE_TOO_SMALL
-    virtual tts_result getProperty(const char *property, char *value,
-            size_t *iosize);
-
-    // Synthesize the text.
-    // As the synthesis is performed, the engine invokes the callback to notify
-    // the TTS framework that it has filled the given buffer, and indicates how
-    // many bytes it wrote. The callback is called repeatedly until the engine
-    // has generated all the audio data corresponding to the text.
-    // Note about the format of the input: the text parameter may use the
-    // following elements
-    // and their respective attributes as defined in the SSML 1.0 specification:
-    //    * lang
-    //    * say-as:
-    //          o interpret-as
-    //    * phoneme
-    //    * voice:
-    //          o gender,
-    //          o age,
-    //          o variant,
-    //          o name
-    //    * emphasis
-    //    * break:
-    //          o strength,
-    //          o time
-    //    * prosody:
-    //          o pitch,
-    //          o contour,
-    //          o range,
-    //          o rate,
-    //          o duration,
-    //          o volume
-    //    * mark
-    // Differences between this text format and SSML are:
-    //    * full SSML documents are not supported
-    //    * namespaces are not supported
-    // Text is coded in UTF-8.
-    // @param text      the UTF-8 text to synthesize
-    // @param userdata  pointer to be returned when the call is invoked
-    // @param buffer    the location where the synthesized data must be written
-    // @param bufferSize the number of bytes that can be written in buffer
-    // @return          TTS_SUCCESS or TTS_FAILURE
-    virtual tts_result synthesizeText(const char *text, int8_t *buffer,
-            size_t bufferSize, void *userdata);
-
-};
-
-} // namespace android
-
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 03d8cfa..a0fc4d0 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -338,7 +338,7 @@
 
 status_t Parcel::setDataCapacity(size_t size)
 {
-    if (size > mDataSize) return continueWrite(size);
+    if (size > mDataCapacity) return continueWrite(size);
     return NO_ERROR;
 }
 
@@ -386,10 +386,12 @@
     }
     int numObjects = lastIndex - firstIndex + 1;
 
-    // grow data
-    err = growData(len);
-    if (err != NO_ERROR) {
-        return err;
+    if ((mDataSize+len) > mDataCapacity) {
+        // grow data
+        err = growData(len);
+        if (err != NO_ERROR) {
+            return err;
+        }
     }
 
     // append data
@@ -1384,8 +1386,10 @@
                 return NO_MEMORY;
             }
         } else {
-            mDataSize = desired;
-            LOGV("continueWrite Setting data size of %p to %d\n", this, mDataSize);
+            if (mDataSize > desired) {
+                mDataSize = desired;
+                LOGV("continueWrite Setting data size of %p to %d\n", this, mDataSize);
+            }
             if (mDataPos > desired) {
                 mDataPos = desired;
                 LOGV("continueWrite Setting data pos of %p to %d\n", this, mDataPos);
diff --git a/libs/gui/IGraphicBufferAlloc.cpp b/libs/gui/IGraphicBufferAlloc.cpp
index e05da72..0cd51da 100644
--- a/libs/gui/IGraphicBufferAlloc.cpp
+++ b/libs/gui/IGraphicBufferAlloc.cpp
@@ -32,7 +32,6 @@
 
 enum {
     CREATE_GRAPHIC_BUFFER = IBinder::FIRST_CALL_TRANSACTION,
-    FREE_ALL_GRAPHIC_BUFFERS_EXCEPT,
 };
 
 class BpGraphicBufferAlloc : public BpInterface<IGraphicBufferAlloc>
@@ -46,8 +45,7 @@
     virtual sp<GraphicBuffer> createGraphicBuffer(uint32_t w, uint32_t h,
             PixelFormat format, uint32_t usage) {
         Parcel data, reply;
-        data.writeInterfaceToken(
-                IGraphicBufferAlloc::getInterfaceDescriptor());
+        data.writeInterfaceToken(IGraphicBufferAlloc::getInterfaceDescriptor());
         data.writeInt32(w);
         data.writeInt32(h);
         data.writeInt32(format);
@@ -58,17 +56,12 @@
         if (nonNull) {
             graphicBuffer = new GraphicBuffer();
             reply.read(*graphicBuffer);
+            // reply.readStrongBinder();
+            // here we don't even have to read the BufferReference from
+            // the parcel, it'll die with the parcel.
         }
         return graphicBuffer;
     }
-
-    virtual void freeAllGraphicBuffersExcept(int bufIdx) {
-        Parcel data, reply;
-        data.writeInterfaceToken(
-                IGraphicBufferAlloc::getInterfaceDescriptor());
-        data.writeInt32(bufIdx);
-        remote()->transact(FREE_ALL_GRAPHIC_BUFFERS_EXCEPT, data, &reply);
-    }
 };
 
 IMPLEMENT_META_INTERFACE(GraphicBufferAlloc, "android.ui.IGraphicBufferAlloc");
@@ -80,6 +73,17 @@
 {
     // codes that don't require permission check
 
+    /* BufferReference just keeps a strong reference to a
+     * GraphicBuffer until it is destroyed (that is, until
+     * no local or remote process have a reference to it).
+     */
+    class BufferReference : public BBinder {
+        sp<GraphicBuffer> buffer;
+    public:
+        BufferReference(const sp<GraphicBuffer>& buffer) : buffer(buffer) { }
+    };
+
+
     switch(code) {
         case CREATE_GRAPHIC_BUFFER: {
             CHECK_INTERFACE(IGraphicBufferAlloc, data, reply);
@@ -91,15 +95,16 @@
             reply->writeInt32(result != 0);
             if (result != 0) {
                 reply->write(*result);
+                // We add a BufferReference to this parcel to make sure the
+                // buffer stays alive until the GraphicBuffer object on
+                // the other side has been created.
+                // This is needed so that the buffer handle can be
+                // registered before the buffer is destroyed on implementations
+                // that do not use file-descriptors to track their buffers.
+                reply->writeStrongBinder( new BufferReference(result) );
             }
             return NO_ERROR;
         } break;
-        case FREE_ALL_GRAPHIC_BUFFERS_EXCEPT: {
-            CHECK_INTERFACE(IGraphicBufferAlloc, data, reply);
-            int bufIdx = data.readInt32();
-            freeAllGraphicBuffersExcept(bufIdx);
-            return NO_ERROR;
-        } break;
         default:
             return BBinder::onTransact(code, data, reply, flags);
     }
diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp
index f4e2a67..e2346f0 100644
--- a/libs/gui/SurfaceTexture.cpp
+++ b/libs/gui/SurfaceTexture.cpp
@@ -172,7 +172,6 @@
             mSlots[buf].mEglImage = EGL_NO_IMAGE_KHR;
             mSlots[buf].mEglDisplay = EGL_NO_DISPLAY;
         }
-        mAllocdBuffers.add(graphicBuffer);
     }
     return graphicBuffer;
 }
@@ -425,19 +424,6 @@
             mSlots[i].mEglDisplay = EGL_NO_DISPLAY;
         }
     }
-
-    int exceptBuf = -1;
-    for (size_t i = 0; i < mAllocdBuffers.size(); i++) {
-        if (mAllocdBuffers[i] == mCurrentTextureBuf) {
-            exceptBuf = i;
-            break;
-        }
-    }
-    mAllocdBuffers.clear();
-    if (exceptBuf >= 0) {
-        mAllocdBuffers.add(mCurrentTextureBuf);
-    }
-    mGraphicBufferAlloc->freeAllGraphicBuffersExcept(exceptBuf);
 }
 
 EGLImageKHR SurfaceTexture::createImage(EGLDisplay dpy,
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 0b0d145a..1e4b585 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1120,6 +1120,7 @@
         mOnErrorListener = null;
         mOnInfoListener = null;
         mOnVideoSizeChangedListener = null;
+        mOnTimedTextListener = null;
         _release();
     }
 
@@ -1301,6 +1302,7 @@
     private static final int MEDIA_BUFFERING_UPDATE = 3;
     private static final int MEDIA_SEEK_COMPLETE = 4;
     private static final int MEDIA_SET_VIDEO_SIZE = 5;
+    private static final int MEDIA_TIMED_TEXT = 99;
     private static final int MEDIA_ERROR = 100;
     private static final int MEDIA_INFO = 200;
 
@@ -1369,6 +1371,11 @@
                 }
                 // No real default action so far.
                 return;
+            case MEDIA_TIMED_TEXT:
+                if (mOnTimedTextListener != null) {
+                    mOnTimedTextListener.onTimedText(mMediaPlayer, (String)msg.obj);
+                }
+                return;
 
             case MEDIA_NOP: // interface test message - ignore
                 break;
@@ -1545,6 +1552,39 @@
 
     private OnVideoSizeChangedListener mOnVideoSizeChangedListener;
 
+    /**
+     * Interface definition of a callback to be invoked when a
+     * timed text is available for display.
+     * {@hide}
+     */
+    public interface OnTimedTextListener
+    {
+        /**
+         * Called to indicate the video size
+         *
+         * @param mp             the MediaPlayer associated with this callback
+         * @param text           the timed text sample which contains the
+         *                       text needed to be displayed.
+         * {@hide}
+         */
+        public void onTimedText(MediaPlayer mp, String text);
+    }
+
+    /**
+     * Register a callback to be invoked when a timed text is available
+     * for display.
+     *
+     * @param listener the callback that will be run
+     * {@hide}
+     */
+    public void setOnTimedTextListener(OnTimedTextListener listener)
+    {
+        mOnTimedTextListener = listener;
+    }
+
+    private OnTimedTextListener mOnTimedTextListener;
+
+
     /* Do not change these values without updating their counterparts
      * in include/media/mediaplayer.h!
      */
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index aa0adf3..23a77d4 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -69,7 +69,7 @@
 public:
     JNIMediaPlayerListener(JNIEnv* env, jobject thiz, jobject weak_thiz);
     ~JNIMediaPlayerListener();
-    void notify(int msg, int ext1, int ext2);
+    virtual void notify(int msg, int ext1, int ext2, const Parcel *obj = NULL);
 private:
     JNIMediaPlayerListener();
     jclass      mClass;     // Reference to MediaPlayer class
@@ -102,10 +102,23 @@
     env->DeleteGlobalRef(mClass);
 }
 
-void JNIMediaPlayerListener::notify(int msg, int ext1, int ext2)
+void JNIMediaPlayerListener::notify(int msg, int ext1, int ext2, const Parcel *obj)
 {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    env->CallStaticVoidMethod(mClass, fields.post_event, mObject, msg, ext1, ext2, 0);
+    if (obj && obj->dataSize() > 0) {
+        jbyteArray jArray = env->NewByteArray(obj->dataSize());
+        if (jArray != NULL) {
+            jbyte *nArray = env->GetByteArrayElements(jArray, NULL);
+            memcpy(nArray, obj->data(), obj->dataSize());
+            env->ReleaseByteArrayElements(jArray, nArray, 0);
+            env->CallStaticVoidMethod(mClass, fields.post_event, mObject,
+                    msg, ext1, ext2, jArray);
+            env->DeleteLocalRef(jArray);
+        }
+    } else {
+        env->CallStaticVoidMethod(mClass, fields.post_event, mObject,
+                msg, ext1, ext2, NULL);
+    }
 }
 
 // ----------------------------------------------------------------------------
diff --git a/media/libmedia/IMediaPlayerClient.cpp b/media/libmedia/IMediaPlayerClient.cpp
index bf51829..1f135c4 100644
--- a/media/libmedia/IMediaPlayerClient.cpp
+++ b/media/libmedia/IMediaPlayerClient.cpp
@@ -35,13 +35,16 @@
     {
     }
 
-    virtual void notify(int msg, int ext1, int ext2)
+    virtual void notify(int msg, int ext1, int ext2, const Parcel *obj)
     {
         Parcel data, reply;
         data.writeInterfaceToken(IMediaPlayerClient::getInterfaceDescriptor());
         data.writeInt32(msg);
         data.writeInt32(ext1);
         data.writeInt32(ext2);
+        if (obj && obj->dataSize() > 0) {
+            data.appendFrom(const_cast<Parcel *>(obj), 0, obj->dataSize());
+        }
         remote()->transact(NOTIFY, data, &reply, IBinder::FLAG_ONEWAY);
     }
 };
@@ -59,7 +62,12 @@
             int msg = data.readInt32();
             int ext1 = data.readInt32();
             int ext2 = data.readInt32();
-            notify(msg, ext1, ext2);
+            Parcel obj;
+            if (data.dataAvail() > 0) {
+                obj.appendFrom(const_cast<Parcel *>(&data), data.dataPosition(), data.dataAvail());
+            }
+
+            notify(msg, ext1, ext2, &obj);
             return NO_ERROR;
         } break;
         default:
diff --git a/media/libmedia/mediaplayer.cpp b/media/libmedia/mediaplayer.cpp
index 0ee0249a..e80e742 100644
--- a/media/libmedia/mediaplayer.cpp
+++ b/media/libmedia/mediaplayer.cpp
@@ -551,7 +551,7 @@
     return mPlayer->attachAuxEffect(effectId);
 }
 
-void MediaPlayer::notify(int msg, int ext1, int ext2)
+void MediaPlayer::notify(int msg, int ext1, int ext2, const Parcel *obj)
 {
     LOGV("message received msg=%d, ext1=%d, ext2=%d", msg, ext1, ext2);
     bool send = true;
@@ -641,6 +641,9 @@
         mVideoWidth = ext1;
         mVideoHeight = ext2;
         break;
+    case MEDIA_TIMED_TEXT:
+        LOGV("Received timed text message");
+        break;
     default:
         LOGV("unrecognized message: (%d, %d, %d)", msg, ext1, ext2);
         break;
@@ -653,7 +656,7 @@
     if ((listener != 0) && send) {
         Mutex::Autolock _l(mNotifyLock);
         LOGV("callback application");
-        listener->notify(msg, ext1, ext2);
+        listener->notify(msg, ext1, ext2, obj);
         LOGV("back from callback");
     }
 }
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index 9c9ac97..6b97708 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -1022,7 +1022,8 @@
     return NO_ERROR;
 }
 
-void MediaPlayerService::Client::notify(void* cookie, int msg, int ext1, int ext2)
+void MediaPlayerService::Client::notify(
+        void* cookie, int msg, int ext1, int ext2, const Parcel *obj)
 {
     Client* client = static_cast<Client*>(cookie);
 
@@ -1039,7 +1040,7 @@
         client->addNewMetadataUpdate(metadata_type);
     }
     LOGV("[%d] notify (%p, %d, %d, %d)", client->mConnId, cookie, msg, ext1, ext2);
-    client->mClient->notify(msg, ext1, ext2);
+    client->mClient->notify(msg, ext1, ext2, obj);
 }
 
 
@@ -1623,7 +1624,8 @@
     return mError;
 }
 
-void MediaPlayerService::AudioCache::notify(void* cookie, int msg, int ext1, int ext2)
+void MediaPlayerService::AudioCache::notify(
+        void* cookie, int msg, int ext1, int ext2, const Parcel *obj)
 {
     LOGV("notify(%p, %d, %d, %d)", cookie, msg, ext1, ext2);
     AudioCache* p = static_cast<AudioCache*>(cookie);
diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h
index ff6ccf54..5539a37 100644
--- a/media/libmediaplayerservice/MediaPlayerService.h
+++ b/media/libmediaplayerservice/MediaPlayerService.h
@@ -156,7 +156,8 @@
 
                 sp<IMemoryHeap> getHeap() const { return mHeap; }
 
-        static  void            notify(void* cookie, int msg, int ext1, int ext2);
+        static  void            notify(void* cookie, int msg,
+                                       int ext1, int ext2, const Parcel *obj);
         virtual status_t        dump(int fd, const Vector<String16>& args) const;
 
     private:
@@ -287,7 +288,8 @@
 
                 status_t        setDataSource(const sp<IStreamSource> &source);
 
-        static  void            notify(void* cookie, int msg, int ext1, int ext2);
+        static  void            notify(void* cookie, int msg,
+                                       int ext1, int ext2, const Parcel *obj);
 
                 pid_t           pid() const { return mPid; }
         virtual status_t        dump(int fd, const Vector<String16>& args) const;
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 9bd329df..1bfdd8e 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define DEBUG_HDCP
+#undef DEBUG_HDCP
 
 //#define LOG_NDEBUG 0
 #define LOG_TAG "AwesomePlayer"
diff --git a/native/include/android/tts.h b/native/include/android/tts.h
deleted file mode 100644
index fb15108..0000000
--- a/native/include/android/tts.h
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * Copyright (C) 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#ifndef ANDROID_TTS_H
-#define ANDROID_TTS_H 
-
-// This header defines the interface used by the Android platform
-// to access Text-To-Speech functionality in shared libraries that implement
-// speech synthesis and the management of resources associated with the
-// synthesis.
-
-// The shared library must contain a function named "android_getTtsEngine"
-// that returns an 'android_tts_engine_t' instance.
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define ANDROID_TTS_ENGINE_PROPERTY_CONFIG "engineConfig"
-#define ANDROID_TTS_ENGINE_PROPERTY_PITCH  "pitch"
-#define ANDROID_TTS_ENGINE_PROPERTY_RATE   "rate"
-#define ANDROID_TTS_ENGINE_PROPERTY_VOLUME "volume"
-
-typedef enum {
-    ANDROID_TTS_SUCCESS                 = 0,
-    ANDROID_TTS_FAILURE                 = -1,
-    ANDROID_TTS_FEATURE_UNSUPPORTED     = -2,
-    ANDROID_TTS_VALUE_INVALID           = -3,
-    ANDROID_TTS_PROPERTY_UNSUPPORTED    = -4,
-    ANDROID_TTS_PROPERTY_SIZE_TOO_SMALL = -5,
-    ANDROID_TTS_MISSING_RESOURCES       = -6
-} android_tts_result_t;
-
-typedef enum {
-    ANDROID_TTS_LANG_COUNTRY_VAR_AVAILABLE = 2,
-    ANDROID_TTS_LANG_COUNTRY_AVAILABLE    = 1,
-    ANDROID_TTS_LANG_AVAILABLE            = 0,
-    ANDROID_TTS_LANG_MISSING_DATA         = -1,
-    ANDROID_TTS_LANG_NOT_SUPPORTED        = -2
-} android_tts_support_result_t;
-
-typedef enum {
-    ANDROID_TTS_SYNTH_DONE              = 0,
-    ANDROID_TTS_SYNTH_PENDING           = 1
-} android_tts_synth_status_t;
-
-typedef enum {
-    ANDROID_TTS_CALLBACK_HALT           = 0,
-    ANDROID_TTS_CALLBACK_CONTINUE       = 1
-} android_tts_callback_status_t;
-
-// Supported audio formats
-typedef enum {
-    ANDROID_TTS_AUDIO_FORMAT_INVALID    = -1,
-    ANDROID_TTS_AUDIO_FORMAT_DEFAULT    = 0,
-    ANDROID_TTS_AUDIO_FORMAT_PCM_16_BIT = 1,
-    ANDROID_TTS_AUDIO_FORMAT_PCM_8_BIT  = 2,
-} android_tts_audio_format_t;
-
-
-/* An android_tts_engine_t object can be anything, but must have,
- * as its first field, a pointer to a table of functions.
- *
- * See the full definition of struct android_tts_engine_t_funcs_t
- * below for details.
- */
-typedef struct android_tts_engine_funcs_t  android_tts_engine_funcs_t;
-
-typedef struct {
-    android_tts_engine_funcs_t *funcs;
-} android_tts_engine_t;
-
-/* This function must be located in the TTS Engine shared library
- * and must return the address of an android_tts_engine_t library.
- */
-extern android_tts_engine_t *android_getTtsEngine();
-
-/* Including the old version for legacy support (Froyo compatibility).
- * This should return the same thing as android_getTtsEngine.
- */
-extern "C" android_tts_engine_t *getTtsEngine();
-
-// A callback type used to notify the framework of new synthetized
-// audio samples, status will be SYNTH_DONE for the last sample of
-// the last request, of SYNTH_PENDING otherwise.
-//
-// This is passed by the framework to the engine through the
-// 'engine_init' function (see below).
-//
-// The callback for synthesis completed takes:
-// @param [inout] void *&       - The userdata pointer set in the original
-//                                 synth call
-// @param [in]    uint32_t      - Track sampling rate in Hz
-// @param [in]    uint32_t      - The audio format
-// @param [in]    int           - The number of channels
-// @param [inout] int8_t *&     - A buffer of audio data only valid during the
-//                                execution of the callback
-// @param [inout] size_t  &     - The size of the buffer
-// @param [in] tts_synth_status - indicate whether the synthesis is done, or
-//                                 if more data is to be synthesized.
-// @return TTS_CALLBACK_HALT to indicate the synthesis must stop,
-//         TTS_CALLBACK_CONTINUE to indicate the synthesis must continue if
-//            there is more data to produce.
-typedef android_tts_callback_status_t (*android_tts_synth_cb_t)
-            (void **pUserData,
-             uint32_t trackSamplingHz,
-             android_tts_audio_format_t audioFormat,
-             int channelCount,
-             int8_t **pAudioBuffer,
-             size_t *pBufferSize,
-             android_tts_synth_status_t status);
-
-
-// The table of function pointers that the android_tts_engine_t must point to.
-// Note that each of these functions will take a handle to the engine itself
-// as their first parameter.
-//
-
-struct android_tts_engine_funcs_t {
-    // reserved fields, ignored by the framework
-    // they must be placed here to ensure binary compatibility
-    // of legacy binary plugins.
-    void *reserved[2];
-
-    // Initialize the TTS engine and returns whether initialization succeeded.
-    // @param synthDoneCBPtr synthesis callback function pointer
-    // @return TTS_SUCCESS, or TTS_FAILURE
-    android_tts_result_t (*init)
-            (void *engine,
-             android_tts_synth_cb_t synthDonePtr,
-             const char *engineConfig);
-
-    // Shut down the TTS engine and releases all associated resources.
-    // @return TTS_SUCCESS, or TTS_FAILURE
-    android_tts_result_t (*shutdown)
-            (void *engine);
-
-    // Interrupt synthesis and flushes any synthesized data that hasn't been
-    // output yet. This will block until callbacks underway are completed.
-    // @return TTS_SUCCESS, or TTS_FAILURE
-    android_tts_result_t (*stop)
-            (void *engine);
-
-    // Returns the level of support for the language, country and variant.
-    // @return TTS_LANG_COUNTRY_VAR_AVAILABLE if the language, country and variant are supported,
-    //            and the corresponding resources are correctly installed
-    //         TTS_LANG_COUNTRY_AVAILABLE if the language and country are supported and the
-    //             corresponding resources are correctly installed, but there is no match for
-    //             the specified variant
-    //         TTS_LANG_AVAILABLE if the language is supported and the
-    //             corresponding resources are correctly installed, but there is no match for
-    //             the specified country and variant
-    //         TTS_LANG_MISSING_DATA if the required resources to provide any level of support
-    //             for the language are not correctly installed
-    //         TTS_LANG_NOT_SUPPORTED if the language is not supported by the TTS engine.
-    android_tts_support_result_t (*isLanguageAvailable)
-            (void *engine,
-             const char *lang,
-             const char *country,
-             const char *variant);
-
-    // Load the resources associated with the specified language. The loaded
-    // language will only be used once a call to setLanguage() with the same
-    // language value is issued. Language and country values are coded according to the ISO three
-    // letter codes for languages and countries, as can be retrieved from a java.util.Locale
-    // instance. The variant value is encoded as the variant string retrieved from a
-    // java.util.Locale instance built with that variant data.
-    // @param lang pointer to the ISO three letter code for the language
-    // @param country pointer to the ISO three letter code for the country
-    // @param variant pointer to the variant code
-    // @return TTS_SUCCESS, or TTS_FAILURE
-    android_tts_result_t (*loadLanguage)
-            (void *engine,
-             const char *lang,
-             const char *country,
-             const char *variant);
-
-    // Load the resources associated with the specified language, country and Locale variant.
-    // The loaded language will only be used once a call to setLanguageFromLocale() with the same
-    // language value is issued. Language and country values are coded according to the ISO three
-    // letter codes for languages and countries, as can be retrieved from a java.util.Locale
-    // instance. The variant value is encoded as the variant string retrieved from a
-    // java.util.Locale instance built with that variant data.
-    // @param lang pointer to the ISO three letter code for the language
-    // @param country pointer to the ISO three letter code for the country
-    // @param variant pointer to the variant code
-    // @return TTS_SUCCESS, or TTS_FAILURE
-    android_tts_result_t (*setLanguage)
-            (void *engine,
-             const char *lang,
-             const char *country,
-             const char *variant);
-
-    // Retrieve the currently set language, country and variant, or empty strings if none of
-    // parameters have been set. Language and country are represented by their 3-letter ISO code
-    // @param[out]   pointer to the retrieved 3-letter code language value
-    // @param[out]   pointer to the retrieved 3-letter code country value
-    // @param[out]   pointer to the retrieved variant value
-    // @return TTS_SUCCESS, or TTS_FAILURE
-    android_tts_result_t (*getLanguage)
-            (void *engine,
-             char *language,
-             char *country,
-             char *variant);
-
-    // Notifies the engine what audio parameters should be used for the synthesis.
-    // This is meant to be used as a hint, the engine implementation will set the output values
-    // to those of the synthesis format, based on a given hint.
-    // @param[inout] encoding in: the desired audio sample format
-    //                         out: the format used by the TTS engine
-    // @param[inout] rate in: the desired audio sample rate
-    //                         out: the sample rate used by the TTS engine
-    // @param[inout] channels in: the desired number of audio channels
-    //                         out: the number of channels used by the TTS engine
-    // @return TTS_SUCCESS, or TTS_FAILURE
-    android_tts_result_t (*setAudioFormat)
-            (void *engine,
-             android_tts_audio_format_t* pEncoding,
-             uint32_t* pRate,
-             int* pChannels);
-
-    // Set a property for the the TTS engine
-    // "size" is the maximum size of "value" for properties "property"
-    // @param property pointer to the property name
-    // @param value    pointer to the property value
-    // @param size     maximum size required to store this type of property
-    // @return         TTS_PROPERTY_UNSUPPORTED, or TTS_SUCCESS, or TTS_FAILURE,
-    //                  or TTS_VALUE_INVALID
-    android_tts_result_t (*setProperty)
-            (void *engine,
-             const char *property,
-             const char *value,
-             const size_t size);
-
-    // Retrieve a property from the TTS engine
-    // @param        property pointer to the property name
-    // @param[out]   value    pointer to the retrieved language value
-    // @param[inout] iosize   in: stores the size available to store the
-    //                          property value.
-    //                        out: stores the size required to hold the language
-    //                          value if getLanguage() returned
-    //                          TTS_PROPERTY_SIZE_TOO_SMALL, unchanged otherwise
-    // @return TTS_PROPERTY_UNSUPPORTED, or TTS_SUCCESS,
-    //         or TTS_PROPERTY_SIZE_TOO_SMALL
-    android_tts_result_t (*getProperty)
-            (void *engine,
-             const char *property,
-             char *value,
-             size_t *iosize);
-
-    // Synthesize the text.
-    // As the synthesis is performed, the engine invokes the callback to notify
-    // the TTS framework that it has filled the given buffer, and indicates how
-    // many bytes it wrote. The callback is called repeatedly until the engine
-    // has generated all the audio data corresponding to the text.
-    // Note about the format of the input: the text parameter may use the
-    // following elements
-    // and their respective attributes as defined in the SSML 1.0 specification:
-    //    * lang
-    //    * say-as:
-    //          o interpret-as
-    //    * phoneme
-    //    * voice:
-    //          o gender,
-    //          o age,
-    //          o variant,
-    //          o name
-    //    * emphasis
-    //    * break:
-    //          o strength,
-    //          o time
-    //    * prosody:
-    //          o pitch,
-    //          o contour,
-    //          o range,
-    //          o rate,
-    //          o duration,
-    //          o volume
-    //    * mark
-    // Differences between this text format and SSML are:
-    //    * full SSML documents are not supported
-    //    * namespaces are not supported
-    // Text is coded in UTF-8.
-    // @param text      the UTF-8 text to synthesize
-    // @param userdata  pointer to be returned when the call is invoked
-    // @param buffer    the location where the synthesized data must be written
-    // @param bufferSize the number of bytes that can be written in buffer
-    // @return          TTS_SUCCESS or TTS_FAILURE
-    android_tts_result_t (*synthesizeText)
-            (void *engine,
-             const char *text,
-             int8_t *buffer,
-             size_t bufferSize,
-             void *userdata);
-};
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* ANDROID_TTS_H */
diff --git a/packages/TtsService/Android.mk b/packages/TtsService/Android.mk
deleted file mode 100644
index a1a3b9f..0000000
--- a/packages/TtsService/Android.mk
+++ /dev/null
@@ -1,15 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := $(call all-subdir-java-files) \
-
-LOCAL_PACKAGE_NAME := TtsService
-LOCAL_CERTIFICATE := platform
-
-LOCAL_PROGUARD_FLAG_FILES := proguard.flags
-
-include $(BUILD_PACKAGE)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/packages/TtsService/AndroidManifest.xml b/packages/TtsService/AndroidManifest.xml
deleted file mode 100755
index 46e0ad1..0000000
--- a/packages/TtsService/AndroidManifest.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.tts">
-    <application android:label="TTS Service"
-        android:icon="@drawable/ic_launcher_text_to_speech">
-        <service android:enabled="true"
-                 android:name=".TtsService"
-                 android:label="TTS Service">
-            <intent-filter>
-                <action android:name="android.intent.action.START_TTS_SERVICE"/>
-                <category android:name="android.intent.category.TTS"/>
-            </intent-filter>
-        </service>
-    </application>
-    <uses-permission android:name="android.permission.INTERNET"/>
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-</manifest>
diff --git a/packages/TtsService/MODULE_LICENSE_APACHE2 b/packages/TtsService/MODULE_LICENSE_APACHE2
deleted file mode 100644
index e69de29..0000000
--- a/packages/TtsService/MODULE_LICENSE_APACHE2
+++ /dev/null
diff --git a/packages/TtsService/NOTICE b/packages/TtsService/NOTICE
deleted file mode 100644
index 64aaa8d..0000000
--- a/packages/TtsService/NOTICE
+++ /dev/null
@@ -1,190 +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.
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
diff --git a/packages/TtsService/jni/Android.mk b/packages/TtsService/jni/Android.mk
deleted file mode 100755
index 5dc0c30..0000000
--- a/packages/TtsService/jni/Android.mk
+++ /dev/null
@@ -1,30 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= \
-	android_tts_SynthProxy.cpp
-
-LOCAL_C_INCLUDES += \
-	frameworks/base/native/include \
-	$(JNI_H_INCLUDE)
-
-LOCAL_SHARED_LIBRARIES := \
-	libandroid_runtime \
-	libnativehelper \
-	libmedia \
-	libutils \
-	libcutils
-
-ifeq ($(TARGET_SIMULATOR),true)
- LOCAL_LDLIBS += -ldl
-else
- LOCAL_SHARED_LIBRARIES += libdl
-endif
-
-
-LOCAL_MODULE:= libttssynthproxy
-
-LOCAL_ARM_MODE := arm
-
-include $(BUILD_SHARED_LIBRARY)
-
diff --git a/packages/TtsService/jni/android_tts_SynthProxy.cpp b/packages/TtsService/jni/android_tts_SynthProxy.cpp
deleted file mode 100644
index e00fa85..0000000
--- a/packages/TtsService/jni/android_tts_SynthProxy.cpp
+++ /dev/null
@@ -1,1071 +0,0 @@
-/*
- * Copyright (C) 2009-2010 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stdio.h>
-#include <unistd.h>
-
-#define LOG_TAG "SynthProxyJNI"
-
-#include <utils/Log.h>
-#include <nativehelper/jni.h>
-#include <nativehelper/JNIHelp.h>
-#include <android_runtime/AndroidRuntime.h>
-#include <android/tts.h>
-#include <media/AudioTrack.h>
-#include <math.h>
-
-#include <dlfcn.h>
-
-#define DEFAULT_TTS_RATE        16000
-#define DEFAULT_TTS_FORMAT      AudioSystem::PCM_16_BIT
-#define DEFAULT_TTS_NB_CHANNELS 1
-#define DEFAULT_TTS_BUFFERSIZE  2048
-#define DEFAULT_TTS_STREAM_TYPE AudioSystem::MUSIC
-#define DEFAULT_VOLUME          1.0f
-
-// EQ + BOOST parameters
-#define FILTER_LOWSHELF_ATTENUATION -18.0f // in dB
-#define FILTER_TRANSITION_FREQ 1100.0f     // in Hz
-#define FILTER_SHELF_SLOPE 1.0f            // Q
-#define FILTER_GAIN 5.5f // linear gain
-
-#define USAGEMODE_PLAY_IMMEDIATELY 0
-#define USAGEMODE_WRITE_TO_FILE    1
-
-#define SYNTHPLAYSTATE_IS_STOPPED 0
-#define SYNTHPLAYSTATE_IS_PLAYING 1
-
-using namespace android;
-
-// ----------------------------------------------------------------------------
-struct fields_t {
-    jfieldID    synthProxyFieldJniData;
-    jmethodID   synthProxyMethodPost;
-};
-
-// structure to hold the data that is used each time the TTS engine has synthesized more data
-struct afterSynthData_t {
-    jint jniStorage;
-    int  usageMode;
-    FILE* outputFile;
-    AudioSystem::stream_type streamType;
-};
-
-// ----------------------------------------------------------------------------
-// EQ data
-double amp;
-double w;
-double sinw;
-double cosw;
-double beta;
-double a0, a1, a2, b0, b1, b2;
-double m_fa, m_fb, m_fc, m_fd, m_fe;
-double x0;  // x[n]
-double x1;  // x[n-1]
-double x2;  // x[n-2]
-double out0;// y[n]
-double out1;// y[n-1]
-double out2;// y[n-2]
-
-static float fFilterLowshelfAttenuation = FILTER_LOWSHELF_ATTENUATION;
-static float fFilterTransitionFreq = FILTER_TRANSITION_FREQ;
-static float fFilterShelfSlope = FILTER_SHELF_SLOPE;
-static float fFilterGain = FILTER_GAIN;
-static bool  bUseFilter = false;
-
-void initializeEQ() {
-
-    amp = float(pow(10.0, fFilterLowshelfAttenuation / 40.0));
-    w = 2.0 * M_PI * (fFilterTransitionFreq / DEFAULT_TTS_RATE);
-    sinw = float(sin(w));
-    cosw = float(cos(w));
-    beta = float(sqrt(amp)/fFilterShelfSlope);
-
-    // initialize low-shelf parameters
-    b0 = amp * ((amp+1.0F) - ((amp-1.0F)*cosw) + (beta*sinw));
-    b1 = 2.0F * amp * ((amp-1.0F) - ((amp+1.0F)*cosw));
-    b2 = amp * ((amp+1.0F) - ((amp-1.0F)*cosw) - (beta*sinw));
-    a0 = (amp+1.0F) + ((amp-1.0F)*cosw) + (beta*sinw);
-    a1 = 2.0F * ((amp-1.0F) + ((amp+1.0F)*cosw));
-    a2 = -((amp+1.0F) + ((amp-1.0F)*cosw) - (beta*sinw));
-
-    m_fa = fFilterGain * b0/a0;
-    m_fb = fFilterGain * b1/a0;
-    m_fc = fFilterGain * b2/a0;
-    m_fd = a1/a0;
-    m_fe = a2/a0;
-}
-
-void initializeFilter() {
-    x0 = 0.0f;
-    x1 = 0.0f;
-    x2 = 0.0f;
-    out0 = 0.0f;
-    out1 = 0.0f;
-    out2 = 0.0f;
-}
-
-void applyFilter(int16_t* buffer, size_t sampleCount) {
-
-    for (size_t i=0 ; i<sampleCount ; i++) {
-
-        x0 = (double) buffer[i];
-
-        out0 = (m_fa*x0) + (m_fb*x1) + (m_fc*x2) + (m_fd*out1) + (m_fe*out2);
-
-        x2 = x1;
-        x1 = x0;
-
-        out2 = out1;
-        out1 = out0;
-
-        if (out0 > 32767.0f) {
-            buffer[i] = 32767;
-        } else if (out0 < -32768.0f) {
-            buffer[i] = -32768;
-        } else {
-            buffer[i] = (int16_t) out0;
-        }
-    }
-}
-
-
-// ----------------------------------------------------------------------------
-static fields_t javaTTSFields;
-
-// TODO move to synth member once we have multiple simultaneous engines running
-static Mutex engineMutex;
-
-// ----------------------------------------------------------------------------
-class SynthProxyJniStorage {
-    public :
-        jobject                   tts_ref;
-        android_tts_engine_t*     mEngine;
-        void*                     mEngineLibHandle;
-        AudioTrack*               mAudioOut;
-        int8_t                    mPlayState;
-        Mutex                     mPlayLock;
-        AudioSystem::stream_type  mStreamType;
-        uint32_t                  mSampleRate;
-        uint32_t                  mAudFormat;
-        int                       mNbChannels;
-        int8_t *                  mBuffer;
-        size_t                    mBufferSize;
-        float                     mVolume[2];
-
-        SynthProxyJniStorage() {
-            tts_ref = NULL;
-            mEngine = NULL;
-            mEngineLibHandle = NULL;
-            mAudioOut = NULL;
-            mPlayState =  SYNTHPLAYSTATE_IS_STOPPED;
-            mStreamType = DEFAULT_TTS_STREAM_TYPE;
-            mSampleRate = DEFAULT_TTS_RATE;
-            mAudFormat  = DEFAULT_TTS_FORMAT;
-            mNbChannels = DEFAULT_TTS_NB_CHANNELS;
-            mBufferSize = DEFAULT_TTS_BUFFERSIZE;
-            mBuffer = new int8_t[mBufferSize];
-            memset(mBuffer, 0, mBufferSize);
-            mVolume[AudioTrack::LEFT] = DEFAULT_VOLUME;
-            mVolume[AudioTrack::RIGHT] = DEFAULT_VOLUME;
-        }
-
-        ~SynthProxyJniStorage() {
-            //LOGV("entering ~SynthProxyJniStorage()");
-            killAudio();
-            if (mEngine) {
-                mEngine->funcs->shutdown(mEngine);
-                mEngine = NULL;
-            }
-            if (mEngineLibHandle) {
-                //LOGV("~SynthProxyJniStorage(): before close library");
-                int res = dlclose(mEngineLibHandle);
-                LOGE_IF( res != 0, "~SynthProxyJniStorage(): dlclose returned %d", res);
-            }
-            delete mBuffer;
-        }
-
-        void killAudio() {
-            if (mAudioOut) {
-                mAudioOut->stop();
-                delete mAudioOut;
-                mAudioOut = NULL;
-            }
-        }
-
-        void createAudioOut(AudioSystem::stream_type streamType, uint32_t rate,
-                AudioSystem::audio_format format, int channel) {
-            mSampleRate = rate;
-            mAudFormat  = format;
-            mNbChannels = channel;
-            mStreamType = streamType;
-
-            // retrieve system properties to ensure successful creation of the
-            // AudioTrack object for playback
-            int afSampleRate;
-            if (AudioSystem::getOutputSamplingRate(&afSampleRate, mStreamType) != NO_ERROR) {
-                afSampleRate = 44100;
-            }
-            int afFrameCount;
-            if (AudioSystem::getOutputFrameCount(&afFrameCount, mStreamType) != NO_ERROR) {
-                afFrameCount = 2048;
-            }
-            uint32_t afLatency;
-            if (AudioSystem::getOutputLatency(&afLatency, mStreamType) != NO_ERROR) {
-                afLatency = 500;
-            }
-            uint32_t minBufCount = afLatency / ((1000 * afFrameCount)/afSampleRate);
-            if (minBufCount < 2) minBufCount = 2;
-            int minFrameCount = (afFrameCount * rate * minBufCount)/afSampleRate;
-
-            mPlayLock.lock();
-            mAudioOut = new AudioTrack(mStreamType, rate, format,
-                    (channel == 2) ? AudioSystem::CHANNEL_OUT_STEREO : AudioSystem::CHANNEL_OUT_MONO,
-                    minFrameCount > 4096 ? minFrameCount : 4096,
-                    0, 0, 0, 0); // not using an AudioTrack callback
-
-            if (mAudioOut->initCheck() != NO_ERROR) {
-              LOGE("createAudioOut(): AudioTrack error");
-              delete mAudioOut;
-              mAudioOut = NULL;
-            } else {
-              //LOGI("AudioTrack OK");
-              mAudioOut->setVolume(mVolume[AudioTrack::LEFT], mVolume[AudioTrack::RIGHT]);
-              LOGV("AudioTrack ready");
-            }
-            mPlayLock.unlock();
-        }
-};
-
-
-// ----------------------------------------------------------------------------
-void prepAudioTrack(SynthProxyJniStorage* pJniData, AudioSystem::stream_type streamType,
-        uint32_t rate, AudioSystem::audio_format format, int channel) {
-    // Don't bother creating a new audiotrack object if the current
-    // object is already initialized with the same audio parameters.
-    if ( pJniData->mAudioOut &&
-         (rate == pJniData->mSampleRate) &&
-         (format == pJniData->mAudFormat) &&
-         (channel == pJniData->mNbChannels) &&
-         (streamType == pJniData->mStreamType) ){
-        return;
-    }
-    if (pJniData->mAudioOut){
-        pJniData->killAudio();
-    }
-    pJniData->createAudioOut(streamType, rate, format, channel);
-}
-
-
-// ----------------------------------------------------------------------------
-/*
- * Callback from TTS engine.
- * Directly speaks using AudioTrack or write to file
- */
-extern "C" android_tts_callback_status_t
-__ttsSynthDoneCB(void ** pUserdata, uint32_t rate,
-               android_tts_audio_format_t format, int channel,
-               int8_t **pWav, size_t *pBufferSize,
-               android_tts_synth_status_t status)
-{
-    //LOGV("ttsSynthDoneCallback: %d bytes", bufferSize);
-    AudioSystem::audio_format  encoding;
-
-    if (*pUserdata == NULL){
-        LOGE("userdata == NULL");
-        return ANDROID_TTS_CALLBACK_HALT;
-    }
-    switch (format) {
-    case ANDROID_TTS_AUDIO_FORMAT_PCM_8_BIT:
-        encoding = AudioSystem::PCM_8_BIT;
-        break;
-    case ANDROID_TTS_AUDIO_FORMAT_PCM_16_BIT:
-        encoding = AudioSystem::PCM_16_BIT;
-        break;
-    default:
-        LOGE("Can't play, bad format");
-        return ANDROID_TTS_CALLBACK_HALT;
-    }
-    afterSynthData_t* pForAfter = (afterSynthData_t*) *pUserdata;
-    SynthProxyJniStorage* pJniData = (SynthProxyJniStorage*)(pForAfter->jniStorage);
-
-    if (pForAfter->usageMode == USAGEMODE_PLAY_IMMEDIATELY){
-        //LOGV("Direct speech");
-
-        if (*pWav == NULL) {
-            delete pForAfter;
-            pForAfter = NULL;
-            LOGV("Null: speech has completed");
-            return ANDROID_TTS_CALLBACK_HALT;
-        }
-
-        if (*pBufferSize > 0) {
-            prepAudioTrack(pJniData, pForAfter->streamType, rate, encoding, channel);
-            if (pJniData->mAudioOut) {
-                pJniData->mPlayLock.lock();
-                if(pJniData->mAudioOut->stopped()
-                        && (pJniData->mPlayState == SYNTHPLAYSTATE_IS_PLAYING)) {
-                    pJniData->mAudioOut->start();
-                }
-                pJniData->mPlayLock.unlock();
-                if (bUseFilter) {
-                    applyFilter((int16_t*)*pWav, *pBufferSize/2);
-                }
-                pJniData->mAudioOut->write(*pWav, *pBufferSize);
-                memset(*pWav, 0, *pBufferSize);
-                //LOGV("AudioTrack wrote: %d bytes", bufferSize);
-            } else {
-                LOGE("Can't play, null audiotrack");
-                delete pForAfter;
-                pForAfter = NULL;
-                return ANDROID_TTS_CALLBACK_HALT;
-            }
-        }
-    } else  if (pForAfter->usageMode == USAGEMODE_WRITE_TO_FILE) {
-        //LOGV("Save to file");
-        if (*pWav == NULL) {
-            delete pForAfter;
-            LOGV("Null: speech has completed");
-            return ANDROID_TTS_CALLBACK_HALT;
-        }
-        if (*pBufferSize > 0){
-            if (bUseFilter) {
-                applyFilter((int16_t*)*pWav, *pBufferSize/2);
-            }
-            fwrite(*pWav, 1, *pBufferSize, pForAfter->outputFile);
-            memset(*pWav, 0, *pBufferSize);
-        }
-    }
-    // Future update:
-    //      For sync points in the speech, call back into the SynthProxy class through the
-    //      javaTTSFields.synthProxyMethodPost methode to notify
-    //      playback has completed if the synthesis is done or if a marker has been reached.
-
-    if (status == ANDROID_TTS_SYNTH_DONE) {
-        // this struct was allocated in the original android_tts_SynthProxy_speak call,
-        // all processing matching this call is now done.
-        LOGV("Speech synthesis done.");
-        if (pForAfter->usageMode == USAGEMODE_PLAY_IMMEDIATELY) {
-            // only delete for direct playback. When writing to a file, we still have work to do
-            // in android_tts_SynthProxy_synthesizeToFile. The struct will be deleted there.
-            delete pForAfter;
-            pForAfter = NULL;
-        }
-        return ANDROID_TTS_CALLBACK_HALT;
-    }
-
-    // we don't update the wav (output) parameter as we'll let the next callback
-    // write at the same location, we've consumed the data already, but we need
-    // to update bufferSize to let the TTS engine know how much it can write the
-    // next time it calls this function.
-    *pBufferSize = pJniData->mBufferSize;
-
-    return ANDROID_TTS_CALLBACK_CONTINUE;
-}
-
-
-// ----------------------------------------------------------------------------
-static int
-android_tts_SynthProxy_setLowShelf(JNIEnv *env, jobject thiz, jboolean applyFilter,
-        jfloat filterGain, jfloat attenuationInDb, jfloat freqInHz, jfloat slope)
-{
-    int result = ANDROID_TTS_SUCCESS;
-
-    bUseFilter = applyFilter;
-    if (applyFilter) {
-        fFilterLowshelfAttenuation = attenuationInDb;
-        fFilterTransitionFreq = freqInHz;
-        fFilterShelfSlope = slope;
-        fFilterGain = filterGain;
-
-        if (fFilterShelfSlope != 0.0f) {
-            initializeEQ();
-        } else {
-            LOGE("Invalid slope, can't be null");
-            result = ANDROID_TTS_FAILURE;
-        }
-    }
-
-    return result;
-}
-
-// ----------------------------------------------------------------------------
-static int
-android_tts_SynthProxy_native_setup(JNIEnv *env, jobject thiz,
-        jobject weak_this, jstring nativeSoLib, jstring engConfig)
-{
-    int result = ANDROID_TTS_FAILURE;
-
-    bUseFilter = false;
-
-    SynthProxyJniStorage* pJniStorage = new SynthProxyJniStorage();
-
-    prepAudioTrack(pJniStorage,
-            DEFAULT_TTS_STREAM_TYPE, DEFAULT_TTS_RATE, DEFAULT_TTS_FORMAT, DEFAULT_TTS_NB_CHANNELS);
-
-    const char *nativeSoLibNativeString =  env->GetStringUTFChars(nativeSoLib, 0);
-    const char *engConfigString = env->GetStringUTFChars(engConfig, 0);
-
-    void *engine_lib_handle = dlopen(nativeSoLibNativeString,
-            RTLD_NOW | RTLD_LOCAL);
-    if (engine_lib_handle == NULL) {
-       LOGE("android_tts_SynthProxy_native_setup(): engine_lib_handle == NULL");
-    } else {
-        android_tts_engine_t * (*get_TtsEngine)() =
-            reinterpret_cast<android_tts_engine_t* (*)()>(dlsym(engine_lib_handle, "android_getTtsEngine"));
-
-        // Support obsolete/legacy binary modules
-        if (get_TtsEngine == NULL) {
-            get_TtsEngine =
-                reinterpret_cast<android_tts_engine_t* (*)()>(dlsym(engine_lib_handle, "getTtsEngine"));
-        }
-
-        pJniStorage->mEngine = (*get_TtsEngine)();
-        pJniStorage->mEngineLibHandle = engine_lib_handle;
-
-        android_tts_engine_t *engine = pJniStorage->mEngine;
-        if (engine) {
-            Mutex::Autolock l(engineMutex);
-            engine->funcs->init(
-                engine,
-                __ttsSynthDoneCB,
-                engConfigString);
-        }
-
-        result = ANDROID_TTS_SUCCESS;
-    }
-
-    // we use a weak reference so the SynthProxy object can be garbage collected.
-    pJniStorage->tts_ref = env->NewGlobalRef(weak_this);
-
-    // save the JNI resources so we can use them (and free them) later
-    env->SetIntField(thiz, javaTTSFields.synthProxyFieldJniData, (int)pJniStorage);
-
-    env->ReleaseStringUTFChars(nativeSoLib, nativeSoLibNativeString);
-    env->ReleaseStringUTFChars(engConfig, engConfigString);
-
-    return result;
-}
-
-
-static void
-android_tts_SynthProxy_native_finalize(JNIEnv *env, jobject thiz, jint jniData)
-{
-    //LOGV("entering android_tts_SynthProxy_finalize()");
-    if (jniData == 0) {
-        //LOGE("android_tts_SynthProxy_native_finalize(): invalid JNI data");
-        return;
-    }
-
-    Mutex::Autolock l(engineMutex);
-
-    SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
-    env->DeleteGlobalRef(pSynthData->tts_ref);
-    delete pSynthData;
-
-    env->SetIntField(thiz, javaTTSFields.synthProxyFieldJniData, 0);
-}
-
-
-static void
-android_tts_SynthProxy_shutdown(JNIEnv *env, jobject thiz, jint jniData)
-{
-    //LOGV("entering android_tts_SynthProxy_shutdown()");
-
-    // do everything a call to finalize would
-    android_tts_SynthProxy_native_finalize(env, thiz, jniData);
-}
-
-
-static int
-android_tts_SynthProxy_isLanguageAvailable(JNIEnv *env, jobject thiz, jint jniData,
-        jstring language, jstring country, jstring variant)
-{
-    int result = ANDROID_TTS_LANG_NOT_SUPPORTED;
-
-    if (jniData == 0) {
-        LOGE("android_tts_SynthProxy_isLanguageAvailable(): invalid JNI data");
-        return result;
-    }
-
-    SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
-    const char *langNativeString = env->GetStringUTFChars(language, 0);
-    const char *countryNativeString = env->GetStringUTFChars(country, 0);
-    const char *variantNativeString = env->GetStringUTFChars(variant, 0);
-
-    android_tts_engine_t *engine = pSynthData->mEngine;
-
-    if (engine) {
-        result = engine->funcs->isLanguageAvailable(engine,langNativeString,
-                countryNativeString, variantNativeString);
-    }
-    env->ReleaseStringUTFChars(language, langNativeString);
-    env->ReleaseStringUTFChars(country, countryNativeString);
-    env->ReleaseStringUTFChars(variant, variantNativeString);
-    return result;
-}
-
-static int
-android_tts_SynthProxy_setConfig(JNIEnv *env, jobject thiz, jint jniData, jstring engineConfig)
-{
-    int result = ANDROID_TTS_FAILURE;
-
-    if (jniData == 0) {
-        LOGE("android_tts_SynthProxy_setConfig(): invalid JNI data");
-        return result;
-    }
-
-    Mutex::Autolock l(engineMutex);
-
-    SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
-    const char *engineConfigNativeString = env->GetStringUTFChars(engineConfig, 0);
-    android_tts_engine_t *engine = pSynthData->mEngine;
-
-    if (engine) {
-        result = engine->funcs->setProperty(engine,ANDROID_TTS_ENGINE_PROPERTY_CONFIG,
-                engineConfigNativeString, strlen(engineConfigNativeString));
-    }
-    env->ReleaseStringUTFChars(engineConfig, engineConfigNativeString);
-
-    return result;
-}
-
-static int
-android_tts_SynthProxy_setLanguage(JNIEnv *env, jobject thiz, jint jniData,
-        jstring language, jstring country, jstring variant)
-{
-    int result = ANDROID_TTS_LANG_NOT_SUPPORTED;
-
-    if (jniData == 0) {
-        LOGE("android_tts_SynthProxy_setLanguage(): invalid JNI data");
-        return result;
-    }
-
-    Mutex::Autolock l(engineMutex);
-
-    SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
-    const char *langNativeString = env->GetStringUTFChars(language, 0);
-    const char *countryNativeString = env->GetStringUTFChars(country, 0);
-    const char *variantNativeString = env->GetStringUTFChars(variant, 0);
-    android_tts_engine_t *engine = pSynthData->mEngine;
-
-    if (engine) {
-        result = engine->funcs->setLanguage(engine, langNativeString,
-                countryNativeString, variantNativeString);
-    }
-    env->ReleaseStringUTFChars(language, langNativeString);
-    env->ReleaseStringUTFChars(country, countryNativeString);
-    env->ReleaseStringUTFChars(variant, variantNativeString);
-    return result;
-}
-
-
-static int
-android_tts_SynthProxy_loadLanguage(JNIEnv *env, jobject thiz, jint jniData,
-        jstring language, jstring country, jstring variant)
-{
-    int result = ANDROID_TTS_LANG_NOT_SUPPORTED;
-
-    if (jniData == 0) {
-        LOGE("android_tts_SynthProxy_loadLanguage(): invalid JNI data");
-        return result;
-    }
-
-    SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
-    const char *langNativeString = env->GetStringUTFChars(language, 0);
-    const char *countryNativeString = env->GetStringUTFChars(country, 0);
-    const char *variantNativeString = env->GetStringUTFChars(variant, 0);
-    android_tts_engine_t *engine = pSynthData->mEngine;
-
-    if (engine) {
-        result = engine->funcs->loadLanguage(engine, langNativeString,
-                countryNativeString, variantNativeString);
-    }
-    env->ReleaseStringUTFChars(language, langNativeString);
-    env->ReleaseStringUTFChars(country, countryNativeString);
-    env->ReleaseStringUTFChars(variant, variantNativeString);
-
-    return result;
-}
-
-
-static int
-android_tts_SynthProxy_setSpeechRate(JNIEnv *env, jobject thiz, jint jniData,
-        jint speechRate)
-{
-    int result = ANDROID_TTS_FAILURE;
-
-    if (jniData == 0) {
-        LOGE("android_tts_SynthProxy_setSpeechRate(): invalid JNI data");
-        return result;
-    }
-
-    int bufSize = 12;
-    char buffer [bufSize];
-    sprintf(buffer, "%d", speechRate);
-
-    Mutex::Autolock l(engineMutex);
-
-    SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
-    //LOGI("setting speech rate to %d", speechRate);
-    android_tts_engine_t *engine = pSynthData->mEngine;
-
-    if (engine) {
-        result = engine->funcs->setProperty(engine, "rate", buffer, bufSize);
-    }
-
-    return result;
-}
-
-
-static int
-android_tts_SynthProxy_setPitch(JNIEnv *env, jobject thiz, jint jniData,
-        jint pitch)
-{
-    int result = ANDROID_TTS_FAILURE;
-
-    if (jniData == 0) {
-        LOGE("android_tts_SynthProxy_setPitch(): invalid JNI data");
-        return result;
-    }
-
-    Mutex::Autolock l(engineMutex);
-
-    int bufSize = 12;
-    char buffer [bufSize];
-    sprintf(buffer, "%d", pitch);
-
-    SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
-    //LOGI("setting pitch to %d", pitch);
-    android_tts_engine_t *engine = pSynthData->mEngine;
-
-    if (engine) {
-        result = engine->funcs->setProperty(engine, "pitch", buffer, bufSize);
-    }
-
-    return result;
-}
-
-
-static int
-android_tts_SynthProxy_synthesizeToFile(JNIEnv *env, jobject thiz, jint jniData,
-        jstring textJavaString, jstring filenameJavaString)
-{
-    int result = ANDROID_TTS_FAILURE;
-
-    if (jniData == 0) {
-        LOGE("android_tts_SynthProxy_synthesizeToFile(): invalid JNI data");
-        return result;
-    }
-
-    SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
-    if (!pSynthData->mEngine) {
-        LOGE("android_tts_SynthProxy_synthesizeToFile(): invalid engine handle");
-        return result;
-    }
-
-    initializeFilter();
-
-    Mutex::Autolock l(engineMutex);
-
-    // Retrieve audio parameters before writing the file header
-    AudioSystem::audio_format encoding;
-    uint32_t rate = DEFAULT_TTS_RATE;
-    int channels = DEFAULT_TTS_NB_CHANNELS;
-    android_tts_engine_t *engine = pSynthData->mEngine;
-    android_tts_audio_format_t  format = ANDROID_TTS_AUDIO_FORMAT_DEFAULT;
-
-    engine->funcs->setAudioFormat(engine, &format, &rate, &channels);
-
-    switch (format) {
-    case ANDROID_TTS_AUDIO_FORMAT_PCM_16_BIT:
-        encoding = AudioSystem::PCM_16_BIT;
-        break;
-    case ANDROID_TTS_AUDIO_FORMAT_PCM_8_BIT:
-        encoding = AudioSystem::PCM_8_BIT;
-        break;
-    default:
-        LOGE("android_tts_SynthProxy_synthesizeToFile(): engine uses invalid format");
-        return result;
-    }
-
-    const char *filenameNativeString =
-            env->GetStringUTFChars(filenameJavaString, 0);
-    const char *textNativeString = env->GetStringUTFChars(textJavaString, 0);
-
-    afterSynthData_t* pForAfter = new (afterSynthData_t);
-    pForAfter->jniStorage = jniData;
-    pForAfter->usageMode  = USAGEMODE_WRITE_TO_FILE;
-
-    pForAfter->outputFile = fopen(filenameNativeString, "wb");
-
-    if (pForAfter->outputFile == NULL) {
-        LOGE("android_tts_SynthProxy_synthesizeToFile(): error creating output file");
-        delete pForAfter;
-        return result;
-    }
-
-    // Write 44 blank bytes for WAV header, then come back and fill them in
-    // after we've written the audio data
-    char header[44];
-    fwrite(header, 1, 44, pForAfter->outputFile);
-
-    unsigned int unique_identifier;
-
-    memset(pSynthData->mBuffer, 0, pSynthData->mBufferSize);
-
-    result = engine->funcs->synthesizeText(engine, textNativeString,
-            pSynthData->mBuffer, pSynthData->mBufferSize, (void *)pForAfter);
-
-    long filelen = ftell(pForAfter->outputFile);
-
-    int samples = (((int)filelen) - 44) / 2;
-    header[0] = 'R';
-    header[1] = 'I';
-    header[2] = 'F';
-    header[3] = 'F';
-    ((uint32_t *)(&header[4]))[0] = filelen - 8;
-    header[8] = 'W';
-    header[9] = 'A';
-    header[10] = 'V';
-    header[11] = 'E';
-
-    header[12] = 'f';
-    header[13] = 'm';
-    header[14] = 't';
-    header[15] = ' ';
-
-    ((uint32_t *)(&header[16]))[0] = 16;  // size of fmt
-
-    int sampleSizeInByte = (encoding == AudioSystem::PCM_16_BIT ? 2 : 1);
-
-    ((unsigned short *)(&header[20]))[0] = 1;  // format
-    ((unsigned short *)(&header[22]))[0] = channels;  // channels
-    ((uint32_t *)(&header[24]))[0] = rate;  // samplerate
-    ((uint32_t *)(&header[28]))[0] = rate * sampleSizeInByte * channels;// byterate
-    ((unsigned short *)(&header[32]))[0] = sampleSizeInByte * channels;  // block align
-    ((unsigned short *)(&header[34]))[0] = sampleSizeInByte * 8;  // bits per sample
-
-    header[36] = 'd';
-    header[37] = 'a';
-    header[38] = 't';
-    header[39] = 'a';
-
-    ((uint32_t *)(&header[40]))[0] = samples * 2;  // size of data
-
-    // Skip back to the beginning and rewrite the header
-    fseek(pForAfter->outputFile, 0, SEEK_SET);
-    fwrite(header, 1, 44, pForAfter->outputFile);
-
-    fflush(pForAfter->outputFile);
-    fclose(pForAfter->outputFile);
-
-    delete pForAfter;
-    pForAfter = NULL;
-
-    env->ReleaseStringUTFChars(textJavaString, textNativeString);
-    env->ReleaseStringUTFChars(filenameJavaString, filenameNativeString);
-
-    return result;
-}
-
-
-static int
-android_tts_SynthProxy_speak(JNIEnv *env, jobject thiz, jint jniData,
-        jstring textJavaString, jint javaStreamType, jfloat volume, jfloat pan)
-{
-    int result = ANDROID_TTS_FAILURE;
-
-    if (jniData == 0) {
-        LOGE("android_tts_SynthProxy_speak(): invalid JNI data");
-        return result;
-    }
-
-    initializeFilter();
-
-    Mutex::Autolock l(engineMutex);
-
-    SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
-
-    {//scope for lock on mPlayLock
-        Mutex::Autolock _l(pSynthData->mPlayLock);
-
-        pSynthData->mPlayState = SYNTHPLAYSTATE_IS_PLAYING;
-
-        // clip volume and pan
-        float vol = (volume > 1.0f) ? 1.0f : (volume < 0.0f) ? 0.0f : volume;
-        float panning = (pan > 1.0f) ? 1.0f : (pan < -1.0f) ? -1.0f : pan;
-        // compute playback volume based on volume and pan, using balance rule, in order to avoid
-        // lowering volume when panning in center
-        pSynthData->mVolume[AudioTrack::LEFT] = vol;
-        pSynthData->mVolume[AudioTrack::RIGHT] = vol;
-        if (panning > 0.0f) {
-            pSynthData->mVolume[AudioTrack::LEFT] *= (1.0f - panning);
-        } else if (panning < 0.0f) {
-            pSynthData->mVolume[AudioTrack::RIGHT] *= (1.0f + panning);
-        }
-
-        // apply the volume if there is an output
-        if (NULL != pSynthData->mAudioOut) {
-            pSynthData->mAudioOut->setVolume(pSynthData->mVolume[AudioTrack::LEFT],
-                    pSynthData->mVolume[AudioTrack::RIGHT]);
-        }
-
-        //LOGV("android_tts_SynthProxy_speak() vol=%.3f pan=%.3f, mVolume=[%.1f %.1f]",
-        //        volume, pan,
-        //        pSynthData->mVolume[AudioTrack::LEFT], pSynthData->mVolume[AudioTrack::RIGHT]);
-    }
-
-    afterSynthData_t* pForAfter = new (afterSynthData_t);
-    pForAfter->jniStorage = jniData;
-    pForAfter->usageMode  = USAGEMODE_PLAY_IMMEDIATELY;
-    pForAfter->streamType = (AudioSystem::stream_type) javaStreamType;
-
-    if (pSynthData->mEngine) {
-        const char *textNativeString = env->GetStringUTFChars(textJavaString, 0);
-        memset(pSynthData->mBuffer, 0, pSynthData->mBufferSize);
-        android_tts_engine_t *engine = pSynthData->mEngine;
-
-        result = engine->funcs->synthesizeText(engine, textNativeString,
-                pSynthData->mBuffer, pSynthData->mBufferSize, (void *)pForAfter);
-        env->ReleaseStringUTFChars(textJavaString, textNativeString);
-    }
-
-    return result;
-}
-
-
-static int
-android_tts_SynthProxy_stop(JNIEnv *env, jobject thiz, jint jniData)
-{
-    int result = ANDROID_TTS_FAILURE;
-
-    if (jniData == 0) {
-        LOGE("android_tts_SynthProxy_stop(): invalid JNI data");
-        return result;
-    }
-
-    SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
-
-    pSynthData->mPlayLock.lock();
-    pSynthData->mPlayState = SYNTHPLAYSTATE_IS_STOPPED;
-    if (pSynthData->mAudioOut) {
-        pSynthData->mAudioOut->stop();
-    }
-    pSynthData->mPlayLock.unlock();
-
-    android_tts_engine_t *engine = pSynthData->mEngine;
-    if (engine) {
-        result = engine->funcs->stop(engine);
-    }
-
-    return result;
-}
-
-
-static int
-android_tts_SynthProxy_stopSync(JNIEnv *env, jobject thiz, jint jniData)
-{
-    int result = ANDROID_TTS_FAILURE;
-
-    if (jniData == 0) {
-        LOGE("android_tts_SynthProxy_stop(): invalid JNI data");
-        return result;
-    }
-
-    // perform a regular stop
-    result = android_tts_SynthProxy_stop(env, thiz, jniData);
-    // but wait on the engine having released the engine mutex which protects
-    // the synthesizer resources.
-    engineMutex.lock();
-    engineMutex.unlock();
-
-    return result;
-}
-
-
-static jobjectArray
-android_tts_SynthProxy_getLanguage(JNIEnv *env, jobject thiz, jint jniData)
-{
-    if (jniData == 0) {
-        LOGE("android_tts_SynthProxy_getLanguage(): invalid JNI data");
-        return NULL;
-    }
-
-    SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
-
-    if (pSynthData->mEngine) {
-        size_t bufSize = 100;
-        char lang[bufSize];
-        char country[bufSize];
-        char variant[bufSize];
-        memset(lang, 0, bufSize);
-        memset(country, 0, bufSize);
-        memset(variant, 0, bufSize);
-        jobjectArray retLocale = (jobjectArray)env->NewObjectArray(3,
-                env->FindClass("java/lang/String"), env->NewStringUTF(""));
-
-        android_tts_engine_t *engine = pSynthData->mEngine;
-        engine->funcs->getLanguage(engine, lang, country, variant);
-        env->SetObjectArrayElement(retLocale, 0, env->NewStringUTF(lang));
-        env->SetObjectArrayElement(retLocale, 1, env->NewStringUTF(country));
-        env->SetObjectArrayElement(retLocale, 2, env->NewStringUTF(variant));
-        return retLocale;
-    } else {
-        return NULL;
-    }
-}
-
-
-JNIEXPORT int JNICALL
-android_tts_SynthProxy_getRate(JNIEnv *env, jobject thiz, jint jniData)
-{
-    if (jniData == 0) {
-        LOGE("android_tts_SynthProxy_getRate(): invalid JNI data");
-        return 0;
-    }
-
-    SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
-    size_t bufSize = 100;
-
-    char buf[bufSize];
-    memset(buf, 0, bufSize);
-    // TODO check return codes
-    android_tts_engine_t *engine = pSynthData->mEngine;
-    if (engine) {
-        engine->funcs->getProperty(engine,"rate", buf, &bufSize);
-    }
-    return atoi(buf);
-}
-
-// Dalvik VM type signatures
-static JNINativeMethod gMethods[] = {
-    {   "native_stop",
-        "(I)I",
-        (void*)android_tts_SynthProxy_stop
-    },
-    {   "native_stopSync",
-        "(I)I",
-        (void*)android_tts_SynthProxy_stopSync
-    },
-    {   "native_speak",
-        "(ILjava/lang/String;IFF)I",
-        (void*)android_tts_SynthProxy_speak
-    },
-    {   "native_synthesizeToFile",
-        "(ILjava/lang/String;Ljava/lang/String;)I",
-        (void*)android_tts_SynthProxy_synthesizeToFile
-    },
-    {   "native_isLanguageAvailable",
-        "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
-        (void*)android_tts_SynthProxy_isLanguageAvailable
-    },
-    {   "native_setConfig",
-            "(ILjava/lang/String;)I",
-            (void*)android_tts_SynthProxy_setConfig
-    },
-    {   "native_setLanguage",
-        "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
-        (void*)android_tts_SynthProxy_setLanguage
-    },
-    {   "native_loadLanguage",
-        "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
-        (void*)android_tts_SynthProxy_loadLanguage
-    },
-    {   "native_setSpeechRate",
-        "(II)I",
-        (void*)android_tts_SynthProxy_setSpeechRate
-    },
-    {   "native_setPitch",
-        "(II)I",
-        (void*)android_tts_SynthProxy_setPitch
-    },
-    {   "native_getLanguage",
-        "(I)[Ljava/lang/String;",
-        (void*)android_tts_SynthProxy_getLanguage
-    },
-    {   "native_getRate",
-        "(I)I",
-        (void*)android_tts_SynthProxy_getRate
-    },
-    {   "native_shutdown",
-        "(I)V",
-        (void*)android_tts_SynthProxy_shutdown
-    },
-    {   "native_setup",
-        "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)I",
-        (void*)android_tts_SynthProxy_native_setup
-    },
-    {   "native_setLowShelf",
-        "(ZFFFF)I",
-        (void*)android_tts_SynthProxy_setLowShelf
-    },
-    {   "native_finalize",
-        "(I)V",
-        (void*)android_tts_SynthProxy_native_finalize
-    }
-};
-
-#define SP_JNIDATA_FIELD_NAME                "mJniData"
-#define SP_POSTSPEECHSYNTHESIZED_METHOD_NAME "postNativeSpeechSynthesizedInJava"
-
-static const char* const kClassPathName = "android/tts/SynthProxy";
-
-jint JNI_OnLoad(JavaVM* vm, void* reserved)
-{
-    JNIEnv* env = NULL;
-    jint result = -1;
-    jclass clazz;
-
-    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
-        LOGE("ERROR: GetEnv failed\n");
-        goto bail;
-    }
-    assert(env != NULL);
-
-    clazz = env->FindClass(kClassPathName);
-    if (clazz == NULL) {
-        LOGE("Can't find %s", kClassPathName);
-        goto bail;
-    }
-
-    javaTTSFields.synthProxyFieldJniData = NULL;
-    javaTTSFields.synthProxyMethodPost = NULL;
-
-    javaTTSFields.synthProxyFieldJniData = env->GetFieldID(clazz,
-            SP_JNIDATA_FIELD_NAME, "I");
-    if (javaTTSFields.synthProxyFieldJniData == NULL) {
-        LOGE("Can't find %s.%s field", kClassPathName, SP_JNIDATA_FIELD_NAME);
-        goto bail;
-    }
-
-    javaTTSFields.synthProxyMethodPost = env->GetStaticMethodID(clazz,
-            SP_POSTSPEECHSYNTHESIZED_METHOD_NAME, "(Ljava/lang/Object;II)V");
-    if (javaTTSFields.synthProxyMethodPost == NULL) {
-        LOGE("Can't find %s.%s method", kClassPathName, SP_POSTSPEECHSYNTHESIZED_METHOD_NAME);
-        goto bail;
-    }
-
-    if (jniRegisterNativeMethods(
-            env, kClassPathName, gMethods, NELEM(gMethods)) < 0)
-        goto bail;
-
-    /* success -- return valid version number */
-    result = JNI_VERSION_1_4;
-
- bail:
-    return result;
-}
diff --git a/packages/TtsService/proguard.flags b/packages/TtsService/proguard.flags
deleted file mode 100644
index e8bee6b..0000000
--- a/packages/TtsService/proguard.flags
+++ /dev/null
@@ -1,5 +0,0 @@
--keep class android.tts.SynthProxy {
-  int mJniData;
-  # keep all declarations for native methods
-  <methods>;
-}
diff --git a/packages/TtsService/res/drawable-hdpi/ic_launcher_text_to_speech.png b/packages/TtsService/res/drawable-hdpi/ic_launcher_text_to_speech.png
deleted file mode 100644
index f075e0f..0000000
--- a/packages/TtsService/res/drawable-hdpi/ic_launcher_text_to_speech.png
+++ /dev/null
Binary files differ
diff --git a/packages/TtsService/res/drawable-mdpi/ic_launcher_text_to_speech.png b/packages/TtsService/res/drawable-mdpi/ic_launcher_text_to_speech.png
deleted file mode 100644
index cbae7de..0000000
--- a/packages/TtsService/res/drawable-mdpi/ic_launcher_text_to_speech.png
+++ /dev/null
Binary files differ
diff --git a/packages/TtsService/src/android/tts/SynthProxy.java b/packages/TtsService/src/android/tts/SynthProxy.java
deleted file mode 100755
index f5f5fcf..0000000
--- a/packages/TtsService/src/android/tts/SynthProxy.java
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * Copyright (C) 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.tts;
-
-import android.media.AudioManager;
-import android.media.AudioSystem;
-import android.util.Log;
-import java.lang.ref.WeakReference;
-
-/**
- * @hide
- *
- * The SpeechSynthesis class provides a high-level api to create and play
- * synthesized speech. This class is used internally to talk to a native
- * TTS library that implements the interface defined in
- * frameworks/base/include/tts/TtsEngine.h
- *
- */
-@SuppressWarnings("unused")
-public class SynthProxy {
-
-    // Default parameters of a filter to be applied when using the Pico engine.
-    // Such a huge filter gain is justified by how much energy in the low frequencies is "wasted" at
-    // the output of the synthesis. The low shelving filter removes it, leaving room for
-    // amplification.
-    private final static float PICO_FILTER_GAIN = 5.0f; // linear gain
-    private final static float PICO_FILTER_LOWSHELF_ATTENUATION = -18.0f; // in dB
-    private final static float PICO_FILTER_TRANSITION_FREQ = 1100.0f;     // in Hz
-    private final static float PICO_FILTER_SHELF_SLOPE = 1.0f;            // Q
-
-    //
-    // External API
-    //
-
-    /**
-     * Constructor; pass the location of the native TTS .so to use.
-     */
-    public SynthProxy(String nativeSoLib, String engineConfig) {
-        boolean applyFilter = nativeSoLib.toLowerCase().contains("pico");
-        Log.v(TtsService.SERVICE_TAG, "About to load "+ nativeSoLib + ", applyFilter="+applyFilter);
-        native_setup(new WeakReference<SynthProxy>(this), nativeSoLib, engineConfig);
-        native_setLowShelf(applyFilter, PICO_FILTER_GAIN, PICO_FILTER_LOWSHELF_ATTENUATION,
-                PICO_FILTER_TRANSITION_FREQ, PICO_FILTER_SHELF_SLOPE);
-    }
-
-    /**
-     * Stops and clears the AudioTrack.
-     */
-    public int stop() {
-        return native_stop(mJniData);
-    }
-
-    /**
-     * Synchronous stop of the synthesizer. This method returns when the synth
-     * has completed the stop procedure and doesn't use any of the resources it
-     * was using while synthesizing.
-     *
-     * @return {@link android.speech.tts.TextToSpeech.SUCCESS} or
-     *         {@link android.speech.tts.TextToSpeech.ERROR}
-     */
-    public int stopSync() {
-        return native_stopSync(mJniData);
-    }
-
-    /**
-     * Synthesize speech and speak it directly using AudioTrack.
-     */
-    public int speak(String text, int streamType, float volume, float pan) {
-        Log.i(TAG, "speak() on stream "+ streamType);
-        if ((streamType > -1) && (streamType < AudioSystem.getNumStreamTypes())) {
-            return native_speak(mJniData, text, streamType, volume, pan);
-        } else {
-            Log.e("SynthProxy", "Trying to speak with invalid stream type " + streamType);
-            return native_speak(mJniData, text, AudioManager.STREAM_MUSIC, volume, pan);
-        }
-    }
-
-    /**
-     * Synthesize speech to a file. The current implementation writes a valid
-     * WAV file to the given path, assuming it is writable. Something like
-     * "/sdcard/???.wav" is recommended.
-     */
-    public int synthesizeToFile(String text, String filename) {
-        Log.i(TAG, "synthesizeToFile() to file "+ filename);
-        return native_synthesizeToFile(mJniData, text, filename);
-    }
-
-    /**
-     * Queries for language support.
-     * Return codes are defined in android.speech.tts.TextToSpeech
-     */
-    public int isLanguageAvailable(String language, String country, String variant) {
-        return native_isLanguageAvailable(mJniData, language, country, variant);
-    }
-
-    /**
-     * Updates the engine configuration.
-     */
-    public int setConfig(String engineConfig) {
-        return native_setConfig(mJniData, engineConfig);
-    }
-
-    /**
-     * Sets the language.
-     */
-    public int setLanguage(String language, String country, String variant) {
-        return native_setLanguage(mJniData, language, country, variant);
-    }
-
-    /**
-     * Loads the language: it's not set, but prepared for use later.
-     */
-    public int loadLanguage(String language, String country, String variant) {
-        return native_loadLanguage(mJniData, language, country, variant);
-    }
-
-    /**
-     * Sets the speech rate.
-     */
-    public final int setSpeechRate(int speechRate) {
-        return native_setSpeechRate(mJniData, speechRate);
-    }
-
-    /**
-     * Sets the pitch of the synthesized voice.
-     */
-    public final int setPitch(int pitch) {
-        return native_setPitch(mJniData, pitch);
-    }
-
-    /**
-     * Returns the currently set language, country and variant information.
-     */
-    public String[] getLanguage() {
-        return native_getLanguage(mJniData);
-    }
-
-    /**
-     * Gets the currently set rate.
-     */
-    public int getRate() {
-        return native_getRate(mJniData);
-    }
-
-    /**
-     * Shuts down the native synthesizer.
-     */
-    public void shutdown()  {
-        native_shutdown(mJniData);
-    }
-
-    //
-    // Internal
-    //
-
-    protected void finalize() {
-        native_finalize(mJniData);
-        mJniData = 0;
-    }
-
-    static {
-        System.loadLibrary("ttssynthproxy");
-    }
-
-    private final static String TAG = "SynthProxy";
-
-    /**
-     * Accessed by native methods
-     */
-    private int mJniData = 0;
-
-    private native final int native_setup(Object weak_this, String nativeSoLib,
-            String engineConfig);
-
-    private native final int native_setLowShelf(boolean applyFilter, float filterGain,
-            float attenuationInDb, float freqInHz, float slope);
-
-    private native final void native_finalize(int jniData);
-
-    private native final int native_stop(int jniData);
-
-    private native final int native_stopSync(int jniData);
-
-    private native final int native_speak(int jniData, String text, int streamType, float volume,
-            float pan);
-
-    private native final int native_synthesizeToFile(int jniData, String text, String filename);
-
-    private native final int  native_isLanguageAvailable(int jniData, String language,
-            String country, String variant);
-
-    private native final int native_setLanguage(int jniData, String language, String country,
-            String variant);
-
-    private native final int native_loadLanguage(int jniData, String language, String country,
-            String variant);
-
-    private native final int native_setConfig(int jniData, String engineConfig);
-
-    private native final int native_setSpeechRate(int jniData, int speechRate);
-
-    private native final int native_setPitch(int jniData, int speechRate);
-
-    private native final String[] native_getLanguage(int jniData);
-
-    private native final int native_getRate(int jniData);
-
-    private native final void native_shutdown(int jniData);
-
-
-    /**
-     * Callback from the C layer
-     */
-    @SuppressWarnings("unused")
-    private static void postNativeSpeechSynthesizedInJava(Object tts_ref,
-            int bufferPointer, int bufferSize) {
-
-        Log.i("TTS plugin debug", "bufferPointer: " + bufferPointer
-                + " bufferSize: " + bufferSize);
-
-        SynthProxy nativeTTS = (SynthProxy)((WeakReference)tts_ref).get();
-        // TODO notify TTS service of synthesis/playback completion,
-        //      method definition to be changed.
-    }
-}
diff --git a/packages/TtsService/src/android/tts/TtsService.java b/packages/TtsService/src/android/tts/TtsService.java
deleted file mode 100755
index c562327..0000000
--- a/packages/TtsService/src/android/tts/TtsService.java
+++ /dev/null
@@ -1,1503 +0,0 @@
-/*
- * Copyright (C) 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.tts;
-
-import android.app.Service;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.database.Cursor;
-import android.media.AudioManager;
-import android.media.MediaPlayer;
-import android.media.MediaPlayer.OnCompletionListener;
-import android.net.Uri;
-import android.os.IBinder;
-import android.os.RemoteCallbackList;
-import android.os.RemoteException;
-import android.preference.PreferenceManager;
-import android.speech.tts.ITts.Stub;
-import android.speech.tts.ITtsCallback;
-import android.speech.tts.TextToSpeech;
-import android.util.Log;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.concurrent.TimeUnit;
-
-
-/**
- * @hide Synthesizes speech from text. This is implemented as a service so that
- *       other applications can call the TTS without needing to bundle the TTS
- *       in the build.
- *
- */
-public class TtsService extends Service implements OnCompletionListener {
-
-    private static class SpeechItem {
-        public static final int TEXT = 0;
-        public static final int EARCON = 1;
-        public static final int SILENCE = 2;
-        public static final int TEXT_TO_FILE = 3;
-        public String mText = "";
-        public ArrayList<String> mParams = null;
-        public int mType = TEXT;
-        public long mDuration = 0;
-        public String mFilename = null;
-        public String mCallingApp = "";
-
-        public SpeechItem(String source, String text, ArrayList<String> params, int itemType) {
-            mText = text;
-            mParams = params;
-            mType = itemType;
-            mCallingApp = source;
-        }
-
-        public SpeechItem(String source, long silenceTime, ArrayList<String> params) {
-            mDuration = silenceTime;
-            mParams = params;
-            mType = SILENCE;
-            mCallingApp = source;
-        }
-
-        public SpeechItem(String source, String text, ArrayList<String> params,
-                int itemType, String filename) {
-            mText = text;
-            mParams = params;
-            mType = itemType;
-            mFilename = filename;
-            mCallingApp = source;
-        }
-
-    }
-
-    /**
-     * Contains the information needed to access a sound resource; the name of
-     * the package that contains the resource and the resID of the resource
-     * within that package.
-     */
-    private static class SoundResource {
-        public String mSourcePackageName = null;
-        public int mResId = -1;
-        public String mFilename = null;
-
-        public SoundResource(String packageName, int id) {
-            mSourcePackageName = packageName;
-            mResId = id;
-            mFilename = null;
-        }
-
-        public SoundResource(String file) {
-            mSourcePackageName = null;
-            mResId = -1;
-            mFilename = file;
-        }
-    }
-    // If the speech queue is locked for more than 5 seconds, something has gone
-    // very wrong with processSpeechQueue.
-    private static final int SPEECHQUEUELOCK_TIMEOUT = 5000;
-    private static final int MAX_SPEECH_ITEM_CHAR_LENGTH = 4000;
-    private static final int MAX_FILENAME_LENGTH = 250;
-    private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-    // TODO use TextToSpeech.DEFAULT_SYNTH once it is unhidden
-    private static final String DEFAULT_SYNTH = "com.svox.pico";
-    private static final String ACTION = "android.intent.action.START_TTS_SERVICE";
-    private static final String CATEGORY = "android.intent.category.TTS";
-    private static final String PKGNAME = "android.tts";
-    protected static final String SERVICE_TAG = "TtsService";
-
-    private final RemoteCallbackList<ITtsCallback> mCallbacks
-            = new RemoteCallbackList<ITtsCallback>();
-
-    private HashMap<String, ITtsCallback> mCallbacksMap;
-
-    private Boolean mIsSpeaking;
-    private Boolean mSynthBusy;
-    private ArrayList<SpeechItem> mSpeechQueue;
-    private HashMap<String, SoundResource> mEarcons;
-    private HashMap<String, SoundResource> mUtterances;
-    private MediaPlayer mPlayer;
-    private SpeechItem mCurrentSpeechItem;
-    private HashMap<SpeechItem, Boolean> mKillList; // Used to ensure that in-flight synth calls
-                                                    // are killed when stop is used.
-    private TtsService mSelf;
-
-    private ContentResolver mResolver;
-
-    // lock for the speech queue (mSpeechQueue) and the current speech item (mCurrentSpeechItem)
-    private final ReentrantLock speechQueueLock = new ReentrantLock();
-    private final ReentrantLock synthesizerLock = new ReentrantLock();
-
-    private static SynthProxy sNativeSynth = null;
-    private String currentSpeechEngineSOFile = "";
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        Log.v("TtsService", "TtsService.onCreate()");
-
-        mResolver = getContentResolver();
-
-        currentSpeechEngineSOFile = "";
-        setEngine(getDefaultEngine());
-
-        mSelf = this;
-        mIsSpeaking = false;
-        mSynthBusy = false;
-
-        mEarcons = new HashMap<String, SoundResource>();
-        mUtterances = new HashMap<String, SoundResource>();
-        mCallbacksMap = new HashMap<String, android.speech.tts.ITtsCallback>();
-
-        mSpeechQueue = new ArrayList<SpeechItem>();
-        mPlayer = null;
-        mCurrentSpeechItem = null;
-        mKillList = new HashMap<SpeechItem, Boolean>();
-
-        setDefaultSettings();
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-
-        killAllUtterances();
-
-        // Don't hog the media player
-        cleanUpPlayer();
-
-        if (sNativeSynth != null) {
-            sNativeSynth.shutdown();
-        }
-        sNativeSynth = null;
-
-        // Unregister all callbacks.
-        mCallbacks.kill();
-
-        Log.v(SERVICE_TAG, "onDestroy() completed");
-    }
-
-
-    private int setEngine(String enginePackageName) {
-        String soFilename = "";
-        if (isDefaultEnforced()) {
-            enginePackageName = getDefaultEngine();
-        }
-
-        // Make sure that the engine has been allowed by the user
-        if (!enginePackageName.equals(DEFAULT_SYNTH)) {
-            String[] enabledEngines = android.provider.Settings.Secure.getString(mResolver,
-                    android.provider.Settings.Secure.TTS_ENABLED_PLUGINS).split(" ");
-            boolean isEnabled = false;
-            for (int i=0; i<enabledEngines.length; i++) {
-                if (enabledEngines[i].equals(enginePackageName)) {
-                    isEnabled = true;
-                    break;
-                }
-            }
-            if (!isEnabled) {
-                // Do not use an engine that the user has not enabled; fall back
-                // to using the default synthesizer.
-                enginePackageName = DEFAULT_SYNTH;
-            }
-        }
-
-        // The SVOX TTS is an exception to how the TTS packaging scheme works
-        // because it is part of the system and not a 3rd party add-on; thus
-        // its binary is actually located under /system/lib/
-        if (enginePackageName.equals(DEFAULT_SYNTH)) {
-            soFilename = "/system/lib/libttspico.so";
-        } else {
-            // Find the package
-            Intent intent = new Intent("android.intent.action.START_TTS_ENGINE");
-            intent.setPackage(enginePackageName);
-            ResolveInfo[] enginesArray = new ResolveInfo[0];
-            PackageManager pm = getPackageManager();
-            List <ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, 0);
-            if ((resolveInfos == null) || resolveInfos.isEmpty()) {
-                Log.e(SERVICE_TAG, "Invalid TTS Engine Package: " + enginePackageName);
-                return TextToSpeech.ERROR;
-            }
-            enginesArray = resolveInfos.toArray(enginesArray);
-            // Generate the TTS .so filename from the package
-            ActivityInfo aInfo = enginesArray[0].activityInfo;
-            soFilename = aInfo.name.replace(aInfo.packageName + ".", "") + ".so";
-            soFilename = soFilename.toLowerCase();
-            soFilename = "/data/data/" + aInfo.packageName + "/lib/libtts" + soFilename;
-        }
-
-        if (currentSpeechEngineSOFile.equals(soFilename)) {
-            return TextToSpeech.SUCCESS;
-        }
-
-        File f = new File(soFilename);
-        if (!f.exists()) {
-            Log.e(SERVICE_TAG, "Invalid TTS Binary: " + soFilename);
-            return TextToSpeech.ERROR;
-        }
-
-        if (sNativeSynth != null) {
-            sNativeSynth.stopSync();
-            sNativeSynth.shutdown();
-            sNativeSynth = null;
-        }
-
-        // Load the engineConfig from the plugin if it has any special configuration
-        // to be loaded. By convention, if an engine wants the TTS framework to pass
-        // in any configuration, it must put it into its content provider which has the URI:
-        // content://<packageName>.providers.SettingsProvider
-        // That content provider must provide a Cursor which returns the String that
-        // is to be passed back to the native .so file for the plugin when getString(0) is
-        // called on it.
-        // Note that the TTS framework does not care what this String data is: it is something
-        // that comes from the engine plugin and is consumed only by the engine plugin itself.
-        String engineConfig = "";
-        Cursor c = getContentResolver().query(Uri.parse("content://" + enginePackageName
-                + ".providers.SettingsProvider"), null, null, null, null);
-        if (c != null){
-            c.moveToFirst();
-            engineConfig = c.getString(0);
-            c.close();
-        }
-        sNativeSynth = new SynthProxy(soFilename, engineConfig);
-        currentSpeechEngineSOFile = soFilename;
-        return TextToSpeech.SUCCESS;
-    }
-
-
-
-    private void setDefaultSettings() {
-        setLanguage("", this.getDefaultLanguage(), getDefaultCountry(), getDefaultLocVariant());
-
-        // speech rate
-        setSpeechRate("", getDefaultRate());
-    }
-
-
-    private boolean isDefaultEnforced() {
-        return (android.provider.Settings.Secure.getInt(mResolver,
-                    android.provider.Settings.Secure.TTS_USE_DEFAULTS,
-                    TextToSpeech.Engine.USE_DEFAULTS)
-                == 1 );
-    }
-
-    private String getDefaultEngine() {
-        String defaultEngine = android.provider.Settings.Secure.getString(mResolver,
-                android.provider.Settings.Secure.TTS_DEFAULT_SYNTH);
-        if (defaultEngine == null) {
-            return TextToSpeech.Engine.DEFAULT_SYNTH;
-        } else {
-            return defaultEngine;
-        }
-    }
-
-    private int getDefaultRate() {
-        return android.provider.Settings.Secure.getInt(mResolver,
-                android.provider.Settings.Secure.TTS_DEFAULT_RATE,
-                TextToSpeech.Engine.DEFAULT_RATE);
-    }
-
-    private int getDefaultPitch() {
-        // Pitch is not user settable; the default pitch is always 100.
-        return 100;
-    }
-
-    private String getDefaultLanguage() {
-        String defaultLang = android.provider.Settings.Secure.getString(mResolver,
-                android.provider.Settings.Secure.TTS_DEFAULT_LANG);
-        if (defaultLang == null) {
-            // no setting found, use the current Locale to determine the default language
-            return Locale.getDefault().getISO3Language();
-        } else {
-            return defaultLang;
-        }
-    }
-
-
-    private String getDefaultCountry() {
-        String defaultCountry = android.provider.Settings.Secure.getString(mResolver,
-                android.provider.Settings.Secure.TTS_DEFAULT_COUNTRY);
-        if (defaultCountry == null) {
-            // no setting found, use the current Locale to determine the default country
-            return Locale.getDefault().getISO3Country();
-        } else {
-            return defaultCountry;
-        }
-    }
-
-
-    private String getDefaultLocVariant() {
-        String defaultVar = android.provider.Settings.Secure.getString(mResolver,
-                android.provider.Settings.Secure.TTS_DEFAULT_VARIANT);
-        if (defaultVar == null) {
-            // no setting found, use the current Locale to determine the default variant
-            return Locale.getDefault().getVariant();
-        } else {
-            return defaultVar;
-        }
-    }
-
-
-    private int setSpeechRate(String callingApp, int rate) {
-        int res = TextToSpeech.ERROR;
-        try {
-            if (isDefaultEnforced()) {
-                res = sNativeSynth.setSpeechRate(getDefaultRate());
-            } else {
-                res = sNativeSynth.setSpeechRate(rate);
-            }
-        } catch (NullPointerException e) {
-            // synth will become null during onDestroy()
-            res = TextToSpeech.ERROR;
-        }
-        return res;
-    }
-
-
-    private int setPitch(String callingApp, int pitch) {
-        int res = TextToSpeech.ERROR;
-        try {
-            res = sNativeSynth.setPitch(pitch);
-        } catch (NullPointerException e) {
-            // synth will become null during onDestroy()
-            res = TextToSpeech.ERROR;
-        }
-        return res;
-    }
-
-
-    private int isLanguageAvailable(String lang, String country, String variant) {
-        int res = TextToSpeech.LANG_NOT_SUPPORTED;
-        try {
-            res = sNativeSynth.isLanguageAvailable(lang, country, variant);
-        } catch (NullPointerException e) {
-            // synth will become null during onDestroy()
-            res = TextToSpeech.LANG_NOT_SUPPORTED;
-        }
-        return res;
-    }
-
-
-    private String[] getLanguage() {
-        try {
-            return sNativeSynth.getLanguage();
-        } catch (Exception e) {
-            return null;
-        }
-    }
-
-
-    private int setLanguage(String callingApp, String lang, String country, String variant) {
-        Log.v(SERVICE_TAG, "TtsService.setLanguage(" + lang + ", " + country + ", " + variant + ")");
-        int res = TextToSpeech.ERROR;
-        try {
-            if (isDefaultEnforced()) {
-                res = sNativeSynth.setLanguage(getDefaultLanguage(), getDefaultCountry(),
-                        getDefaultLocVariant());
-            } else {
-                res = sNativeSynth.setLanguage(lang, country, variant);
-            }
-        } catch (NullPointerException e) {
-            // synth will become null during onDestroy()
-            res = TextToSpeech.ERROR;
-        }
-        return res;
-    }
-
-
-    /**
-     * Adds a sound resource to the TTS.
-     *
-     * @param text
-     *            The text that should be associated with the sound resource
-     * @param packageName
-     *            The name of the package which has the sound resource
-     * @param resId
-     *            The resource ID of the sound within its package
-     */
-    private void addSpeech(String callingApp, String text, String packageName, int resId) {
-        mUtterances.put(text, new SoundResource(packageName, resId));
-    }
-
-    /**
-     * Adds a sound resource to the TTS.
-     *
-     * @param text
-     *            The text that should be associated with the sound resource
-     * @param filename
-     *            The filename of the sound resource. This must be a complete
-     *            path like: (/sdcard/mysounds/mysoundbite.mp3).
-     */
-    private void addSpeech(String callingApp, String text, String filename) {
-        mUtterances.put(text, new SoundResource(filename));
-    }
-
-    /**
-     * Adds a sound resource to the TTS as an earcon.
-     *
-     * @param earcon
-     *            The text that should be associated with the sound resource
-     * @param packageName
-     *            The name of the package which has the sound resource
-     * @param resId
-     *            The resource ID of the sound within its package
-     */
-    private void addEarcon(String callingApp, String earcon, String packageName, int resId) {
-        mEarcons.put(earcon, new SoundResource(packageName, resId));
-    }
-
-    /**
-     * Adds a sound resource to the TTS as an earcon.
-     *
-     * @param earcon
-     *            The text that should be associated with the sound resource
-     * @param filename
-     *            The filename of the sound resource. This must be a complete
-     *            path like: (/sdcard/mysounds/mysoundbite.mp3).
-     */
-    private void addEarcon(String callingApp, String earcon, String filename) {
-        mEarcons.put(earcon, new SoundResource(filename));
-    }
-
-    /**
-     * Speaks the given text using the specified queueing mode and parameters.
-     *
-     * @param text
-     *            The text that should be spoken
-     * @param queueMode
-     *            TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances),
-     *            TextToSpeech.TTS_QUEUE_ADD for queued
-     * @param params
-     *            An ArrayList of parameters. This is not implemented for all
-     *            engines.
-     */
-    private int speak(String callingApp, String text, int queueMode, ArrayList<String> params) {
-        // Log.v(SERVICE_TAG, "TTS service received " + text);
-        if (queueMode == TextToSpeech.QUEUE_FLUSH) {
-            stop(callingApp);
-        } else if (queueMode == 2) {
-            stopAll(callingApp);
-        }
-        mSpeechQueue.add(new SpeechItem(callingApp, text, params, SpeechItem.TEXT));
-        if (!mIsSpeaking) {
-            processSpeechQueue();
-        }
-        return TextToSpeech.SUCCESS;
-    }
-
-    /**
-     * Plays the earcon using the specified queueing mode and parameters.
-     *
-     * @param earcon
-     *            The earcon that should be played
-     * @param queueMode
-     *            TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances),
-     *            TextToSpeech.TTS_QUEUE_ADD for queued
-     * @param params
-     *            An ArrayList of parameters. This is not implemented for all
-     *            engines.
-     */
-    private int playEarcon(String callingApp, String earcon, int queueMode,
-            ArrayList<String> params) {
-        if (queueMode == TextToSpeech.QUEUE_FLUSH) {
-            stop(callingApp);
-        } else if (queueMode == 2) {
-            stopAll(callingApp);
-        }
-        mSpeechQueue.add(new SpeechItem(callingApp, earcon, params, SpeechItem.EARCON));
-        if (!mIsSpeaking) {
-            processSpeechQueue();
-        }
-        return TextToSpeech.SUCCESS;
-    }
-
-    /**
-     * Stops all speech output and removes any utterances still in the queue for the calling app.
-     */
-    private int stop(String callingApp) {
-        int result = TextToSpeech.ERROR;
-        boolean speechQueueAvailable = false;
-        try{
-            speechQueueAvailable =
-                    speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, TimeUnit.MILLISECONDS);
-            if (speechQueueAvailable) {
-                Log.i(SERVICE_TAG, "Stopping");
-                for (int i = mSpeechQueue.size() - 1; i > -1; i--){
-                    if (mSpeechQueue.get(i).mCallingApp.equals(callingApp)){
-                        mSpeechQueue.remove(i);
-                    }
-                }
-                if ((mCurrentSpeechItem != null) &&
-                     mCurrentSpeechItem.mCallingApp.equals(callingApp)) {
-                    try {
-                        result = sNativeSynth.stop();
-                    } catch (NullPointerException e1) {
-                        // synth will become null during onDestroy()
-                        result = TextToSpeech.ERROR;
-                    }
-                    mKillList.put(mCurrentSpeechItem, true);
-                    if (mPlayer != null) {
-                        try {
-                            mPlayer.stop();
-                        } catch (IllegalStateException e) {
-                            // Do nothing, the player is already stopped.
-                        }
-                    }
-                    mIsSpeaking = false;
-                    mCurrentSpeechItem = null;
-                } else {
-                    result = TextToSpeech.SUCCESS;
-                }
-                Log.i(SERVICE_TAG, "Stopped");
-            } else {
-                Log.e(SERVICE_TAG, "TTS stop(): queue locked longer than expected");
-                result = TextToSpeech.ERROR;
-            }
-        } catch (InterruptedException e) {
-          Log.e(SERVICE_TAG, "TTS stop: tryLock interrupted");
-          e.printStackTrace();
-        } finally {
-            // This check is needed because finally will always run; even if the
-            // method returns somewhere in the try block.
-            if (speechQueueAvailable) {
-                speechQueueLock.unlock();
-            }
-            return result;
-        }
-    }
-
-
-    /**
-     * Stops all speech output, both rendered to a file and directly spoken, and removes any
-     * utterances still in the queue globally. Files that were being written are deleted.
-     */
-    @SuppressWarnings("finally")
-    private int killAllUtterances() {
-        int result = TextToSpeech.ERROR;
-        boolean speechQueueAvailable = false;
-
-        try {
-            speechQueueAvailable = speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT,
-                    TimeUnit.MILLISECONDS);
-            if (speechQueueAvailable) {
-                // remove every single entry in the speech queue
-                mSpeechQueue.clear();
-
-                // clear the current speech item
-                if (mCurrentSpeechItem != null) {
-                    result = sNativeSynth.stopSync();
-                    mKillList.put(mCurrentSpeechItem, true);
-                    mIsSpeaking = false;
-
-                    // was the engine writing to a file?
-                    if (mCurrentSpeechItem.mType == SpeechItem.TEXT_TO_FILE) {
-                        // delete the file that was being written
-                        if (mCurrentSpeechItem.mFilename != null) {
-                            File tempFile = new File(mCurrentSpeechItem.mFilename);
-                            Log.v(SERVICE_TAG, "Leaving behind " + mCurrentSpeechItem.mFilename);
-                            if (tempFile.exists()) {
-                                Log.v(SERVICE_TAG, "About to delete "
-                                        + mCurrentSpeechItem.mFilename);
-                                if (tempFile.delete()) {
-                                    Log.v(SERVICE_TAG, "file successfully deleted");
-                                }
-                            }
-                        }
-                    }
-
-                    mCurrentSpeechItem = null;
-                }
-            } else {
-                Log.e(SERVICE_TAG, "TTS killAllUtterances(): queue locked longer than expected");
-                result = TextToSpeech.ERROR;
-            }
-        } catch (InterruptedException e) {
-            Log.e(SERVICE_TAG, "TTS killAllUtterances(): tryLock interrupted");
-            result = TextToSpeech.ERROR;
-        } finally {
-            // This check is needed because finally will always run, even if the
-            // method returns somewhere in the try block.
-            if (speechQueueAvailable) {
-                speechQueueLock.unlock();
-            }
-            return result;
-        }
-    }
-
-
-    /**
-     * Stops all speech output and removes any utterances still in the queue globally, except
-     * those intended to be synthesized to file.
-     */
-    private int stopAll(String callingApp) {
-        int result = TextToSpeech.ERROR;
-        boolean speechQueueAvailable = false;
-        try{
-            speechQueueAvailable =
-                    speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, TimeUnit.MILLISECONDS);
-            if (speechQueueAvailable) {
-                for (int i = mSpeechQueue.size() - 1; i > -1; i--){
-                    if (mSpeechQueue.get(i).mType != SpeechItem.TEXT_TO_FILE){
-                        mSpeechQueue.remove(i);
-                    }
-                }
-                if ((mCurrentSpeechItem != null) &&
-                    ((mCurrentSpeechItem.mType != SpeechItem.TEXT_TO_FILE) ||
-                      mCurrentSpeechItem.mCallingApp.equals(callingApp))) {
-                    try {
-                        result = sNativeSynth.stop();
-                    } catch (NullPointerException e1) {
-                        // synth will become null during onDestroy()
-                        result = TextToSpeech.ERROR;
-                    }
-                    mKillList.put(mCurrentSpeechItem, true);
-                    if (mPlayer != null) {
-                        try {
-                            mPlayer.stop();
-                        } catch (IllegalStateException e) {
-                            // Do nothing, the player is already stopped.
-                        }
-                    }
-                    mIsSpeaking = false;
-                    mCurrentSpeechItem = null;
-                } else {
-                    result = TextToSpeech.SUCCESS;
-                }
-                Log.i(SERVICE_TAG, "Stopped all");
-            } else {
-                Log.e(SERVICE_TAG, "TTS stopAll(): queue locked longer than expected");
-                result = TextToSpeech.ERROR;
-            }
-        } catch (InterruptedException e) {
-          Log.e(SERVICE_TAG, "TTS stopAll: tryLock interrupted");
-          e.printStackTrace();
-        } finally {
-            // This check is needed because finally will always run; even if the
-            // method returns somewhere in the try block.
-            if (speechQueueAvailable) {
-                speechQueueLock.unlock();
-            }
-            return result;
-        }
-    }
-
-    public void onCompletion(MediaPlayer arg0) {
-        // mCurrentSpeechItem may become null if it is stopped at the same
-        // time it completes.
-        SpeechItem currentSpeechItemCopy = mCurrentSpeechItem;
-        if (currentSpeechItemCopy != null) {
-            String callingApp = currentSpeechItemCopy.mCallingApp;
-            ArrayList<String> params = currentSpeechItemCopy.mParams;
-            String utteranceId = "";
-            if (params != null) {
-                for (int i = 0; i < params.size() - 1; i = i + 2) {
-                    String param = params.get(i);
-                    if (param.equals(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID)) {
-                        utteranceId = params.get(i + 1);
-                    }
-                }
-            }
-            if (utteranceId.length() > 0) {
-                dispatchUtteranceCompletedCallback(utteranceId, callingApp);
-            }
-        }
-        processSpeechQueue();
-    }
-
-    private int playSilence(String callingApp, long duration, int queueMode,
-            ArrayList<String> params) {
-        if (queueMode == TextToSpeech.QUEUE_FLUSH) {
-            stop(callingApp);
-        }
-        mSpeechQueue.add(new SpeechItem(callingApp, duration, params));
-        if (!mIsSpeaking) {
-            processSpeechQueue();
-        }
-        return TextToSpeech.SUCCESS;
-    }
-
-    private void silence(final SpeechItem speechItem) {
-        class SilenceThread implements Runnable {
-            public void run() {
-                String utteranceId = "";
-                if (speechItem.mParams != null){
-                    for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){
-                        String param = speechItem.mParams.get(i);
-                        if (param.equals(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID)){
-                            utteranceId = speechItem.mParams.get(i+1);
-                        }
-                    }
-                }
-                try {
-                    Thread.sleep(speechItem.mDuration);
-                } catch (InterruptedException e) {
-                    e.printStackTrace();
-                } finally {
-                    if (utteranceId.length() > 0){
-                        dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp);
-                    }
-                    processSpeechQueue();
-                }
-            }
-        }
-        Thread slnc = (new Thread(new SilenceThread()));
-        slnc.setPriority(Thread.MIN_PRIORITY);
-        slnc.start();
-    }
-
-    private void speakInternalOnly(final SpeechItem speechItem) {
-        class SynthThread implements Runnable {
-            public void run() {
-                boolean synthAvailable = false;
-                String utteranceId = "";
-                try {
-                    synthAvailable = synthesizerLock.tryLock();
-                    if (!synthAvailable) {
-                        mSynthBusy = true;
-                        Thread.sleep(100);
-                        Thread synth = (new Thread(new SynthThread()));
-                        synth.start();
-                        mSynthBusy = false;
-                        return;
-                    }
-                    int streamType = DEFAULT_STREAM_TYPE;
-                    String language = "";
-                    String country = "";
-                    String variant = "";
-                    String speechRate = "";
-                    String engine = "";
-                    String pitch = "";
-                    float volume = TextToSpeech.Engine.DEFAULT_VOLUME;
-                    float pan = TextToSpeech.Engine.DEFAULT_PAN;
-                    if (speechItem.mParams != null){
-                        for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){
-                            String param = speechItem.mParams.get(i);
-                            if (param != null) {
-                                if (param.equals(TextToSpeech.Engine.KEY_PARAM_RATE)) {
-                                    speechRate = speechItem.mParams.get(i+1);
-                                } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_LANGUAGE)){
-                                    language = speechItem.mParams.get(i+1);
-                                } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_COUNTRY)){
-                                    country = speechItem.mParams.get(i+1);
-                                } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_VARIANT)){
-                                    variant = speechItem.mParams.get(i+1);
-                                } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID)){
-                                    utteranceId = speechItem.mParams.get(i+1);
-                                } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_STREAM)) {
-                                    try {
-                                        streamType
-                                                = Integer.parseInt(speechItem.mParams.get(i + 1));
-                                    } catch (NumberFormatException e) {
-                                        streamType = DEFAULT_STREAM_TYPE;
-                                    }
-                                } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_ENGINE)) {
-                                    engine = speechItem.mParams.get(i + 1);
-                                } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_PITCH)) {
-                                    pitch = speechItem.mParams.get(i + 1);
-                                } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_VOLUME)) {
-                                    try {
-                                        volume = Float.parseFloat(speechItem.mParams.get(i + 1));
-                                    } catch (NumberFormatException e) {
-                                        volume = TextToSpeech.Engine.DEFAULT_VOLUME;
-                                    }
-                                } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_PAN)) {
-                                    try {
-                                        pan = Float.parseFloat(speechItem.mParams.get(i + 1));
-                                    } catch (NumberFormatException e) {
-                                        pan = TextToSpeech.Engine.DEFAULT_PAN;
-                                    }
-                                }
-                            }
-                        }
-                    }
-                    // Only do the synthesis if it has not been killed by a subsequent utterance.
-                    if (mKillList.get(speechItem) == null) {
-                        if (engine.length() > 0) {
-                            setEngine(engine);
-                        } else {
-                            setEngine(getDefaultEngine());
-                        }
-                        if (language.length() > 0){
-                            setLanguage("", language, country, variant);
-                        } else {
-                            setLanguage("", getDefaultLanguage(), getDefaultCountry(),
-                                    getDefaultLocVariant());
-                        }
-                        if (speechRate.length() > 0){
-                            setSpeechRate("", Integer.parseInt(speechRate));
-                        } else {
-                            setSpeechRate("", getDefaultRate());
-                        }
-                        if (pitch.length() > 0){
-                            setPitch("", Integer.parseInt(pitch));
-                        } else {
-                            setPitch("", getDefaultPitch());
-                        }
-                        try {
-                            sNativeSynth.speak(speechItem.mText, streamType, volume, pan);
-                        } catch (NullPointerException e) {
-                            // synth will become null during onDestroy()
-                            Log.v(SERVICE_TAG, " null synth, can't speak");
-                        }
-                    }
-                } catch (InterruptedException e) {
-                    Log.e(SERVICE_TAG, "TTS speakInternalOnly(): tryLock interrupted");
-                    e.printStackTrace();
-                } finally {
-                    // This check is needed because finally will always run;
-                    // even if the
-                    // method returns somewhere in the try block.
-                    if (utteranceId.length() > 0){
-                        dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp);
-                    }
-                    if (synthAvailable) {
-                        synthesizerLock.unlock();
-                        processSpeechQueue();
-                    }
-                }
-            }
-        }
-        Thread synth = (new Thread(new SynthThread()));
-        synth.setPriority(Thread.MAX_PRIORITY);
-        synth.start();
-    }
-
-    private void synthToFileInternalOnly(final SpeechItem speechItem) {
-        class SynthThread implements Runnable {
-            public void run() {
-                boolean synthAvailable = false;
-                String utteranceId = "";
-                Log.i(SERVICE_TAG, "Synthesizing to " + speechItem.mFilename);
-                try {
-                    synthAvailable = synthesizerLock.tryLock();
-                    if (!synthAvailable) {
-                        synchronized (this) {
-                            mSynthBusy = true;
-                        }
-                        Thread.sleep(100);
-                        Thread synth = (new Thread(new SynthThread()));
-                        synth.start();
-                        synchronized (this) {
-                            mSynthBusy = false;
-                        }
-                        return;
-                    }
-                    String language = "";
-                    String country = "";
-                    String variant = "";
-                    String speechRate = "";
-                    String engine = "";
-                    String pitch = "";
-                    if (speechItem.mParams != null){
-                        for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){
-                            String param = speechItem.mParams.get(i);
-                            if (param != null) {
-                                if (param.equals(TextToSpeech.Engine.KEY_PARAM_RATE)) {
-                                    speechRate = speechItem.mParams.get(i+1);
-                                } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_LANGUAGE)){
-                                    language = speechItem.mParams.get(i+1);
-                                } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_COUNTRY)){
-                                    country = speechItem.mParams.get(i+1);
-                                } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_VARIANT)){
-                                    variant = speechItem.mParams.get(i+1);
-                                } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID)){
-                                    utteranceId = speechItem.mParams.get(i+1);
-                                } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_ENGINE)) {
-                                    engine = speechItem.mParams.get(i + 1);
-                                } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_PITCH)) {
-                                    pitch = speechItem.mParams.get(i + 1);
-                                }
-                            }
-                        }
-                    }
-                    // Only do the synthesis if it has not been killed by a subsequent utterance.
-                    if (mKillList.get(speechItem) == null){
-                        if (engine.length() > 0) {
-                            setEngine(engine);
-                        } else {
-                            setEngine(getDefaultEngine());
-                        }
-                        if (language.length() > 0){
-                            setLanguage("", language, country, variant);
-                        } else {
-                            setLanguage("", getDefaultLanguage(), getDefaultCountry(),
-                                    getDefaultLocVariant());
-                        }
-                        if (speechRate.length() > 0){
-                            setSpeechRate("", Integer.parseInt(speechRate));
-                        } else {
-                            setSpeechRate("", getDefaultRate());
-                        }
-                        if (pitch.length() > 0){
-                            setPitch("", Integer.parseInt(pitch));
-                        } else {
-                            setPitch("", getDefaultPitch());
-                        }
-                        try {
-                            sNativeSynth.synthesizeToFile(speechItem.mText, speechItem.mFilename);
-                        } catch (NullPointerException e) {
-                            // synth will become null during onDestroy()
-                            Log.v(SERVICE_TAG, " null synth, can't synthesize to file");
-                        }
-                    }
-                } catch (InterruptedException e) {
-                    Log.e(SERVICE_TAG, "TTS synthToFileInternalOnly(): tryLock interrupted");
-                    e.printStackTrace();
-                } finally {
-                    // This check is needed because finally will always run;
-                    // even if the
-                    // method returns somewhere in the try block.
-                    if (utteranceId.length() > 0){
-                        dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp);
-                    }
-                    if (synthAvailable) {
-                        synthesizerLock.unlock();
-                        processSpeechQueue();
-                    }
-                }
-            }
-        }
-        Thread synth = (new Thread(new SynthThread()));
-        synth.setPriority(Thread.MAX_PRIORITY);
-        synth.start();
-    }
-
-    private SoundResource getSoundResource(SpeechItem speechItem) {
-        SoundResource sr = null;
-        String text = speechItem.mText;
-        if (speechItem.mType == SpeechItem.SILENCE) {
-            // Do nothing if this is just silence
-        } else if (speechItem.mType == SpeechItem.EARCON) {
-            sr = mEarcons.get(text);
-        } else {
-            sr = mUtterances.get(text);
-        }
-        return sr;
-    }
-
-    private void broadcastTtsQueueProcessingCompleted(){
-        Intent i = new Intent(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED);
-        sendBroadcast(i);
-    }
-
-
-    private void dispatchUtteranceCompletedCallback(String utteranceId, String packageName) {
-        ITtsCallback cb = mCallbacksMap.get(packageName);
-        if (cb == null){
-            return;
-        }
-        Log.v(SERVICE_TAG, "TTS callback: dispatch started");
-        // Broadcast to all clients the new value.
-        final int N = mCallbacks.beginBroadcast();
-        try {
-            cb.utteranceCompleted(utteranceId);
-        } catch (RemoteException e) {
-            // The RemoteCallbackList will take care of removing
-            // the dead object for us.
-        }
-        mCallbacks.finishBroadcast();
-        Log.v(SERVICE_TAG, "TTS callback: dispatch completed to " + N);
-    }
-
-    private SpeechItem splitCurrentTextIfNeeded(SpeechItem currentSpeechItem){
-        if (currentSpeechItem.mText.length() < MAX_SPEECH_ITEM_CHAR_LENGTH){
-            return currentSpeechItem;
-        } else {
-            String callingApp = currentSpeechItem.mCallingApp;
-            ArrayList<SpeechItem> splitItems = new ArrayList<SpeechItem>();
-            int start = 0;
-            int end = start + MAX_SPEECH_ITEM_CHAR_LENGTH - 1;
-            String splitText;
-            SpeechItem splitItem;
-            while (end < currentSpeechItem.mText.length()){
-                splitText = currentSpeechItem.mText.substring(start, end);
-                splitItem = new SpeechItem(callingApp, splitText, null, SpeechItem.TEXT);
-                splitItems.add(splitItem);
-                start = end;
-                end = start + MAX_SPEECH_ITEM_CHAR_LENGTH - 1;
-            }
-            splitText = currentSpeechItem.mText.substring(start);
-            splitItem = new SpeechItem(callingApp, splitText, null, SpeechItem.TEXT);
-            splitItems.add(splitItem);
-            mSpeechQueue.remove(0);
-            for (int i = splitItems.size() - 1; i >= 0; i--){
-                mSpeechQueue.add(0, splitItems.get(i));
-            }
-            return mSpeechQueue.get(0);
-        }
-    }
-
-    private void processSpeechQueue() {
-        boolean speechQueueAvailable = false;
-        synchronized (this) {
-            if (mSynthBusy){
-                // There is already a synth thread waiting to run.
-                return;
-            }
-        }
-        try {
-            speechQueueAvailable =
-                    speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, TimeUnit.MILLISECONDS);
-            if (!speechQueueAvailable) {
-                Log.e(SERVICE_TAG, "processSpeechQueue - Speech queue is unavailable.");
-                return;
-            }
-            if (mSpeechQueue.size() < 1) {
-                mIsSpeaking = false;
-                mKillList.clear();
-                broadcastTtsQueueProcessingCompleted();
-                return;
-            }
-
-            mCurrentSpeechItem = mSpeechQueue.get(0);
-            mIsSpeaking = true;
-            SoundResource sr = getSoundResource(mCurrentSpeechItem);
-            // Synth speech as needed - synthesizer should call
-            // processSpeechQueue to continue running the queue
-            // Log.v(SERVICE_TAG, "TTS processing: " + mCurrentSpeechItem.mText);
-            if (sr == null) {
-                if (mCurrentSpeechItem.mType == SpeechItem.TEXT) {
-                    mCurrentSpeechItem = splitCurrentTextIfNeeded(mCurrentSpeechItem);
-                    speakInternalOnly(mCurrentSpeechItem);
-                } else if (mCurrentSpeechItem.mType == SpeechItem.TEXT_TO_FILE) {
-                    synthToFileInternalOnly(mCurrentSpeechItem);
-                } else {
-                    // This is either silence or an earcon that was missing
-                    silence(mCurrentSpeechItem);
-                }
-            } else {
-                cleanUpPlayer();
-                if (sr.mSourcePackageName == PKGNAME) {
-                    // Utterance is part of the TTS library
-                    mPlayer = MediaPlayer.create(this, sr.mResId);
-                } else if (sr.mSourcePackageName != null) {
-                    // Utterance is part of the app calling the library
-                    Context ctx;
-                    try {
-                        ctx = this.createPackageContext(sr.mSourcePackageName, 0);
-                    } catch (NameNotFoundException e) {
-                        e.printStackTrace();
-                        mSpeechQueue.remove(0); // Remove it from the queue and
-                        // move on
-                        mIsSpeaking = false;
-                        return;
-                    }
-                    mPlayer = MediaPlayer.create(ctx, sr.mResId);
-                } else {
-                    // Utterance is coming from a file
-                    mPlayer = MediaPlayer.create(this, Uri.parse(sr.mFilename));
-                }
-
-                // Check if Media Server is dead; if it is, clear the queue and
-                // give up for now - hopefully, it will recover itself.
-                if (mPlayer == null) {
-                    mSpeechQueue.clear();
-                    mIsSpeaking = false;
-                    return;
-                }
-                mPlayer.setOnCompletionListener(this);
-                try {
-                    mPlayer.setAudioStreamType(getStreamTypeFromParams(mCurrentSpeechItem.mParams));
-                    mPlayer.start();
-                } catch (IllegalStateException e) {
-                    mSpeechQueue.clear();
-                    mIsSpeaking = false;
-                    cleanUpPlayer();
-                    return;
-                }
-            }
-            if (mSpeechQueue.size() > 0) {
-                mSpeechQueue.remove(0);
-            }
-        } catch (InterruptedException e) {
-          Log.e(SERVICE_TAG, "TTS processSpeechQueue: tryLock interrupted");
-          e.printStackTrace();
-        } finally {
-            // This check is needed because finally will always run; even if the
-            // method returns somewhere in the try block.
-            if (speechQueueAvailable) {
-                speechQueueLock.unlock();
-            }
-        }
-    }
-
-    private int getStreamTypeFromParams(ArrayList<String> paramList) {
-        int streamType = DEFAULT_STREAM_TYPE;
-        if (paramList == null) {
-            return streamType;
-        }
-        for (int i = 0; i < paramList.size() - 1; i = i + 2) {
-            String param = paramList.get(i);
-            if ((param != null) && (param.equals(TextToSpeech.Engine.KEY_PARAM_STREAM))) {
-                try {
-                    streamType = Integer.parseInt(paramList.get(i + 1));
-                } catch (NumberFormatException e) {
-                    streamType = DEFAULT_STREAM_TYPE;
-                }
-            }
-        }
-        return streamType;
-    }
-
-    private void cleanUpPlayer() {
-        if (mPlayer != null) {
-            mPlayer.release();
-            mPlayer = null;
-        }
-    }
-
-    /**
-     * Synthesizes the given text to a file using the specified parameters.
-     *
-     * @param text
-     *            The String of text that should be synthesized
-     * @param params
-     *            An ArrayList of parameters. The first element of this array
-     *            controls the type of voice to use.
-     * @param filename
-     *            The string that gives the full output filename; it should be
-     *            something like "/sdcard/myappsounds/mysound.wav".
-     * @return A boolean that indicates if the synthesis can be started
-     */
-    private boolean synthesizeToFile(String callingApp, String text, ArrayList<String> params,
-            String filename) {
-        // Don't allow a filename that is too long
-        if (filename.length() > MAX_FILENAME_LENGTH) {
-            return false;
-        }
-        // Don't allow anything longer than the max text length; since this
-        // is synthing to a file, don't even bother splitting it.
-        if (text.length() >= MAX_SPEECH_ITEM_CHAR_LENGTH){
-            return false;
-        }
-        // Check that the output file can be created
-        try {
-            File tempFile = new File(filename);
-            if (tempFile.exists()) {
-                Log.v("TtsService", "File " + filename + " exists, deleting.");
-                tempFile.delete();
-            }
-            if (!tempFile.createNewFile()) {
-                Log.e("TtsService", "Unable to synthesize to file: can't create " + filename);
-                return false;
-            }
-            tempFile.delete();
-        } catch (IOException e) {
-            Log.e("TtsService", "Can't create " + filename + " due to exception " + e);
-            return false;
-        }
-        mSpeechQueue.add(new SpeechItem(callingApp, text, params, SpeechItem.TEXT_TO_FILE, filename));
-        if (!mIsSpeaking) {
-            processSpeechQueue();
-        }
-        return true;
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        if (ACTION.equals(intent.getAction())) {
-            for (String category : intent.getCategories()) {
-                if (category.equals(CATEGORY)) {
-                    return mBinder;
-                }
-            }
-        }
-        return null;
-    }
-
-    private final android.speech.tts.ITts.Stub mBinder = new Stub() {
-
-        public int registerCallback(String packageName, ITtsCallback cb) {
-            if (cb != null) {
-                mCallbacks.register(cb);
-                mCallbacksMap.put(packageName, cb);
-                return TextToSpeech.SUCCESS;
-            }
-            return TextToSpeech.ERROR;
-        }
-
-        public int unregisterCallback(String packageName, ITtsCallback cb) {
-            if (cb != null) {
-                mCallbacksMap.remove(packageName);
-                mCallbacks.unregister(cb);
-                return TextToSpeech.SUCCESS;
-            }
-            return TextToSpeech.ERROR;
-        }
-
-        /**
-         * Speaks the given text using the specified queueing mode and
-         * parameters.
-         *
-         * @param text
-         *            The text that should be spoken
-         * @param queueMode
-         *            TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances)
-         *            TextToSpeech.TTS_QUEUE_ADD for queued
-         * @param params
-         *            An ArrayList of parameters. The first element of this
-         *            array controls the type of voice to use.
-         */
-        public int speak(String callingApp, String text, int queueMode, String[] params) {
-            ArrayList<String> speakingParams = new ArrayList<String>();
-            if (params != null) {
-                speakingParams = new ArrayList<String>(Arrays.asList(params));
-            }
-            return mSelf.speak(callingApp, text, queueMode, speakingParams);
-        }
-
-        /**
-         * Plays the earcon using the specified queueing mode and parameters.
-         *
-         * @param earcon
-         *            The earcon that should be played
-         * @param queueMode
-         *            TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances)
-         *            TextToSpeech.TTS_QUEUE_ADD for queued
-         * @param params
-         *            An ArrayList of parameters.
-         */
-        public int playEarcon(String callingApp, String earcon, int queueMode, String[] params) {
-            ArrayList<String> speakingParams = new ArrayList<String>();
-            if (params != null) {
-                speakingParams = new ArrayList<String>(Arrays.asList(params));
-            }
-            return mSelf.playEarcon(callingApp, earcon, queueMode, speakingParams);
-        }
-
-        /**
-         * Plays the silence using the specified queueing mode and parameters.
-         *
-         * @param duration
-         *            The duration of the silence that should be played
-         * @param queueMode
-         *            TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances)
-         *            TextToSpeech.TTS_QUEUE_ADD for queued
-         * @param params
-         *            An ArrayList of parameters.
-         */
-        public int playSilence(String callingApp, long duration, int queueMode, String[] params) {
-            ArrayList<String> speakingParams = new ArrayList<String>();
-            if (params != null) {
-                speakingParams = new ArrayList<String>(Arrays.asList(params));
-            }
-            return mSelf.playSilence(callingApp, duration, queueMode, speakingParams);
-        }
-
-        /**
-         * Stops all speech output and removes any utterances still in the
-         * queue.
-         */
-        public int stop(String callingApp) {
-            return mSelf.stop(callingApp);
-        }
-
-        /**
-         * Returns whether or not the TTS is speaking.
-         *
-         * @return Boolean to indicate whether or not the TTS is speaking
-         */
-        public boolean isSpeaking() {
-            return (mSelf.mIsSpeaking && (mSpeechQueue.size() < 1));
-        }
-
-        /**
-         * Adds a sound resource to the TTS.
-         *
-         * @param text
-         *            The text that should be associated with the sound resource
-         * @param packageName
-         *            The name of the package which has the sound resource
-         * @param resId
-         *            The resource ID of the sound within its package
-         */
-        public void addSpeech(String callingApp, String text, String packageName, int resId) {
-            mSelf.addSpeech(callingApp, text, packageName, resId);
-        }
-
-        /**
-         * Adds a sound resource to the TTS.
-         *
-         * @param text
-         *            The text that should be associated with the sound resource
-         * @param filename
-         *            The filename of the sound resource. This must be a
-         *            complete path like: (/sdcard/mysounds/mysoundbite.mp3).
-         */
-        public void addSpeechFile(String callingApp, String text, String filename) {
-            mSelf.addSpeech(callingApp, text, filename);
-        }
-
-        /**
-         * Adds a sound resource to the TTS as an earcon.
-         *
-         * @param earcon
-         *            The text that should be associated with the sound resource
-         * @param packageName
-         *            The name of the package which has the sound resource
-         * @param resId
-         *            The resource ID of the sound within its package
-         */
-        public void addEarcon(String callingApp, String earcon, String packageName, int resId) {
-            mSelf.addEarcon(callingApp, earcon, packageName, resId);
-        }
-
-        /**
-         * Adds a sound resource to the TTS as an earcon.
-         *
-         * @param earcon
-         *            The text that should be associated with the sound resource
-         * @param filename
-         *            The filename of the sound resource. This must be a
-         *            complete path like: (/sdcard/mysounds/mysoundbite.mp3).
-         */
-        public void addEarconFile(String callingApp, String earcon, String filename) {
-            mSelf.addEarcon(callingApp, earcon, filename);
-        }
-
-        /**
-         * Sets the speech rate for the TTS. Note that this will only have an
-         * effect on synthesized speech; it will not affect pre-recorded speech.
-         *
-         * @param speechRate
-         *            The speech rate that should be used
-         */
-        public int setSpeechRate(String callingApp, int speechRate) {
-            return mSelf.setSpeechRate(callingApp, speechRate);
-        }
-
-        /**
-         * Sets the pitch for the TTS. Note that this will only have an
-         * effect on synthesized speech; it will not affect pre-recorded speech.
-         *
-         * @param pitch
-         *            The pitch that should be used for the synthesized voice
-         */
-        public int setPitch(String callingApp, int pitch) {
-            return mSelf.setPitch(callingApp, pitch);
-        }
-
-        /**
-         * Returns the level of support for the specified language.
-         *
-         * @param lang  the three letter ISO language code.
-         * @param country  the three letter ISO country code.
-         * @param variant  the variant code associated with the country and language pair.
-         * @return one of TTS_LANG_NOT_SUPPORTED, TTS_LANG_MISSING_DATA, TTS_LANG_AVAILABLE,
-         *      TTS_LANG_COUNTRY_AVAILABLE, TTS_LANG_COUNTRY_VAR_AVAILABLE as defined in
-         *      android.speech.tts.TextToSpeech.
-         */
-        public int isLanguageAvailable(String lang, String country, String variant,
-                String[] params) {
-            for (int i = 0; i < params.length - 1; i = i + 2){
-                String param = params[i];
-                if (param != null) {
-                    if (param.equals(TextToSpeech.Engine.KEY_PARAM_ENGINE)) {
-                        mSelf.setEngine(params[i + 1]);
-                        break;
-                    }
-                }
-            }
-            return mSelf.isLanguageAvailable(lang, country, variant);
-        }
-
-        /**
-         * Returns the currently set language / country / variant strings representing the
-         * language used by the TTS engine.
-         * @return null is no language is set, or an array of 3 string containing respectively
-         *      the language, country and variant.
-         */
-        public String[] getLanguage() {
-            return mSelf.getLanguage();
-        }
-
-        /**
-         * Sets the speech rate for the TTS, which affects the synthesized voice.
-         *
-         * @param lang  the three letter ISO language code.
-         * @param country  the three letter ISO country code.
-         * @param variant  the variant code associated with the country and language pair.
-         */
-        public int setLanguage(String callingApp, String lang, String country, String variant) {
-            return mSelf.setLanguage(callingApp, lang, country, variant);
-        }
-
-        /**
-         * Synthesizes the given text to a file using the specified
-         * parameters.
-         *
-         * @param text
-         *            The String of text that should be synthesized
-         * @param params
-         *            An ArrayList of parameters. The first element of this
-         *            array controls the type of voice to use.
-         * @param filename
-         *            The string that gives the full output filename; it should
-         *            be something like "/sdcard/myappsounds/mysound.wav".
-         * @return A boolean that indicates if the synthesis succeeded
-         */
-        public boolean synthesizeToFile(String callingApp, String text, String[] params,
-                String filename) {
-            ArrayList<String> speakingParams = new ArrayList<String>();
-            if (params != null) {
-                speakingParams = new ArrayList<String>(Arrays.asList(params));
-            }
-            return mSelf.synthesizeToFile(callingApp, text, speakingParams, filename);
-        }
-
-        /**
-         * Sets the speech synthesis engine for the TTS by specifying its packagename
-         *
-         * @param packageName  the packageName of the speech synthesis engine (ie, "com.svox.pico")
-         *
-         * @return SUCCESS or ERROR as defined in android.speech.tts.TextToSpeech.
-         */
-        public int setEngineByPackageName(String packageName) {
-            return mSelf.setEngine(packageName);
-        }
-
-        /**
-         * Returns the packagename of the default speech synthesis engine.
-         *
-         * @return Packagename of the TTS engine that the user has chosen as their default.
-         */
-        public String getDefaultEngine() {
-            return mSelf.getDefaultEngine();
-        }
-
-        /**
-         * Returns whether or not the user is forcing their defaults to override the
-         * Text-To-Speech settings set by applications.
-         *
-         * @return Whether or not defaults are enforced.
-         */
-        public boolean areDefaultsEnforced() {
-            return mSelf.isDefaultEnforced();
-        }
-
-    };
-
-}
diff --git a/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java b/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java
index 5b80a93..ca56c4f 100644
--- a/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java
@@ -28,6 +28,7 @@
 import android.os.CountDownTimer;
 import android.os.SystemClock;
 import android.telephony.TelephonyManager;
+import android.text.InputType;
 import android.text.method.DigitsKeyListener;
 import android.text.method.TextKeyListener;
 import android.util.Log;
@@ -70,6 +71,7 @@
     private CountDownTimer mCountdownTimer;
 
     private StatusView mStatusView;
+    private final boolean mUseSystemIME = true; // TODO: Make configurable
 
     // To avoid accidental lockout due to events while the device in in the pocket, ignore
     // any passwords with length less than or equal to this length.
@@ -108,14 +110,23 @@
         mPasswordEntry.setOnEditorActionListener(this);
         mPasswordEntry.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
-                if (mIsAlpha && !isPhysicalKbShowing) {
-                    mKeyboardViewAlpha.setVisibility(
-                            mKeyboardViewAlpha.getVisibility() == View.VISIBLE
-                            ? View.GONE : View.VISIBLE);
-                    mCallback.pokeWakelock();
+                if (mIsAlpha && !isPhysicalKbShowing && !mUseSystemIME) {
+                    // Toggle visibility of alpha keyboard
+                    final boolean visible = mKeyboardViewAlpha.getVisibility() == View.VISIBLE;
+                    mKeyboardViewAlpha.setVisibility(visible ? View.GONE : View.VISIBLE);
                 }
+                mCallback.pokeWakelock();
             }
         });
+
+        // We don't currently use the IME for PIN mode, but this will make it work if we ever do...
+        if (!mIsAlpha) {
+            mPasswordEntry.setInputType(InputType.TYPE_CLASS_NUMBER
+                    | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
+        } else {
+            mPasswordEntry.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
+        }
+
         mEmergencyCallButton = (Button) findViewById(R.id.emergencyCall);
         mEmergencyCallButton.setOnClickListener(this);
         mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
@@ -176,7 +187,7 @@
 
     /** {@inheritDoc} */
     public boolean needsInput() {
-        return false;
+        return mUseSystemIME && mIsAlpha;
     }
 
     /** {@inheritDoc} */
@@ -295,7 +306,7 @@
 
     public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
         // Check if this was the result of hitting the enter key
-        if (actionId == EditorInfo.IME_NULL) {
+        if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE) {
             verifyPasswordAndUnlock();
             return true;
         }
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index 1455764..7028772 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -1399,6 +1399,24 @@
         }
     }
 
+    public InputMethodSubtype getLastInputMethodSubtype() {
+        synchronized (mMethodMap) {
+            final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
+            // TODO: Handle the case of the last IME with no subtypes
+            if (lastIme == null || TextUtils.isEmpty(lastIme.first)
+                    || TextUtils.isEmpty(lastIme.second)) return null;
+            final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
+            if (lastImi == null) return null;
+            try {
+                final int lastSubtypeHash = Integer.valueOf(lastIme.second);
+                return lastImi.getSubtypeAt(getSubtypeIdFromHashCode(
+                        lastImi, lastSubtypeHash));
+            } catch (NumberFormatException e) {
+                return null;
+            }
+        }
+    }
+
     private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
         synchronized (mMethodMap) {
             if (token == null) {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 768d990..31b7f86 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -3892,18 +3892,17 @@
             }
             for (int i=0; i<intents.length; i++) {
                 Intent intent = intents[i];
-                if (intent == null) {
-                    throw new IllegalArgumentException("Null intent at index " + i);
+                if (intent != null) {
+                    if (intent.hasFileDescriptors()) {
+                        throw new IllegalArgumentException("File descriptors passed in Intent");
+                    }
+                    if (type == INTENT_SENDER_BROADCAST &&
+                            (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) {
+                        throw new IllegalArgumentException(
+                                "Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
+                    }
+                    intents[i] = new Intent(intent);
                 }
-                if (intent.hasFileDescriptors()) {
-                    throw new IllegalArgumentException("File descriptors passed in Intent");
-                }
-                if (type == INTENT_SENDER_BROADCAST &&
-                        (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) {
-                    throw new IllegalArgumentException(
-                            "Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
-                }
-                intents[i] = new Intent(intent);
             }
             if (resolvedTypes != null && resolvedTypes.length != intents.length) {
                 throw new IllegalArgumentException(
diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java
index 963a691..b4fdc9f 100644
--- a/services/java/com/android/server/am/BatteryStatsService.java
+++ b/services/java/com/android/server/am/BatteryStatsService.java
@@ -449,6 +449,7 @@
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         boolean isCheckin = false;
+        boolean noOutput = false;
         if (args != null) {
             for (String arg : args) {
                 if ("--checkin".equals(arg)) {
@@ -457,10 +458,22 @@
                     synchronized (mStats) {
                         mStats.resetAllStatsLocked();
                         pw.println("Battery stats reset.");
+                        noOutput = true;
                     }
+                } else if ("--write".equals(arg)) {
+                    synchronized (mStats) {
+                        mStats.writeSyncLocked();
+                        pw.println("Battery stats written.");
+                        noOutput = true;
+                    }
+                } else {
+                    pw.println("Unknown option: " + arg);
                 }
             }
         }
+        if (noOutput) {
+            return;
+        }
         if (isCheckin) {
             List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(0);
             synchronized (mStats) {
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index ea283c606..2f3a144 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -2553,22 +2553,9 @@
         LOGE("createGraphicBuffer: unable to create GraphicBuffer");
         return 0;
     }
-    Mutex::Autolock _l(mLock);
-    mBuffers.add(graphicBuffer);
     return graphicBuffer;
 }
 
-void GraphicBufferAlloc::freeAllGraphicBuffersExcept(int bufIdx) {
-    Mutex::Autolock _l(mLock);
-    if (bufIdx >= 0 && size_t(bufIdx) < mBuffers.size()) {
-        sp<GraphicBuffer> b(mBuffers[bufIdx]);
-        mBuffers.clear();
-        mBuffers.add(b);
-    } else {
-        mBuffers.clear();
-    }
-}
-
 // ---------------------------------------------------------------------------
 
 GraphicPlane::GraphicPlane()
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 0964848..8d43157 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -125,14 +125,8 @@
 public:
     GraphicBufferAlloc();
     virtual ~GraphicBufferAlloc();
-
     virtual sp<GraphicBuffer> createGraphicBuffer(uint32_t w, uint32_t h,
         PixelFormat format, uint32_t usage);
-    virtual void freeAllGraphicBuffersExcept(int bufIdx);
-
-private:
-    Vector<sp<GraphicBuffer> > mBuffers;
-    Mutex mLock;
 };
 
 // ---------------------------------------------------------------------------
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
index a2c08ed..3280c44 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
@@ -97,7 +97,6 @@
     protected static final int EVENT_TRY_SETUP_DATA = 5;
     protected static final int EVENT_DATA_STATE_CHANGED = 6;
     protected static final int EVENT_POLL_PDP = 7;
-    protected static final int EVENT_GET_PDP_LIST_COMPLETE = 11;
     protected static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE = 12;
     protected static final int EVENT_VOICE_CALL_STARTED = 14;
     protected static final int EVENT_VOICE_CALL_ENDED = 15;
@@ -408,7 +407,7 @@
     }
 
     /** TODO: See if we can remove */
-    public String getActiveApnString() {
+    public String getActiveApnString(String apnType) {
         String result = null;
         if (mActiveApn != null) {
             result = mActiveApn.apn;
@@ -466,6 +465,8 @@
     protected abstract void onVoiceCallEnded();
     protected abstract void onCleanUpConnection(boolean tearDown, int apnId, String reason);
     protected abstract void onCleanUpAllConnections(String cause);
+    protected abstract boolean isDataPossible();
+    protected abstract boolean isDataPossible(String apnType);
 
     @Override
     public void handleMessage(Message msg) {
@@ -719,29 +720,6 @@
         notifyOffApnsOfAvailability(reason, isDataPossible());
     }
 
-    /**
-     * The only circumstances under which we report that data connectivity is not
-     * possible are
-     * <ul>
-     * <li>Data is disallowed (roaming, power state, voice call, etc).</li>
-     * <li>The current data state is {@code DISCONNECTED} for a reason other than
-     * having explicitly disabled connectivity. In other words, data is not available
-     * because the phone is out of coverage or some like reason.</li>
-     * </ul>
-     * @return {@code true} if data connectivity is possible, {@code false} otherwise.
-     */
-    protected boolean isDataPossible() {
-        boolean dataAllowed = isDataAllowed();
-        boolean anyDataEnabled = getAnyDataEnabled();
-        boolean possible = (dataAllowed
-                && !(anyDataEnabled && (mState == State.FAILED || mState == State.IDLE)));
-        if (!possible && DBG) {
-            log("isDataPossible() " + possible + ", dataAllowed=" + dataAllowed +
-                    " anyDataEnabled=" + anyDataEnabled + " dataState=" + mState);
-        }
-        return possible;
-    }
-
     public boolean isApnTypeEnabled(String apnType) {
         if (apnType == null) {
             return false;
diff --git a/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java
index 83db3d1..910905a 100644
--- a/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -114,8 +114,8 @@
         try {
             mRegistry.notifyDataConnection(
                     convertDataState(state),
-                    sender.isDataConnectivityPossible(), reason,
-                    sender.getActiveApnHost(),
+                    sender.isDataConnectivityPossible(apnType), reason,
+                    sender.getActiveApnHost(apnType),
                     apnType,
                     linkProperties,
                     linkCapabilities,
diff --git a/telephony/java/com/android/internal/telephony/Phone.java b/telephony/java/com/android/internal/telephony/Phone.java
index 9f16d31..488794f 100644
--- a/telephony/java/com/android/internal/telephony/Phone.java
+++ b/telephony/java/com/android/internal/telephony/Phone.java
@@ -333,7 +333,7 @@
      * Returns string for the active APN host.
      *  @return type as a string or null if none.
      */
-    String getActiveApnHost();
+    String getActiveApnHost(String apnType);
 
     /**
      * Return the LinkProperties for the named apn or null if not available
@@ -1375,6 +1375,11 @@
     boolean isDataConnectivityPossible();
 
     /**
+     * Report on whether data connectivity is allowed for an APN.
+     */
+    boolean isDataConnectivityPossible(String apnType);
+
+    /**
      * Retrieves the unique device ID, e.g., IMEI for GSM phones and MEID for CDMA phones.
      */
     String getDeviceId();
diff --git a/telephony/java/com/android/internal/telephony/PhoneBase.java b/telephony/java/com/android/internal/telephony/PhoneBase.java
index 3224995..5a77da7 100644
--- a/telephony/java/com/android/internal/telephony/PhoneBase.java
+++ b/telephony/java/com/android/internal/telephony/PhoneBase.java
@@ -977,8 +977,8 @@
         return mDataConnectionTracker.getActiveApnTypes();
     }
 
-    public String getActiveApnHost() {
-        return mDataConnectionTracker.getActiveApnString();
+    public String getActiveApnHost(String apnType) {
+        return mDataConnectionTracker.getActiveApnString(apnType);
     }
 
     public LinkProperties getLinkProperties(String apnType) {
@@ -1001,6 +1001,11 @@
         return ((mDataConnectionTracker != null) && (mDataConnectionTracker.isDataPossible()));
     }
 
+    public boolean isDataConnectivityPossible(String apnType) {
+        return ((mDataConnectionTracker != null) &&
+                (mDataConnectionTracker.isDataPossible(apnType)));
+    }
+
     /**
      * simulateDataConnection
      *
diff --git a/telephony/java/com/android/internal/telephony/PhoneFactory.java b/telephony/java/com/android/internal/telephony/PhoneFactory.java
index ab0bb63..a04623f 100644
--- a/telephony/java/com/android/internal/telephony/PhoneFactory.java
+++ b/telephony/java/com/android/internal/telephony/PhoneFactory.java
@@ -106,10 +106,32 @@
                         Settings.Secure.PREFERRED_NETWORK_MODE, preferredNetworkMode);
                 Log.i(LOG_TAG, "Network Mode set to " + Integer.toString(networkMode));
 
-                //Get preferredNetworkMode from Settings.System
-                int cdmaSubscription = Settings.Secure.getInt(context.getContentResolver(),
-                        Settings.Secure.PREFERRED_CDMA_SUBSCRIPTION, preferredCdmaSubscription);
-                Log.i(LOG_TAG, "Cdma Subscription set to " + Integer.toString(cdmaSubscription));
+                // Get cdmaSubscription
+                // TODO: Change when the ril will provides a way to know at runtime
+                //       the configuration, bug 4202572. And the ril issues the
+                //       RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED, bug 4295439.
+                int cdmaSubscription;
+                int lteOnCdma = SystemProperties.getInt(
+                                TelephonyProperties.PROPERTY_NETWORK_LTE_ON_CDMA, -1);
+                switch (lteOnCdma) {
+                    case 0:
+                        cdmaSubscription = RILConstants.SUBSCRIPTION_FROM_NV;
+                        Log.i(LOG_TAG, "lteOnCdma is 0 use SUBSCRIPTION_FROM_NV");
+                        break;
+                    case 1:
+                        cdmaSubscription = RILConstants.SUBSCRIPTION_FROM_RUIM;
+                        Log.i(LOG_TAG, "lteOnCdma is 1 use SUBSCRIPTION_FROM_RUIM");
+                        break;
+                    case -1:
+                    default:
+                        //Get cdmaSubscription mode from Settings.System
+                        cdmaSubscription = Settings.Secure.getInt(context.getContentResolver(),
+                                Settings.Secure.PREFERRED_CDMA_SUBSCRIPTION,
+                                preferredCdmaSubscription);
+                        Log.i(LOG_TAG, "lteOnCdma not set, using PREFERRED_CDMA_SUBSCRIPTION");
+                        break;
+                }
+                Log.i(LOG_TAG, "Cdma Subscription set to " + cdmaSubscription);
 
                 //reads the system properties and makes commandsinterface
                 sCommandsInterface = new RIL(context, networkMode, cdmaSubscription);
@@ -156,7 +178,10 @@
         case RILConstants.NETWORK_MODE_GSM_UMTS:
             return Phone.PHONE_TYPE_GSM;
 
+        // Use CDMA Phone for the global mode including CDMA
         case RILConstants.NETWORK_MODE_GLOBAL:
+        case RILConstants.NETWORK_MODE_LTE_CDMA_EVDO:
+        case RILConstants.NETWORK_MODE_LTE_CMDA_EVDO_GSM_WCDMA:
             return Phone.PHONE_TYPE_CDMA;
 
         case RILConstants.NETWORK_MODE_LTE_ONLY:
diff --git a/telephony/java/com/android/internal/telephony/PhoneProxy.java b/telephony/java/com/android/internal/telephony/PhoneProxy.java
index 49497b4..5e506b3 100644
--- a/telephony/java/com/android/internal/telephony/PhoneProxy.java
+++ b/telephony/java/com/android/internal/telephony/PhoneProxy.java
@@ -208,8 +208,8 @@
         return mActivePhone.getActiveApnTypes();
     }
 
-    public String getActiveApnHost() {
-        return mActivePhone.getActiveApnHost();
+    public String getActiveApnHost(String apnType) {
+        return mActivePhone.getActiveApnHost(apnType);
     }
 
     public LinkProperties getLinkProperties(String apnType) {
@@ -661,6 +661,10 @@
         return mActivePhone.isDataConnectivityPossible();
     }
 
+    public boolean isDataConnectivityPossible(String apnType) {
+        return mActivePhone.isDataConnectivityPossible(apnType);
+    }
+
     public String getDeviceId() {
         return mActivePhone.getDeviceId();
     }
diff --git a/telephony/java/com/android/internal/telephony/RIL.java b/telephony/java/com/android/internal/telephony/RIL.java
index 863daef..c052e5142 100644
--- a/telephony/java/com/android/internal/telephony/RIL.java
+++ b/telephony/java/com/android/internal/telephony/RIL.java
@@ -234,9 +234,6 @@
     // WAKE_LOCK_TIMEOUT occurs.
     int mRequestMessagesWaiting;
 
-    // Is this the first radio state change?
-    private boolean mInitialRadioStateChange = true;
-
     //I'd rather this be LinkedList or something
     ArrayList<RILRequest> mRequestsList = new ArrayList<RILRequest>();
 
@@ -613,11 +610,6 @@
 
 
     //***** Constructors
-    public
-    RIL(Context context) {
-        this(context, RILConstants.PREFERRED_NETWORK_MODE,
-                RILConstants.PREFERRED_CDMA_SUBSCRIPTION);
-    }
 
     public RIL(Context context, int networkMode, int cdmaSubscription) {
         super(context);
@@ -913,10 +905,7 @@
     getIMSI(Message result) {
         RILRequest rr = RILRequest.obtain(RIL_REQUEST_GET_IMSI, result);
 
-        if (RILJ_LOGD) riljLog(rr.serialString() +
-                              "> getIMSI:RIL_REQUEST_GET_IMSI " +
-                              RIL_REQUEST_GET_IMSI +
-                              " " + requestToString(rr.mRequest));
+        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
 
         send(rr);
     }
@@ -1394,24 +1383,6 @@
 
     public void
     setRadioPower(boolean on, Message result) {
-        //if radio is OFF set preferred NW type and cmda subscription
-        if(mInitialRadioStateChange) {
-            synchronized (mStateMonitor) {
-                if (!mState.isOn()) {
-                    setPreferredNetworkType(mNetworkMode, null);
-
-                    RILRequest rrCs = RILRequest.obtain(
-                                   RIL_REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE, null);
-                    rrCs.mp.writeInt(1);
-                    rrCs.mp.writeInt(mCdmaSubscription);
-                    if (RILJ_LOGD) {
-                        riljLog(rrCs.serialString() + "> "
-                                + requestToString(rrCs.mRequest) + " : " + mCdmaSubscription);
-                    }
-                    send(rrCs);
-                }
-            }
-        }
         RILRequest rr = RILRequest.obtain(RIL_REQUEST_RADIO_POWER, result);
 
         rr.mp.writeInt(1);
@@ -2058,26 +2029,7 @@
     }
 
     private void switchToRadioState(RadioState newState) {
-
-        if (mInitialRadioStateChange) {
-            if (newState.isOn()) {
-                /* If this is our first notification, make sure the radio
-                 * is powered off.  This gets the radio into a known state,
-                 * since it's possible for the phone proc to have restarted
-                 * (eg, if it or the runtime crashed) without the RIL
-                 * and/or radio knowing.
-                 */
-                if (RILJ_LOGD) Log.d(LOG_TAG, "Radio ON @ init; reset to OFF");
-                setRadioPower(false, null);
-            } else {
-                if (RILJ_LOGD) Log.d(LOG_TAG, "Radio OFF @ init");
-                setRadioState(newState);
-                setPreferredNetworkType(mNetworkMode, null);
-            }
-            mInitialRadioStateChange = false;
-        } else {
-            setRadioState(newState);
-        }
+        setRadioState(newState);
     }
 
     /**
@@ -2467,7 +2419,7 @@
             case RIL_UNSOL_OEM_HOOK_RAW: ret = responseRaw(p); break;
             case RIL_UNSOL_RINGBACK_TONE: ret = responseInts(p); break;
             case RIL_UNSOL_RESEND_INCALL_MUTE: ret = responseVoid(p); break;
-            case RIL_UNSOL_CDMA_SUBSCRIPTION_CHANGED: ret = responseInts(p); break;
+            case RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED: ret = responseInts(p); break;
             case RIL_UNSOl_CDMA_PRL_CHANGED: ret = responseInts(p); break;
             case RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE: ret = responseVoid(p); break;
             case RIL_UNSOL_RIL_CONNECTED: ret = responseInts(p); break;
@@ -2775,7 +2727,7 @@
                 }
                 break;
 
-            case RIL_UNSOL_CDMA_SUBSCRIPTION_CHANGED:
+            case RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED:
                 if (RILJ_LOGD) unsljLogRet(response, ret);
 
                 if (mCdmaSubscriptionChangedRegistrants != null) {
@@ -2804,6 +2756,11 @@
 
             case RIL_UNSOL_RIL_CONNECTED: {
                 if (RILJ_LOGD) unsljLogRet(response, ret);
+
+                // Initial conditions
+                setRadioPower(false, null);
+                setPreferredNetworkType(mNetworkMode, null);
+                setCdmaSubscriptionSource(mCdmaSubscription, null);
                 notifyRegistrantsRilConnectionChanged(((int[])ret)[0]);
                 break;
             }
@@ -3554,7 +3511,7 @@
             case RIL_UNSOL_OEM_HOOK_RAW: return "UNSOL_OEM_HOOK_RAW";
             case RIL_UNSOL_RINGBACK_TONE: return "UNSOL_RINGBACK_TONG";
             case RIL_UNSOL_RESEND_INCALL_MUTE: return "UNSOL_RESEND_INCALL_MUTE";
-            case RIL_UNSOL_CDMA_SUBSCRIPTION_CHANGED: return "CDMA_SUBSCRIPTION_CHANGED";
+            case RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED: return "CDMA_SUBSCRIPTION_SOURCE_CHANGED";
             case RIL_UNSOl_CDMA_PRL_CHANGED: return "UNSOL_CDMA_PRL_CHANGED";
             case RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE: return "UNSOL_EXIT_EMERGENCY_CALLBACK_MODE";
             case RIL_UNSOL_RIL_CONNECTED: return "UNSOL_RIL_CONNECTED";
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 73dfdc0..2a27926 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -286,7 +286,7 @@
     int RIL_UNSOL_OEM_HOOK_RAW = 1028;
     int RIL_UNSOL_RINGBACK_TONE = 1029;
     int RIL_UNSOL_RESEND_INCALL_MUTE = 1030;
-    int RIL_UNSOL_CDMA_SUBSCRIPTION_CHANGED = 1031;
+    int RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED = 1031;
     int RIL_UNSOl_CDMA_PRL_CHANGED = 1032;
     int RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE = 1033;
     int RIL_UNSOL_RIL_CONNECTED = 1034;
diff --git a/telephony/java/com/android/internal/telephony/ServiceStateTracker.java b/telephony/java/com/android/internal/telephony/ServiceStateTracker.java
index e58ccfc..695805c 100644
--- a/telephony/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -398,7 +398,7 @@
         synchronized (this) {
             if (!mPendingRadioPowerOffAfterDataOff) {
                 if (dcTracker.isAnyActiveDataConnections()) {
-                    dcTracker.cleanUpAllConnections(null);
+                    dcTracker.cleanUpAllConnections(Phone.REASON_RADIO_TURNED_OFF);
                     Message msg = Message.obtain(this);
                     msg.what = EVENT_SET_RADIO_POWER_OFF;
                     msg.arg1 = ++mPendingRadioPowerOffAfterDataOffTag;
@@ -410,7 +410,7 @@
                         hangupAndPowerOff();
                     }
                 } else {
-                    dcTracker.cleanUpAllConnections(null);
+                    dcTracker.cleanUpAllConnections(Phone.REASON_RADIO_TURNED_OFF);
                     if (DBG) log("Data disconnected, turn off radio right away.");
                     hangupAndPowerOff();
                 }
diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java
index 1cb1118..e45141a 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java
@@ -164,10 +164,7 @@
     }
 
     public String getActiveApn(String apnType) {
-        if (mDataConnectionTracker instanceof CdmaDataConnectionTracker)
-            return mDataConnectionTracker.getActiveApnString();
-
-        return ((GsmDataConnectionTracker)mDataConnectionTracker).getActiveApnString(apnType);
+        return mDataConnectionTracker.getActiveApnString(apnType);
     }
 
     protected void log(String s) {
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
index 2637507..dc85017 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
@@ -215,6 +215,36 @@
         return allowed;
     }
 
+    /**
+     * The only circumstances under which we report that data connectivity is not
+     * possible are
+     * <ul>
+     * <li>Data is disallowed (roaming, power state, voice call, etc).</li>
+     * <li>The current data state is {@code DISCONNECTED} for a reason other than
+     * having explicitly disabled connectivity. In other words, data is not available
+     * because the phone is out of coverage or some like reason.</li>
+     * </ul>
+     * @return {@code true} if data connectivity is possible, {@code false} otherwise.
+     */
+    @Override
+    protected boolean isDataPossible() {
+        boolean dataAllowed = isDataAllowed();
+        boolean anyDataEnabled = getAnyDataEnabled();
+        boolean possible = (dataAllowed
+                && !(anyDataEnabled && (mState == State.FAILED || mState == State.IDLE)));
+        if (!possible && DBG) {
+            log("isDataPossible() " + possible + ", dataAllowed=" + dataAllowed +
+                    " anyDataEnabled=" + anyDataEnabled + " dataState=" + mState);
+        }
+        return possible;
+    }
+ 
+    @Override
+    protected boolean isDataPossible(String apnType) {
+        return isDataPossible();
+    }
+
+
     private boolean trySetupData(String reason) {
         if (DBG) log("***trySetupData due to " + (reason == null ? "(unspecified)" : reason));
 
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index 8c2851a78..9a91ee4 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -221,7 +221,7 @@
     @Override
     protected boolean isDataPossible() {
         boolean possible = (isDataAllowed()
-                && getAnyDataEnabled() && (getOverallState() == State.CONNECTED));
+                && !(getAnyDataEnabled() && (getOverallState() == State.FAILED)));
         if (!possible && DBG && isDataAllowed()) {
             log("Data not possible.  No coverage: dataState = " + getOverallState());
         }
@@ -229,6 +229,28 @@
     }
 
     @Override
+    protected boolean isDataPossible(String apnType) {
+        ApnContext apnContext = mApnContexts.get(apnType);
+        if (apnContext == null) {
+            return false;
+        }
+        boolean apnContextIsEnabled = apnContext.isEnabled();
+        State apnContextState = apnContext.getState();
+        boolean apnTypePossible = !(apnContextIsEnabled &&
+                (apnContextState == State.FAILED));
+        boolean dataAllowed = isDataAllowed();
+        boolean possible = dataAllowed && apnTypePossible;
+
+        if (DBG) {
+            log(String.format("isDataPossible(%s): possible=%b isDataAllowed=%b " +
+                    "apnTypePossible=%b apnContextisEnabled=%b apnContextState()=%s",
+                    apnType, possible, dataAllowed, apnTypePossible,
+                    apnContextIsEnabled, apnContextState));
+        }
+        return possible;
+    }
+
+    @Override
     protected void finalize() {
         if(DBG) log("finalize");
     }
@@ -336,21 +358,6 @@
     }
 
     @Override
-    /**
-     * Return DEFAULT APN due to the limit of the interface
-     */
-    public String getActiveApnString() {
-        if (DBG) log( "get default active apn string");
-        ApnContext defaultApnContext = mApnContexts.get(Phone.APN_TYPE_DEFAULT);
-        if (defaultApnContext != null) {
-            ApnSetting apnSetting = defaultApnContext.getApnSetting();
-            if (apnSetting != null) {
-                return apnSetting.apn;
-            }
-        }
-        return null;
-    }
-
     // Return active apn of specific apn type
     public String getActiveApnString(String apnType) {
         if (DBG) log( "get active apn string for type:" + apnType);
@@ -365,6 +372,15 @@
     }
 
     @Override
+    public boolean isApnTypeEnabled(String apnType) {
+        ApnContext apnContext = mApnContexts.get(apnType);
+        if (apnContext == null) {
+            return false;
+        }
+        return apnContext.isEnabled();
+    }
+
+    @Override
     protected void setState(State s) {
         if (DBG) log("setState should not be used in GSM" + s);
     }
@@ -382,23 +398,43 @@
     // Return state of overall
     public State getOverallState() {
         boolean isConnecting = false;
+        boolean isFailed = true; // All enabled Apns should be FAILED.
+        boolean isAnyEnabled = false;
+
         for (ApnContext apnContext : mApnContexts.values()) {
-            if (apnContext.getState() == State.CONNECTED ||
-                    apnContext.getState() == State.DISCONNECTING) {
-                if (DBG) log("overall state is CONNECTED");
-                return State.CONNECTED;
-            }
-            else if (apnContext.getState() == State.CONNECTING
-                    || apnContext.getState() == State.INITING) {
-                isConnecting = true;
+            if (apnContext.isEnabled()) {
+                isAnyEnabled = true;
+                switch (apnContext.getState()) {
+                case CONNECTED:
+                case DISCONNECTING:
+                    if (DBG) log("overall state is CONNECTED");
+                    return State.CONNECTED;
+                case CONNECTING:
+                case INITING:
+                    isConnecting = true;
+                    isFailed = false;
+                    break;
+                case IDLE:
+                case SCANNING:
+                    isFailed = false;
+                    break;
+                }
             }
         }
+
+        if (!isAnyEnabled) { // Nothing enabled. return IDLE.
+            return State.IDLE;
+        }
+
         if (isConnecting) {
             if (DBG) log( "overall state is CONNECTING");
             return State.CONNECTING;
-        } else {
+        } else if (!isFailed) {
             if (DBG) log( "overall state is IDLE");
             return State.IDLE;
+        } else {
+            if (DBG) log( "overall state is FAILED");
+            return State.FAILED;
         }
     }
 
@@ -509,16 +545,6 @@
         return false;
     }
 
-    protected boolean isEnabled(String apnType) {
-        ApnContext apnContext = mApnContexts.get(apnType);
-        if (apnContext == null) return false;
-        if (apnContext.getState() == State.DISCONNECTING
-                && apnContext.getPendingAction() == ApnContext.PENDING_ACTION_APN_DISABLE) {
-            return false;
-        }
-        return true;
-    }
-
     /**
      * Report on whether data connectivity is enabled for any APN.
      * @return {@code false} if data connectivity has been explicitly disabled,
@@ -640,7 +666,7 @@
         if (DBG) {
             log("trySetupData for type:" + apnContext.getApnType() +
                     " due to " + apnContext.getReason());
-            log("[DSAC DEB] " + "trySetupData with mIsPsRestricted=" + mIsPsRestricted);
+            log("trySetupData with mIsPsRestricted=" + mIsPsRestricted);
         }
 
         if (mPhone.getSimulatedRadioControl() != null) {
@@ -943,8 +969,8 @@
 
         // TODO: It'd be nice to only do this if the changed entrie(s)
         // match the current operator.
+        if (DBG) log("onApnChanged createAllApnList and cleanUpAllConnections");
         createAllApnList();
-        if (DBG) log("onApnChanged clean all connections");
         cleanUpAllConnections(isConnected, Phone.REASON_APN_CHANGED);
         if (!isConnected) {
             // TODO: Won't work for multiple connections!!!!
@@ -960,7 +986,7 @@
      * via an unsolicited response (which could have happened at any
      * previous state
      */
-    private void onDataStateChanged (AsyncResult ar, boolean explicitPoll) {
+    private void onDataStateChanged (AsyncResult ar) {
         ArrayList<DataCallState> dataCallStates;
 
         dataCallStates = (ArrayList<DataCallState>)(ar.result);
@@ -973,12 +999,11 @@
         }
 
         for (ApnContext apnContext : mApnContexts.values()) {
-            onDataStateChanged(dataCallStates, explicitPoll, apnContext);
+            onDataStateChanged(dataCallStates, apnContext);
         }
     }
 
     private void onDataStateChanged (ArrayList<DataCallState> dataCallStates,
-                                     boolean explicitPoll,
                                      ApnContext apnContext) {
 
         if (apnContext == null) {
@@ -1008,25 +1033,16 @@
                 return;
             } else if (!dataCallStatesHasActiveCID(dataCallStates,
                     apnContext.getDataConnection().getCid())) {
-                // Here, we only consider this authoritative if we asked for the
-                // PDP list. If it was an unsolicited response, we poll again
-                // to make sure everyone agrees on the initial state.
 
-                if (!explicitPoll) {
-                    // We think it disconnected but aren't sure...poll from our side
-                    mPhone.mCM.getPDPContextList(
-                            this.obtainMessage(EVENT_GET_PDP_LIST_COMPLETE));
-                } else {
-                    Log.i(LOG_TAG, "PDP connection has dropped (active=false case). "
+                Log.i(LOG_TAG, "PDP connection has dropped (active=false case). "
                                     + " Reconnecting");
 
-                    // Log the network drop on the event log.
-                    int cid = getCellLocationId();
-                    EventLog.writeEvent(EventLogTags.PDP_NETWORK_DROP, cid,
-                            TelephonyManager.getDefault().getNetworkType());
+                // Log the network drop on the event log.
+                int cid = getCellLocationId();
+                EventLog.writeEvent(EventLogTags.PDP_NETWORK_DROP, cid,
+                        TelephonyManager.getDefault().getNetworkType());
 
-                    cleanUpConnection(true, apnContext);
-                }
+                cleanUpConnection(true, apnContext);
             }
         }
     }
@@ -1178,7 +1194,7 @@
                     // It's possible the PDP context went down and we weren't notified.
                     // Start polling the context list in an attempt to recover.
                     if (DBG) log("no DATAIN in a while; polling PDP");
-                    mPhone.mCM.getDataCallList(obtainMessage(EVENT_GET_PDP_LIST_COMPLETE));
+                    mPhone.mCM.getDataCallList(obtainMessage(EVENT_DATA_STATE_CHANGED));
 
                     mNoRecvPollCount++;
 
@@ -1298,6 +1314,7 @@
     }
 
     private void onRecordsLoaded() {
+        if (DBG) log("onRecordsLoaded: createAllApnList");
         createAllApnList();
         for (ApnContext apnContext : mApnContexts.values()) {
             if (apnContext.isReady()) {
@@ -1431,6 +1448,10 @@
             log("We're on the simulator; assuming data is connected");
         }
 
+        if (mPhone.mSIMRecords.getRecordsLoaded()) {
+            notifyDataAvailability(null);
+        }
+
         if (getOverallState() != State.IDLE) {
             cleanUpConnection(true, null);
         }
@@ -1472,8 +1493,8 @@
                 log(String.format("onDataSetupComplete: success apn=%s",
                     apnContext.getWaitingApns().get(0).apn));
             }
-            mLinkProperties = getLinkProperties(apnContext.getDataConnection());
-            mLinkCapabilities = getLinkCapabilities(apnContext.getDataConnection());
+            mLinkProperties = getLinkProperties(apnContext.getApnType());
+            mLinkCapabilities = getLinkCapabilities(apnContext.getApnType());
 
             ApnSetting apn = apnContext.getApnSetting();
             if (apn.proxy != null && apn.proxy.length() != 0) {
@@ -1582,6 +1603,12 @@
 
         mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
 
+        // Check if APN disabled.
+        if (apnContext.getPendingAction() == ApnContext.PENDING_ACTION_APN_DISABLE) {
+           apnContext.setEnabled(false);
+           apnContext.setPendingAction(ApnContext.PENDING_ACTION_NONE);
+        }
+
         // if all data connection are gone, check whether Airplane mode request was
         // pending.
         if (!isConnected()) {
@@ -1591,12 +1618,6 @@
             }
         }
 
-        // Check if APN disabled.
-        if (apnContext.getPendingAction() == ApnContext.PENDING_ACTION_APN_DISABLE) {
-           apnContext.setEnabled(false);
-           apnContext.setPendingAction(ApnContext.PENDING_ACTION_NONE);
-        }
-
         if (TextUtils.equals(apnContext.getApnType(), Phone.APN_TYPE_DEFAULT)
             && retryAfterDisconnected(apnContext.getReason())) {
             SystemProperties.set("gsm.defaultpdpcontext.active", "false");
@@ -1612,7 +1633,7 @@
     protected void onPollPdp() {
         if (getOverallState() == State.CONNECTED) {
             // only poll when connected
-            mPhone.mCM.getPDPContextList(this.obtainMessage(EVENT_GET_PDP_LIST_COMPLETE));
+            mPhone.mCM.getDataCallList(this.obtainMessage(EVENT_DATA_STATE_CHANGED));
             sendMessageDelayed(obtainMessage(EVENT_POLL_PDP), POLL_PDP_MILLIS);
         }
     }
@@ -1892,11 +1913,7 @@
                 break;
 
             case EVENT_DATA_STATE_CHANGED:
-                onDataStateChanged((AsyncResult) msg.obj, false);
-                break;
-
-            case EVENT_GET_PDP_LIST_COMPLETE:
-                onDataStateChanged((AsyncResult) msg.obj, true);
+                onDataStateChanged((AsyncResult) msg.obj);
                 break;
 
             case EVENT_POLL_PDP:
@@ -1922,7 +1939,7 @@
                  * PDP context and notify us with PDP_CONTEXT_CHANGED.
                  * But we should stop the network polling and prevent reset PDP.
                  */
-                log("[DSAC DEB] " + "EVENT_PS_RESTRICT_ENABLED " + mIsPsRestricted);
+                log("EVENT_PS_RESTRICT_ENABLED " + mIsPsRestricted);
                 stopNetStatPoll();
                 mIsPsRestricted = true;
                 break;
@@ -1932,7 +1949,7 @@
                  * When PS restrict is removed, we need setup PDP connection if
                  * PDP connection is down.
                  */
-                log("[DSAC DEB] " + "EVENT_PS_RESTRICT_DISABLED " + mIsPsRestricted);
+                log("EVENT_PS_RESTRICT_DISABLED " + mIsPsRestricted);
                 mIsPsRestricted  = false;
                 if (isConnected()) {
                     startNetStatPoll();
diff --git a/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java b/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
index e69989a..8b032ff 100755
--- a/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
@@ -472,17 +472,23 @@
      *  provided the SIM card. Returns null of SIM is not yet ready
      */
     public String getSIMOperatorNumeric() {
-        if (imsi == null || mncLength == UNINITIALIZED || mncLength == UNKNOWN) {
+        if (imsi == null) {
+            Log.d(LOG_TAG, "getSIMOperatorNumeric: IMSI == null");
+            return null;
+        }
+        if (mncLength == UNINITIALIZED || mncLength == UNKNOWN) {
+            Log.d(LOG_TAG, "getSIMOperatorNumeric: bad mncLength");
             return null;
         }
 
-        // Length = length of MCC + length of MNC
-        // length of mcc = 3 (TS 23.003 Section 2.2)
+        // STOPSHIP: to be removed
         if (SystemProperties.getInt(com.android.internal.telephony.TelephonyProperties
                 .PROPERTY_NETWORK_LTE_ON_CDMA, 0) == 1) {
             Log.e(LOG_TAG, "getSIMOperatorNumeric: STOPSHIP bad numeric operators in lte");
             return SystemProperties.get("ro.cdma.home.operator.numeric", "310004");
         }
+        // Length = length of MCC + length of MNC
+        // length of mcc = 3 (TS 23.003 Section 2.2)
         return imsi.substring(0, 3 + mncLength);
     }
 
@@ -524,7 +530,7 @@
                     imsi = null;
                 }
 
-                Log.d(LOG_TAG, "IMSI: " + imsi.substring(0, 6) + "xxxxxxx");
+                Log.d(LOG_TAG, "IMSI: " + /* imsi.substring(0, 6) +*/ "xxxxxxx");
 
                 if (((mncLength == UNKNOWN) || (mncLength == 2)) &&
                         ((imsi != null) && (imsi.length() >= 6))) {