Merge change 5485 into donut

* changes:
  cdma sms is91 support
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index c3ddd20..c90b862 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -62,6 +62,16 @@
         String op = args[0];
         mNextArg = 1;
 
+        if ("enabled".equals(op)) {
+            doEnabled();
+            return;
+        }
+
+        if ("enable".equals(op)) {
+            doEnable();
+            return;
+        }
+
         if ("run".equals(op)) {
             doRun();
             return;
@@ -91,6 +101,41 @@
         showUsage();
     }
 
+    private String enableToString(boolean enabled) {
+        return enabled ? "enabled" : "disabled";
+    }
+
+    private void doEnabled() {
+        try {
+            boolean isEnabled = mBmgr.isBackupEnabled();
+            System.out.println("Backup Manager currently "
+                    + enableToString(isEnabled));
+        } catch (RemoteException e) {
+            System.err.println(e.toString());
+            System.err.println(BMGR_NOT_RUNNING_ERR);
+        }
+    }
+
+    private void doEnable() {
+        String arg = nextArg();
+        if (arg == null) {
+            showUsage();
+            return;
+        }
+
+        try {
+            boolean enable = Boolean.parseBoolean(arg);
+            mBmgr.setBackupEnabled(enable);
+            System.out.println("Backup Manager now " + enableToString(enable));
+        } catch (NumberFormatException e) {
+            showUsage();
+            return;
+        } catch (RemoteException e) {
+            System.err.println(e.toString());
+            System.err.println(BMGR_NOT_RUNNING_ERR);
+        }
+    }
+
     private void doRun() {
         try {
             mBmgr.backupNow();
@@ -291,6 +336,8 @@
     private static void showUsage() {
         System.err.println("usage: bmgr [backup|restore|list|transport|run]");
         System.err.println("       bmgr backup PACKAGE");
+        System.err.println("       bmgr enable BOOL");
+        System.err.println("       bmgr enabled");
         System.err.println("       bmgr list transports");
         System.err.println("       bmgr list sets");
         System.err.println("       bmgr transport WHICH");
@@ -301,6 +348,14 @@
         System.err.println("Note that the backup pass will effectively be a no-op if the package");
         System.err.println("does not actually have changed data to store.");
         System.err.println("");
+        System.err.println("The 'enable' command enables or disables the entire backup mechanism.");
+        System.err.println("If the argument is 'true' it will be enabled, otherwise it will be");
+        System.err.println("disabled.  When disabled, neither backup or restore operations will");
+        System.err.println("be performed.");
+        System.err.println("");
+        System.err.println("The 'enabled' command reports the current enabled/disabled state of");
+        System.err.println("the backup mechanism.");
+        System.err.println("");
         System.err.println("The 'list transports' command reports the names of the backup transports");
         System.err.println("currently available on the device.  These names can be passed as arguments");
         System.err.println("to the 'transport' command.  The currently selected transport is indicated");
diff --git a/core/java/android/backup/IBackupManager.aidl b/core/java/android/backup/IBackupManager.aidl
index 39e160b..1f11762 100644
--- a/core/java/android/backup/IBackupManager.aidl
+++ b/core/java/android/backup/IBackupManager.aidl
@@ -48,6 +48,24 @@
     void agentDisconnected(String packageName);
 
     /**
+     * Enable/disable the backup service entirely.  When disabled, no backup
+     * or restore operations will take place.  Data-changed notifications will
+     * still be observed and collected, however, so that changes made while the
+     * mechanism was disabled will still be backed up properly if it is enabled
+     * at some point in the future.
+     *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     */
+    void setBackupEnabled(boolean isEnabled);
+
+    /**
+     * Report whether the backup mechanism is currently enabled.
+     *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     */
+    boolean isBackupEnabled();
+
+    /**
      * Schedule an immediate backup attempt for all pending updates.  This is
      * primarily intended for transports to use when they detect a suitable
      * opportunity for doing a backup pass.  If there are no pending updates to
diff --git a/core/java/android/speech/tts/ITts.aidl b/core/java/android/speech/tts/ITts.aidl
index 47976e5..15f3876 100755
--- a/core/java/android/speech/tts/ITts.aidl
+++ b/core/java/android/speech/tts/ITts.aidl
@@ -43,6 +43,10 @@
 

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

 

+    String[] getLanguage();

+

+    int isLanguageAvailable(in String language, in String country, in String variant);

+

     void setLanguage(in String language, in String country, in String variant);

 

     boolean synthesizeToFile(in String text, in String[] params, in String outputDirectory);
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 8fa06fa..b245713 100755
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -251,14 +251,17 @@
      *
      * @param resourceId
      *            Example: <b><code>R.raw.south_south_east</code></b>
+     *
+     * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS.
      */
-    public void addSpeech(String text, String packagename, int resourceId) {
+    public int addSpeech(String text, String packagename, int resourceId) {
         synchronized(mStartLock) {
             if (!mStarted) {
-                return;
+                return TTS_ERROR;
             }
             try {
                 mITts.addSpeech(text, packagename, resourceId);
+                return TTS_SUCCESS;
             } catch (RemoteException e) {
                 // TTS died; restart it.
                 mStarted = false;
@@ -272,6 +275,7 @@
                 mStarted = false;
                 initTts();
             }
+            return TTS_ERROR;
         }
     }
 
@@ -285,14 +289,17 @@
      * @param filename
      *            The full path to the sound file (for example:
      *            "/sdcard/mysounds/hello.wav")
+     *
+     * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS.
      */
-    public void addSpeech(String text, String filename) {
+    public int addSpeech(String text, String filename) {
         synchronized (mStartLock) {
             if (!mStarted) {
-                return;
+                return TTS_ERROR;
             }
             try {
                 mITts.addSpeechFile(text, filename);
+                return TTS_SUCCESS;
             } catch (RemoteException e) {
                 // TTS died; restart it.
                 mStarted = false;
@@ -306,6 +313,7 @@
                 mStarted = false;
                 initTts();
             }
+            return TTS_ERROR;
         }
     }
 
@@ -324,17 +332,20 @@
      *            See TTS_QUEUE_ADD and TTS_QUEUE_FLUSH.
      * @param params
      *            The hashmap of speech parameters to be used.
+     *
+     * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS.
      */
-    public void speak(String text, int queueMode, HashMap<String,String> params)
+    public int speak(String text, int queueMode, HashMap<String,String> params)
     {
         synchronized (mStartLock) {
             Log.i("TTS received: ", text);
             if (!mStarted) {
-                return;
+                return TTS_ERROR;
             }
             try {
                 // TODO support extra parameters, passing cache of current parameters for the moment
                 mITts.speak(text, queueMode, mCachedParams);
+                return TTS_SUCCESS;
             } catch (RemoteException e) {
                 // TTS died; restart it.
                 mStarted = false;
@@ -348,6 +359,7 @@
                 mStarted = false;
                 initTts();
             }
+            return TTS_ERROR;
         }
     }
 
@@ -366,17 +378,22 @@
      *            See TTS_QUEUE_ADD and TTS_QUEUE_FLUSH.
      * @param params
      *            The hashmap of speech parameters to be used.
+     *
+     * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS.
+     *
+     * {@hide}
      */
-    public void speakIpa(String ipaText, int queueMode, HashMap<String,String> params)
+    public int speakIpa(String ipaText, int queueMode, HashMap<String,String> params)
     {
         synchronized (mStartLock) {
             Log.i("TTS received: ", ipaText);
             if (!mStarted) {
-                return;
+                return TTS_ERROR;
             }
             try {
                 // TODO support extra parameters, passing cache of current parameters for the moment
                 mITts.speakIpa(ipaText, queueMode, mCachedParams);
+                return TTS_SUCCESS;
             } catch (RemoteException e) {
                 // TTS died; restart it.
                 mStarted = false;
@@ -390,6 +407,7 @@
                 mStarted = false;
                 initTts();
             }
+            return TTS_ERROR;
         }
     }
 
@@ -403,16 +421,19 @@
      *            See TTS_QUEUE_ADD and TTS_QUEUE_FLUSH.
      * @param params
      *            The hashmap of parameters to be used.
+     *
+     * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS.
      */
-    public void playEarcon(String earcon, int queueMode,
+    public int playEarcon(String earcon, int queueMode,
             HashMap<String,String> params) {
         synchronized (mStartLock) {
             if (!mStarted) {
-                return;
+                return TTS_ERROR;
             }
             try {
                 // TODO support extra parameters, passing null for the moment
                 mITts.playEarcon(earcon, queueMode, null);
+                return TTS_SUCCESS;
             } catch (RemoteException e) {
                 // TTS died; restart it.
                 mStarted = false;
@@ -426,18 +447,30 @@
                 mStarted = false;
                 initTts();
             }
+            return TTS_ERROR;
         }
     }
 
-
-    public void playSilence(long durationInMs, int queueMode) {
+    /**
+     * 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
+     *            See TTS_QUEUE_ADD and TTS_QUEUE_FLUSH.
+     *
+     * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS.
+     */
+    public int playSilence(long durationInMs, int queueMode) {
         synchronized (mStartLock) {
             if (!mStarted) {
-                return;
+                return TTS_ERROR;
             }
             try {
                 // TODO support extra parameters, passing cache of current parameters for the moment
                 mITts.playSilence(durationInMs, queueMode, mCachedParams);
+                return TTS_SUCCESS;
             } catch (RemoteException e) {
                 // TTS died; restart it.
                 mStarted = false;
@@ -451,6 +484,7 @@
                 mStarted = false;
                 initTts();
             }
+            return TTS_ERROR;
         }
     }
 
@@ -487,14 +521,17 @@
 
     /**
      * Stops speech from the TTS.
+     *
+     * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS.
      */
-    public void stop() {
+    public int stop() {
         synchronized (mStartLock) {
             if (!mStarted) {
-                return;
+                return TTS_ERROR;
             }
             try {
                 mITts.stop();
+                return TTS_SUCCESS;
             } catch (RemoteException e) {
                 // TTS died; restart it.
                 mStarted = false;
@@ -508,6 +545,7 @@
                 mStarted = false;
                 initTts();
             }
+            return TTS_ERROR;
         }
     }
 
@@ -524,23 +562,27 @@
      *            The speech rate for the TTS 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).
+     *
+     * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS.
      */
-    public void setSpeechRate(float speechRate) {
+    public int setSpeechRate(float speechRate) {
         synchronized (mStartLock) {
             if (!mStarted) {
-                return;
+                return TTS_SUCCESS;
             }
             try {
                 if (speechRate > 0) {
                     mCachedRate = (int)(speechRate*100);
                     updateCachedParamArray();
                     mITts.setSpeechRate(mCachedRate);
+                    return TTS_SUCCESS;
                 }
             } catch (RemoteException e) {
                 // TTS died; restart it.
                 mStarted = false;
                 initTts();
             }
+            return TTS_ERROR;
         }
     }
 
@@ -557,21 +599,25 @@
      *            The pitch for the TTS engine. 1 is the normal pitch,
      *            lower values lower the tone of the synthesized voice,
      *            greater values increase it.
+     *
+     * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS.
      */
-    public void setPitch(float pitch) {
+    public int setPitch(float pitch) {
         synchronized (mStartLock) {
             if (!mStarted) {
-                return;
+                return TTS_ERROR;
             }
             try {
                 if (pitch > 0) {
                     mITts.setPitch((int)(pitch*100));
+                    return TTS_SUCCESS;
                 }
             } catch (RemoteException e) {
                 // TTS died; restart it.
                 mStarted = false;
                 initTts();
             }
+            return TTS_ERROR;
         }
     }
 
@@ -585,11 +631,13 @@
      *
      * @param loc
      *            The locale describing the language to be used.
+     *
+     * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS.
      */
-    public void setLanguage(Locale loc) {
+    public int setLanguage(Locale loc) {
         synchronized (mStartLock) {
             if (!mStarted) {
-                return;
+                return TTS_ERROR;
             }
             try {
                 mCachedLang = loc.getISO3Language();
@@ -597,29 +645,70 @@
                 mCachedVariant = loc.getVariant();
                 updateCachedParamArray();
                 mITts.setLanguage(mCachedLang, mCachedCountry, mCachedVariant);
+                return TTS_SUCCESS;
             } catch (RemoteException e) {
                 // TTS died; restart it.
                 mStarted = false;
                 initTts();
             }
+            return TTS_ERROR;
+        }
+    }
+
+
+    /**
+     * Returns a Locale instance describing the language currently being used by the TTS engine.
+     * @return language, country (if any) and variant (if any) used by the engine stored in a Locale
+     *     instance, or null is the TTS engine has failed.
+     */
+    public Locale getLanguage() {
+        synchronized (mStartLock) {
+            if (!mStarted) {
+                return null;
+            }
+            try {
+                String[] locStrings =  mITts.getLanguage();
+                if (locStrings.length == 3) {
+                    return new Locale(locStrings[0], locStrings[1], locStrings[2]);
+                } else {
+                    return null;
+                }
+            } catch (RemoteException e) {
+                // TTS died; restart it.
+                mStarted = false;
+                initTts();
+            }
+            return null;
         }
     }
 
     /**
-     * Checks if the specified language as represented by the locale is available.
+     * Checks if the specified language as represented by the Locale is available.
      *
      * @param loc
-     *            The locale describing the language to be used.
+     *            The Locale describing the language to be used.
+     *
      * @return one of TTS_LANG_NOT_SUPPORTED, TTS_LANG_MISSING_DATA, TTS_LANG_AVAILABLE,
-               TTS_LANG_COUNTRY_AVAILABLE, TTS_LANG_COUNTRY_VAR_AVAILABLE.
+     *         TTS_LANG_COUNTRY_AVAILABLE, TTS_LANG_COUNTRY_VAR_AVAILABLE.
      */
     public int isLanguageAvailable(Locale loc) {
-        //TODO: Implement isLanguageAvailable
-        return TTS_LANG_NOT_SUPPORTED;
+        synchronized (mStartLock) {
+            if (!mStarted) {
+                return TTS_LANG_NOT_SUPPORTED;
+            }
+            try {
+                return mITts.isLanguageAvailable(loc.getISO3Language(), loc.getISO3Country(),
+                        loc.getVariant());
+            } catch (RemoteException e) {
+                // TTS died; restart it.
+                mStarted = false;
+                initTts();
+            }
+            return TTS_LANG_NOT_SUPPORTED;
+        }
     }
 
 
-
     /**
      * Synthesizes the given text to a file using the specified parameters.
      *
@@ -630,17 +719,20 @@
      * @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
+     *
+     * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS.
      */
-    public boolean synthesizeToFile(String text, HashMap<String,String> params,
+    public int synthesizeToFile(String text, HashMap<String,String> params,
             String filename) {
         synchronized (mStartLock) {
             if (!mStarted) {
-                return false;
+                return TTS_ERROR;
             }
             try {
                 // TODO support extra parameters, passing null for the moment
-                return mITts.synthesizeToFile(text, null, filename);
+                if (mITts.synthesizeToFile(text, null, filename)){
+                    return TTS_SUCCESS;
+                }
             } catch (RemoteException e) {
                 // TTS died; restart it.
                 mStarted = false;
@@ -654,10 +746,11 @@
                 mStarted = false;
                 initTts();
             }
-            return false;
+            return TTS_ERROR;
         }
     }
 
+
     /**
      * Synthesizes the given IPA text to a file using the specified parameters.
      *
@@ -668,17 +761,22 @@
      * @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
+     *
+     * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS.
+     *
+     * {@hide}
      */
-    public boolean synthesizeIpaToFile(String ipaText,
+    public int synthesizeIpaToFile(String ipaText,
             HashMap<String,String> params, String filename) {
         synchronized (mStartLock) {
             if (!mStarted) {
-                return false;
+                return TTS_ERROR;
             }
             try {
                 // TODO support extra parameters, passing null for the moment
-                return mITts.synthesizeIpaToFile(ipaText, null, filename);
+                if (mITts.synthesizeIpaToFile(ipaText, null, filename)){
+                    return TTS_SUCCESS;
+                }
             } catch (RemoteException e) {
                 // TTS died; restart it.
                 mStarted = false;
@@ -692,7 +790,7 @@
                 mStarted = false;
                 initTts();
             }
-            return false;
+            return TTS_ERROR;
         }
     }
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index c9a785c..b3180ca 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5922,8 +5922,9 @@
             int height = mBottom - mTop;
 
             final AttachInfo attachInfo = mAttachInfo;
+            final boolean scalingRequired = attachInfo != null && attachInfo.mScalingRequired;
 
-            if (autoScale && attachInfo != null && attachInfo.mScalingRequired) {
+            if (autoScale && scalingRequired) {
                 width = (int) ((width * attachInfo.mApplicationScale) + 0.5f);
                 height = (int) ((height * attachInfo.mApplicationScale) + 0.5f);
             }
@@ -6014,7 +6015,7 @@
             computeScroll();
             final int restoreCount = canvas.save();
             
-            if (autoScale && attachInfo.mScalingRequired) {
+            if (autoScale && scalingRequired) {
                 final float scale = attachInfo.mApplicationScale;
                 canvas.scale(scale, scale);
             }
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index 9a0f467..7393737 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -340,10 +340,19 @@
     }
 
     /**
-     * The default implementation does nothing.
+     * The default implementation turns this into the enter key.
      */
     public boolean performEditorAction(int actionCode) {
-        return false;
+        long eventTime = SystemClock.uptimeMillis();
+        sendKeyEvent(new KeyEvent(eventTime, eventTime,
+                KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
+                KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
+                | KeyEvent.FLAG_EDITOR_ACTION));
+        sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
+                KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
+                KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
+                | KeyEvent.FLAG_EDITOR_ACTION));
+        return true;
     }
 
     /**
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index 12bb01c..e62dda5 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -558,9 +558,9 @@
                 myWidth);
         int childHeightMeasureSpec;
         if (params.width == LayoutParams.FILL_PARENT) {
-            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY, myHeight);
+            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.EXACTLY);
         } else {
-            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.AT_MOST);
         }
         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
     }
@@ -1403,7 +1403,9 @@
             /*
              * START POOL IMPLEMENTATION
              */
-            private static final int POOL_LIMIT = 12;
+            // The pool is static, so all nodes instances are shared across
+            // activities, that's why we give it a rather high limit
+            private static final int POOL_LIMIT = 100;
             private static final Pool<Node> sPool = Pools.synchronizedPool(
                     Pools.finitePool(new PoolableManager<Node>() {
                         public Node newInstance() {
diff --git a/core/java/com/android/internal/util/BitwiseInputStream.java b/core/java/com/android/internal/util/BitwiseInputStream.java
index 031abdd..86f74f3 100644
--- a/core/java/com/android/internal/util/BitwiseInputStream.java
+++ b/core/java/com/android/internal/util/BitwiseInputStream.java
@@ -65,8 +65,10 @@
     /**
      * Read some data and increment the current position.
      *
-     * @param bits the amount of data to read (gte 0, lte 8)
+     * The 8-bit limit on access to bitwise streams is intentional to
+     * avoid endianness issues.
      *
+     * @param bits the amount of data to read (gte 0, lte 8)
      * @return byte of read data (possibly partially filled, from lsb)
      */
     public int read(int bits) throws AccessException {
@@ -88,7 +90,6 @@
      * Read data in bulk into a byte array and increment the current position.
      *
      * @param bits the amount of data to read
-     *
      * @return newly allocated byte array of read data
      */
     public byte[] readByteArray(int bits) throws AccessException {
diff --git a/core/java/com/android/internal/util/BitwiseOutputStream.java b/core/java/com/android/internal/util/BitwiseOutputStream.java
index ab8a7f3..70c0be8 100644
--- a/core/java/com/android/internal/util/BitwiseOutputStream.java
+++ b/core/java/com/android/internal/util/BitwiseOutputStream.java
@@ -82,6 +82,9 @@
     /**
      * Write some data and increment the current position.
      *
+     * The 8-bit limit on access to bitwise streams is intentional to
+     * avoid endianness issues.
+     *
      * @param bits the amount of data to write (gte 0, lte 8)
      * @param data to write, will be masked to expose only bits param from lsb
      */
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7f24dcc..599360f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -973,7 +973,7 @@
     <permission android:name="android.permission.BACKUP"
         android:label="@string/permlab_backup"
         android:description="@string/permdesc_backup"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signatureOrSystem" />
 
     <!-- Allows an application to tell the AppWidget service which application
          can access AppWidget's data.  The normal user flow is that a user
diff --git a/core/res/res/layout/character_picker.xml b/core/res/res/layout/character_picker.xml
index bb4955a..0344849 100644
--- a/core/res/res/layout/character_picker.xml
+++ b/core/res/res/layout/character_picker.xml
@@ -23,8 +23,8 @@
         android:id="@+id/characterPicker"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:padding="12dp"
-        android:verticalSpacing="8dp"
+        android:padding="4dp"
+        android:verticalSpacing="4dp"
         android:horizontalSpacing="8dp"
         android:stretchMode="spacingWidth"
         android:gravity="left"
diff --git a/include/tts/TtsEngine.h b/include/tts/TtsEngine.h
index ca50a5e..21cb73b 100644
--- a/include/tts/TtsEngine.h
+++ b/include/tts/TtsEngine.h
@@ -133,16 +133,26 @@
     // @return TTS_SUCCESS, or TTS_FAILURE
     virtual tts_result setLanguage(const char *lang, const char *country, const char *variant);
 
-    // Retrieve the currently set language, or an empty "value" if no language
-    // has been set.
-    // @param[out]   value pointer to the retrieved language value
-    // @param[inout] iosize in: stores the size available to store the language
-    //                         value in *value
-    //                      out: stores the size required to hold the language
-    //                         value if  getLanguage() returned
-    //                         TTS_PROPERTY_SIZE_TOO_SMALL, unchanged otherwise.
-    // @return TTS_SUCCESS, or TTS_PROPERTY_SIZE_TOO_SMALL, or TTS_FAILURE
-    virtual tts_result getLanguage(char *value, size_t *iosize);
+    // 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"
diff --git a/libs/surfaceflinger/SurfaceFlinger.cpp b/libs/surfaceflinger/SurfaceFlinger.cpp
index 0e998bf..ef4a8ea 100644
--- a/libs/surfaceflinger/SurfaceFlinger.cpp
+++ b/libs/surfaceflinger/SurfaceFlinger.cpp
@@ -771,10 +771,11 @@
             dirty.orSelf(layer->visibleRegionScreen);
             layer->contentDirty = false;
         } else {
-            // compute the exposed region
-            // dirty = what's visible now - what's wasn't covered before
-            //       = what's visible now & what's was covered before
-            dirty = visibleRegion.intersect(layer->coveredRegionScreen);            
+            /* compute the exposed region:
+             *    exposed = what's VISIBLE and NOT COVERED now 
+             *    but was COVERED before
+             */
+            dirty = (visibleRegion - coveredRegion) & layer->coveredRegionScreen;
         }
         dirty.subtractSelf(aboveOpaqueLayers);
 
@@ -783,7 +784,7 @@
 
         // updade aboveOpaqueLayers/aboveCoveredLayers for next (lower) layer
         aboveOpaqueLayers.orSelf(opaqueRegion);
-        aboveCoveredLayers.orSelf(bounds);
+        aboveCoveredLayers.orSelf(visibleRegion);
         
         // Store the visible region is screen space
         layer->setVisibleRegion(visibleRegion);
diff --git a/packages/TtsService/jni/android_tts_SynthProxy.cpp b/packages/TtsService/jni/android_tts_SynthProxy.cpp
index 8537cae..69ba062 100644
--- a/packages/TtsService/jni/android_tts_SynthProxy.cpp
+++ b/packages/TtsService/jni/android_tts_SynthProxy.cpp
@@ -278,6 +278,33 @@
 }
 
 
+static int
+android_tts_SynthProxy_isLanguageAvailable(JNIEnv *env, jobject thiz, jint jniData,
+        jstring language, jstring country, jstring variant)
+{
+    int result = 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);
+    // TODO check return codes
+    if (pSynthData->mNativeSynthInterface) {
+        result = pSynthData->mNativeSynthInterface->isLanguageAvailable(langNativeString,
+                countryNativeString, variantNativeString);
+    }
+    env->ReleaseStringUTFChars(language, langNativeString);
+    env->ReleaseStringUTFChars(country, countryNativeString);
+    env->ReleaseStringUTFChars(variant, variantNativeString);
+    return result;
+}
+
+
 static void
 android_tts_SynthProxy_setLanguage(JNIEnv *env, jobject thiz, jint jniData,
         jstring language, jstring country, jstring variant)
@@ -533,23 +560,34 @@
 }
 
 
-JNIEXPORT jstring JNICALL
+static jobjectArray
 android_tts_SynthProxy_getLanguage(JNIEnv *env, jobject thiz, jint jniData)
 {
     if (jniData == 0) {
         LOGE("android_tts_SynthProxy_getLanguage(): invalid JNI data");
-        return env->NewStringUTF("");
+        return NULL;
     }
 
     SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
-    size_t bufSize = 100;
-    char buf[bufSize];
-    memset(buf, 0, bufSize);
-    // TODO check return codes
+
     if (pSynthData->mNativeSynthInterface) {
-        pSynthData->mNativeSynthInterface->getLanguage(buf, &bufSize);
+        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(""));
+        pSynthData->mNativeSynthInterface->getLanguage(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;
     }
-    return env->NewStringUTF(buf);
 }
 
 
@@ -587,6 +625,10 @@
         "(ILjava/lang/String;Ljava/lang/String;)V",
         (void*)android_tts_SynthProxy_synthesizeToFile
     },
+    {   "native_isLanguageAvailable",
+        "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
+        (void*)android_tts_SynthProxy_isLanguageAvailable
+    },
     {   "native_setLanguage",
         "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
         (void*)android_tts_SynthProxy_setLanguage
@@ -608,7 +650,7 @@
         (void*)android_tts_SynthProxy_playAudioBuffer
     },
     {   "native_getLanguage",
-        "(I)Ljava/lang/String;",
+        "(I)[Ljava/lang/String;",
         (void*)android_tts_SynthProxy_getLanguage
     },
     {   "native_getRate",
diff --git a/packages/TtsService/src/android/tts/SynthProxy.java b/packages/TtsService/src/android/tts/SynthProxy.java
index a8eaaa43..91fe3b7 100755
--- a/packages/TtsService/src/android/tts/SynthProxy.java
+++ b/packages/TtsService/src/android/tts/SynthProxy.java
@@ -68,12 +68,20 @@
     // TODO add IPA methods
 
     /**
-     * Sets the language
+     * 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);
+    }
+
+    /**
+     * Sets the language.
      */
     public void setLanguage(String language, String country, String variant) {
         native_setLanguage(mJniData, language, country, variant);
     }
-    
+
     /**
      * Loads the language: it's not set, but prepared for use later.
      */
@@ -82,42 +90,42 @@
     }
 
     /**
-     * Sets the speech rate
+     * Sets the speech rate.
      */
     public final void setSpeechRate(int speechRate) {
         native_setSpeechRate(mJniData, speechRate);
     }
 
     /**
-     * Sets the pitch of the synthesized voice
+     * Sets the pitch of the synthesized voice.
      */
     public final void setPitch(int pitch) {
         native_setPitch(mJniData, pitch);
     }
 
     /**
-     * Plays the given audio buffer
+     * Plays the given audio buffer.
      */
     public void playAudioBuffer(int bufferPointer, int bufferSize) {
         native_playAudioBuffer(mJniData, bufferPointer, bufferSize);
     }
 
     /**
-     * Gets the currently set language
+     * Returns the currently set language, country and variant information.
      */
-    public String getLanguage() {
+    public String[] getLanguage() {
         return native_getLanguage(mJniData);
     }
 
     /**
-     * Gets the currently set rate
+     * Gets the currently set rate.
      */
     public int getRate() {
         return native_getRate(mJniData);
     }
 
     /**
-     * Shuts down the native synthesizer
+     * Shuts down the native synthesizer.
      */
     public void shutdown()  {
         native_shutdown(mJniData);
@@ -154,20 +162,23 @@
 
     private native final void 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 void native_setLanguage(int jniData, String language, String country,
             String variant);
-    
+
     private native final void native_loadLanguage(int jniData, String language, String country,
             String variant);
 
     private native final void native_setSpeechRate(int jniData, int speechRate);
-    
+
     private native final void native_setPitch(int jniData, int speechRate);
 
     // TODO add buffer format
     private native final void native_playAudioBuffer(int jniData, int bufferPointer, int bufferSize);
 
-    private native final String native_getLanguage(int jniData);
+    private native final String[] native_getLanguage(int jniData);
 
     private native final int native_getRate(int jniData);
 
diff --git a/packages/TtsService/src/android/tts/TtsService.java b/packages/TtsService/src/android/tts/TtsService.java
index 421b2ca..b1e6425 100755
--- a/packages/TtsService/src/android/tts/TtsService.java
+++ b/packages/TtsService/src/android/tts/TtsService.java
@@ -91,6 +91,7 @@
     }
 
     private static final int MAX_SPEECH_ITEM_CHAR_LENGTH = 4000;
+    private static final int MAX_FILENAME_LENGTH = 250;
 
     private static final String ACTION = "android.intent.action.USE_TTS";
     private static final String CATEGORY = "android.intent.category.TTS";
@@ -147,8 +148,7 @@
 
 
     private void setDefaultSettings() {
-        // TODO handle default language
-        setLanguage("eng", "USA", "");
+        setLanguage(this.getDefaultLanguage(), getDefaultCountry(), getDefaultLocVariant());
 
         // speech rate
         setSpeechRate(getDefaultRate());
@@ -217,6 +217,17 @@
     }
 
 
+    private int isLanguageAvailable(String lang, String country, String variant) {
+        Log.v("TTS", "TtsService.isLanguageAvailable(" + lang + ", " + country + ", " +variant+")");
+        return nativeSynth.isLanguageAvailable(lang, country, variant);
+    }
+
+
+    private String[] getLanguage() {
+        return nativeSynth.getLanguage();
+    }
+
+
     private void setLanguage(String lang, String country, String variant) {
         Log.v("TTS", "TtsService.setLanguage(" + lang + ", " + country + ", " + variant + ")");
         if (isDefaultEnforced()) {
@@ -414,6 +425,26 @@
                         synth.start();
                         return;
                     }
+                    if (params != null){
+                        String language = "";
+                        String country = "";
+                        String variant = "";
+                        for (int i = 0; i < params.size() - 1; i = i + 2){
+                            String param = params.get(i);
+                            if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_RATE)){
+                                setSpeechRate(Integer.parseInt(params.get(i+1)));
+                            } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_LANGUAGE)){
+                                language = params.get(i+1);
+                            } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_COUNTRY)){
+                                country = params.get(i+1);
+                            } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_VARIANT)){
+                                variant = params.get(i+1);
+                            }
+                        }
+                        if (language.length() > 0){
+                            setLanguage(language, country, variant);
+                        }
+                    }
                     nativeSynth.speak(text);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
@@ -614,8 +645,7 @@
                 return false;
             }
             // Don't allow a filename that is too long
-            // TODO use platform constant
-            if (filename.length() > 250) {
+            if (filename.length() > MAX_FILENAME_LENGTH) {
                 return false;
             }
             nativeSynth.synthesizeToFile(text, filename);
@@ -660,8 +690,7 @@
                 return false;
             }
             // Don't allow a filename that is too long
-            // TODO use platform constant
-            if (filename.length() > 250) {
+            if (filename.length() > MAX_FILENAME_LENGTH) {
                 return false;
             }
             // TODO: Add nativeSynth.synthesizeIpaToFile(text, filename);
@@ -874,6 +903,30 @@
         }
 
         /**
+         * 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) {
+            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.
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 2b9ac4d..c67f0b5 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -74,6 +74,10 @@
     private static final String TAG = "BackupManagerService";
     private static final boolean DEBUG = true;
 
+    // Persistent properties
+    private static final String BACKUP_TRANSPORT_PROPERTY = "persist.service.bkup.trans";
+    private static final String BACKUP_ENABLED_PROPERTY = "persist.service.bkup.enabled";
+
     // Default time to wait after data changes before we back up the data
     private static final long COLLECTION_INTERVAL = 3 * 60 * 1000;
 
@@ -86,10 +90,11 @@
 
     private Context mContext;
     private PackageManager mPackageManager;
-    private final IActivityManager mActivityManager;
+    private IActivityManager mActivityManager;
+    private boolean mEnabled;   // access to this is synchronized on 'this'
     private final BackupHandler mBackupHandler = new BackupHandler();
     // map UIDs to the set of backup client services within that UID's app set
-    private SparseArray<HashSet<ApplicationInfo>> mBackupParticipants
+    private final SparseArray<HashSet<ApplicationInfo>> mBackupParticipants
         = new SparseArray<HashSet<ApplicationInfo>>();
     // set of backup services that have pending changes
     private class BackupRequest {
@@ -128,7 +133,6 @@
     private volatile boolean mClearingData;
 
     // Transport bookkeeping
-    static private final String BACKUP_TRANSPORT_PROPERTY = "persist.service.bkup.trans";
     private final HashMap<String,IBackupTransport> mTransports
             = new HashMap<String,IBackupTransport>();
     private String mCurrentTransport;
@@ -160,6 +164,9 @@
         mActivityManager = ActivityManagerNative.getDefault();
 
         // Set up our bookkeeping
+        // !!! STOPSHIP: make this disabled by default so that we then gate on
+        //               setupwizard or other opt-out UI
+        mEnabled = SystemProperties.getBoolean(BACKUP_ENABLED_PROPERTY, true);
         mBaseStateDir = new File(Environment.getDataDirectory(), "backup");
         mDataDir = Environment.getDownloadCacheDirectory();
 
@@ -489,8 +496,15 @@
 
     // The queue lock should be held when scheduling a backup pass
     private void scheduleBackupPassLocked(long timeFromNowMillis) {
-        mBackupHandler.removeMessages(MSG_RUN_BACKUP);
-        mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, timeFromNowMillis);
+        // We only schedule backups when we're actually enabled
+        synchronized (this) {
+            if (mEnabled) {
+                mBackupHandler.removeMessages(MSG_RUN_BACKUP);
+                mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, timeFromNowMillis);
+            } else if (DEBUG) {
+                Log.v(TAG, "Disabled, so not scheduling backup pass");
+            }
+        }
     }
 
     // Return the given transport
@@ -1087,7 +1101,7 @@
 
                 if (DEBUG) {
                     int numKeys = mPendingBackups.size();
-                    Log.d(TAG, "Scheduling backup for " + numKeys + " participants:");
+                    Log.d(TAG, "Now awaiting backup for " + numKeys + " participants:");
                     for (BackupRequest b : mPendingBackups.values()) {
                         Log.d(TAG, "    + " + b + " agent=" + b.appInfo.backupAgentName);
                     }
@@ -1117,7 +1131,7 @@
     // Run a backup pass immediately for any applications that have declared
     // that they have pending updates.
     public void backupNow() throws RemoteException {
-        mContext.enforceCallingPermission("android.permission.BACKUP", "tryBackupNow");
+        mContext.enforceCallingPermission("android.permission.BACKUP", "backupNow");
 
         if (DEBUG) Log.v(TAG, "Scheduling immediate backup pass");
         synchronized (mQueueLock) {
@@ -1125,16 +1139,43 @@
         }
     }
 
+    // Enable/disable the backup transport
+    public void setBackupEnabled(boolean enable) {
+        mContext.enforceCallingPermission("android.permission.BACKUP", "setBackupEnabled");
+
+        boolean wasEnabled = mEnabled;
+        synchronized (this) {
+            SystemProperties.set(BACKUP_ENABLED_PROPERTY, enable ? "true" : "false");
+            mEnabled = enable;
+        }
+
+        if (enable && !wasEnabled) {
+            synchronized (mQueueLock) {
+                if (mPendingBackups.size() > 0) {
+                    // !!! TODO: better policy around timing of the first backup pass
+                    if (DEBUG) Log.v(TAG, "Backup enabled with pending data changes, scheduling");
+                    this.scheduleBackupPassLocked(COLLECTION_INTERVAL);
+                }
+            }
+        }
+}
+
+    // Report whether the backup mechanism is currently enabled
+    public boolean isBackupEnabled() {
+        mContext.enforceCallingPermission("android.permission.BACKUP", "isBackupEnabled");
+        return mEnabled;    // no need to synchronize just to read it
+    }
+
     // Report the name of the currently active transport
     public String getCurrentTransport() {
-        mContext.enforceCallingPermission("android.permission.BACKUP", "selectBackupTransport");
+        mContext.enforceCallingPermission("android.permission.BACKUP", "getCurrentTransport");
         Log.v(TAG, "getCurrentTransport() returning " + mCurrentTransport);
         return mCurrentTransport;
     }
 
     // Report all known, available backup transports
     public String[] listAllTransports() {
-        mContext.enforceCallingPermission("android.permission.BACKUP", "selectBackupTransport");
+        mContext.enforceCallingPermission("android.permission.BACKUP", "listAllTransports");
 
         String[] list = null;
         ArrayList<String> known = new ArrayList<String>();
@@ -1292,7 +1333,8 @@
         synchronized (mQueueLock) {
             pw.println("Available transports:");
             for (String t : listAllTransports()) {
-                pw.println("  " + t);
+                String pad = (t.equals(mCurrentTransport)) ? "  * " : "    ";
+                pw.println(pad + t);
             }
             int N = mBackupParticipants.size();
             pw.println("Participants:");
@@ -1302,14 +1344,12 @@
                 pw.println(uid);
                 HashSet<ApplicationInfo> participants = mBackupParticipants.valueAt(i);
                 for (ApplicationInfo app: participants) {
-                    pw.print("    ");
-                    pw.println(app.toString());
+                    pw.println("    " + app.toString());
                 }
             }
             pw.println("Pending: " + mPendingBackups.size());
             for (BackupRequest req : mPendingBackups.values()) {
-                pw.print("   ");
-                pw.println(req);
+                pw.println("    " + req);
             }
         }
     }
diff --git a/services/java/com/android/server/PackageManagerBackupAgent.java b/services/java/com/android/server/PackageManagerBackupAgent.java
index 66fb86d..16f14e8 100644
--- a/services/java/com/android/server/PackageManagerBackupAgent.java
+++ b/services/java/com/android/server/PackageManagerBackupAgent.java
@@ -120,7 +120,10 @@
             // write its signature block to the output, keyed on the package name.
             for (PackageInfo pkg : mAllPackages) {
                 String packName = pkg.packageName;
-                if (!existing.contains(packName)) {
+                if (packName.equals(GLOBAL_METADATA_KEY)) {
+                    // We've already handled the metadata key; skip it here
+                    continue;
+                } else if (!existing.contains(packName)) {
                     // We haven't stored this app's signatures yet, so we do that now
                     try {
                         PackageInfo info = mPackageManager.getPackageInfo(packName,
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 5ea7504..9bad153 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -3834,7 +3834,7 @@
                 "dispatchPointer " + ev);
 
         Object targetObj = mKeyWaiter.waitForNextEventTarget(null, qev,
-                ev, true, false);
+                ev, true, false, pid, uid);
 
         int action = ev.getAction();
 
@@ -4032,7 +4032,7 @@
                 TAG, "dispatchTrackball [" + ev.getAction() +"] <" + ev.getX() + ", " + ev.getY() + ">");
 
         Object focusObj = mKeyWaiter.waitForNextEventTarget(null, qev,
-                ev, false, false);
+                ev, false, false, pid, uid);
         if (focusObj == null) {
             Log.w(TAG, "No focus window, dropping trackball: " + ev);
             if (qev != null) {
@@ -4103,7 +4103,7 @@
         if (DEBUG_INPUT) Log.v(TAG, "Dispatch key: " + event);
 
         Object focusObj = mKeyWaiter.waitForNextEventTarget(event, null,
-                null, false, false);
+                null, false, false, pid, uid);
         if (focusObj == null) {
             Log.w(TAG, "No focus window, dropping: " + event);
             return INJECT_FAILED;
@@ -4220,10 +4220,14 @@
         KeyEvent newEvent = new KeyEvent(downTime, eventTime, action, code, repeatCount, metaState,
                 deviceId, scancode, KeyEvent.FLAG_FROM_SYSTEM);
 
-        int result = dispatchKey(newEvent, Binder.getCallingPid(), Binder.getCallingUid());
+        final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
+        final long ident = Binder.clearCallingIdentity();
+        final int result = dispatchKey(newEvent, pid, uid);
         if (sync) {
-            mKeyWaiter.waitForNextEventTarget(null, null, null, false, true);
+            mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid);
         }
+        Binder.restoreCallingIdentity(ident);
         switch (result) {
             case INJECT_NO_PERMISSION:
                 throw new SecurityException(
@@ -4244,10 +4248,14 @@
      * @return Returns true if event was dispatched, false if it was dropped for any reason
      */
     public boolean injectPointerEvent(MotionEvent ev, boolean sync) {
-        int result = dispatchPointer(null, ev, Binder.getCallingPid(), Binder.getCallingUid());
+        final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
+        final long ident = Binder.clearCallingIdentity();
+        final int result = dispatchPointer(null, ev, pid, uid);
         if (sync) {
-            mKeyWaiter.waitForNextEventTarget(null, null, null, false, true);
+            mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid);
         }
+        Binder.restoreCallingIdentity(ident);
         switch (result) {
             case INJECT_NO_PERMISSION:
                 throw new SecurityException(
@@ -4268,10 +4276,14 @@
      * @return Returns true if event was dispatched, false if it was dropped for any reason
      */
     public boolean injectTrackballEvent(MotionEvent ev, boolean sync) {
-        int result = dispatchTrackball(null, ev, Binder.getCallingPid(), Binder.getCallingUid());
+        final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
+        final long ident = Binder.clearCallingIdentity();
+        final int result = dispatchTrackball(null, ev, pid, uid);
         if (sync) {
-            mKeyWaiter.waitForNextEventTarget(null, null, null, false, true);
+            mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid);
         }
+        Binder.restoreCallingIdentity(ident);
         switch (result) {
             case INJECT_NO_PERMISSION:
                 throw new SecurityException(
@@ -4380,7 +4392,7 @@
          */
         Object waitForNextEventTarget(KeyEvent nextKey, QueuedEvent qev,
                 MotionEvent nextMotion, boolean isPointerEvent,
-                boolean failIfTimeout) {
+                boolean failIfTimeout, int callingPid, int callingUid) {
             long startTime = SystemClock.uptimeMillis();
             long keyDispatchingTimeout = 5 * 1000;
             long waitedFor = 0;
@@ -4398,7 +4410,7 @@
                         ", mLastWin=" + mLastWin);
                 if (targetIsNew) {
                     Object target = findTargetWindow(nextKey, qev, nextMotion,
-                            isPointerEvent);
+                            isPointerEvent, callingPid, callingUid);
                     if (target == SKIP_TARGET_TOKEN) {
                         // The user has pressed a special key, and we are
                         // dropping all pending events before it.
@@ -4574,7 +4586,8 @@
         }
 
         Object findTargetWindow(KeyEvent nextKey, QueuedEvent qev,
-                MotionEvent nextMotion, boolean isPointerEvent) {
+                MotionEvent nextMotion, boolean isPointerEvent,
+                int callingPid, int callingUid) {
             mOutsideTouchTargets = null;
 
             if (nextKey != null) {
@@ -4583,9 +4596,16 @@
                 final int repeatCount = nextKey.getRepeatCount();
                 final boolean down = nextKey.getAction() != KeyEvent.ACTION_UP;
                 boolean dispatch = mKeyWaiter.checkShouldDispatchKey(keycode);
+                
                 if (!dispatch) {
-                    mPolicy.interceptKeyTi(null, keycode,
-                            nextKey.getMetaState(), down, repeatCount);
+                    if (callingUid == 0 ||
+                            mContext.checkPermission(
+                                    android.Manifest.permission.INJECT_EVENTS,
+                                    callingPid, callingUid)
+                                    == PackageManager.PERMISSION_GRANTED) {
+                        mPolicy.interceptKeyTi(null, keycode,
+                                nextKey.getMetaState(), down, repeatCount);
+                    }
                     Log.w(TAG, "Event timeout during app switch: dropping "
                             + nextKey);
                     return SKIP_TARGET_TOKEN;
@@ -4600,9 +4620,16 @@
 
                 wakeupIfNeeded(focus, LocalPowerManager.BUTTON_EVENT);
 
-                if (mPolicy.interceptKeyTi(focus,
-                        keycode, nextKey.getMetaState(), down, repeatCount)) {
-                    return CONSUMED_EVENT_TOKEN;
+                if (callingUid == 0 ||
+                        (focus != null && callingUid == focus.mSession.mUid) ||
+                        mContext.checkPermission(
+                                android.Manifest.permission.INJECT_EVENTS,
+                                callingPid, callingUid)
+                                == PackageManager.PERMISSION_GRANTED) {
+                    if (mPolicy.interceptKeyTi(focus,
+                            keycode, nextKey.getMetaState(), down, repeatCount)) {
+                        return CONSUMED_EVENT_TOKEN;
+                    }
                 }
 
                 return focus;
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index d3942fc..dbe8431 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -701,17 +701,6 @@
     }
 
     /**
-     * Note: calls extractNetworkPortion(), so do not use for
-     * SIM EF[ADN] style records
-     *
-     * Exceptions thrown if extractNetworkPortion(s).length() == 0
-     */
-    public static byte[]
-    networkPortionToCalledPartyBCD(String s) {
-        return numberToCalledPartyBCD(extractNetworkPortion(s));
-    }
-
-    /**
      * Return true iff the network portion of <code>address</code> is,
      * as far as we can tell on the device, suitable for use as an SMS
      * destination address.
@@ -744,12 +733,25 @@
     }
 
     /**
+     * Note: calls extractNetworkPortion(), so do not use for
+     * SIM EF[ADN] style records
+     *
+     * Returns null if network portion is empty.
+     */
+    public static byte[]
+    networkPortionToCalledPartyBCD(String s) {
+        String networkPortion = extractNetworkPortion(s);
+        return numberToCalledPartyBCDHelper(networkPortion, false);
+    }
+
+    /**
      * Same as {@link #networkPortionToCalledPartyBCD}, but includes a
      * one-byte length prefix.
      */
     public static byte[]
     networkPortionToCalledPartyBCDWithLength(String s) {
-        return numberToCalledPartyBCDWithLength(extractNetworkPortion(s));
+        String networkPortion = extractNetworkPortion(s);
+        return numberToCalledPartyBCDHelper(networkPortion, true);
     }
 
     /**
@@ -761,61 +763,46 @@
      */
     public static byte[]
     numberToCalledPartyBCD(String number) {
-        // The extra byte required for '+' is taken into consideration while calculating
-        // length of ret.
-        int size = (hasPlus(number) ? number.length() - 1 : number.length());
-        byte[] ret = new byte[(size + 1) / 2 + 1];
-
-        return numberToCalledPartyBCDHelper(ret, 0, number);
+        return numberToCalledPartyBCDHelper(number, false);
     }
 
     /**
-     * Same as {@link #numberToCalledPartyBCD}, but includes a
-     * one-byte length prefix.
+     * If includeLength is true, prepend a one-byte length value to
+     * the return array.
      */
     private static byte[]
-    numberToCalledPartyBCDWithLength(String number) {
-        // The extra byte required for '+' is taken into consideration while calculating
-        // length of ret.
-        int size = (hasPlus(number) ? number.length() - 1 : number.length());
-        int length = (size + 1) / 2 + 1;
-        byte[] ret = new byte[length + 1];
+    numberToCalledPartyBCDHelper(String number, boolean includeLength) {
+        int numberLenReal = number.length();
+        int numberLenEffective = numberLenReal;
+        boolean hasPlus = number.indexOf('+') != -1;
+        if (hasPlus) numberLenEffective--;
 
-        ret[0] = (byte) (length & 0xff);
-        return numberToCalledPartyBCDHelper(ret, 1, number);
-    }
+        if (numberLenEffective == 0) return null;
 
-    private static boolean
-    hasPlus(String s) {
-      return s.indexOf('+') >= 0;
-    }
+        int resultLen = (numberLenEffective + 1) / 2;  // Encoded numbers require only 4 bits each.
+        int extraBytes = 1;                            // Prepended TOA byte.
+        if (includeLength) extraBytes++;               // Optional prepended length byte.
+        resultLen += extraBytes;
 
-    private static byte[]
-    numberToCalledPartyBCDHelper(byte[] ret, int offset, String number) {
-        if (hasPlus(number)) {
-            number = number.replaceAll("\\+", "");
-            ret[offset] = (byte) TOA_International;
-        } else {
-            ret[offset] = (byte) TOA_Unknown;
+        byte[] result = new byte[resultLen];
+
+        int digitCount = 0;
+        for (int i = 0; i < numberLenReal; i++) {
+            char c = number.charAt(i);
+            if (c == '+') continue;
+            int shift = ((digitCount & 0x01) == 1) ? 4 : 0;
+            result[extraBytes + (digitCount >> 1)] |= (byte)((charToBCD(c) & 0x0F) << shift);
+            digitCount++;
         }
 
-        int size = number.length();
-        int curChar = 0;
-        int countFullBytes = ret.length - offset - 1 - ((size - curChar) & 1);
-        for (int i = 1; i < 1 + countFullBytes; i++) {
-            ret[offset + i]
-                    = (byte) ((charToBCD(number.charAt(curChar++)))
-                              | (charToBCD(number.charAt(curChar++))) << 4);
-        }
+        // 1-fill any trailing odd nibble/quartet.
+        if ((digitCount & 0x01) == 1) result[extraBytes + (digitCount >> 1)] |= 0xF0;
 
-        // The left-over octet for odd-length phone numbers should be
-        // filled with 0xf.
-        if (countFullBytes + offset < ret.length - 1) {
-            ret[ret.length - 1]
-                    = (byte) (charToBCD(number.charAt(curChar))
-                              | (0xf << 4));
-        }
-        return ret;
+        int offset = 0;
+        if (includeLength) result[offset++] = (byte)(resultLen - 1);
+        result[offset] = (byte)(hasPlus ? TOA_International : TOA_Unknown);
+
+        return result;
     }
 
     /** all of 'a' up to len must match non-US trunk prefix ('0') */
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index 346944a..e00ee83 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -377,10 +377,14 @@
             removeMessages(EVENT_RESTORE_DEFAULT_APN);
             setEnabled(type, false);
             if (isApnTypeActive(Phone.APN_TYPE_DEFAULT)) {
+                mRequestedApnType = Phone.APN_TYPE_DEFAULT;
                 if (dataEnabled[APN_DEFAULT_ID]) {
                     return Phone.APN_ALREADY_ACTIVE;
                 } else {
-                    cleanUpConnection(true, Phone.REASON_DATA_DISABLED);
+                    Message msg = obtainMessage(EVENT_CLEAN_UP_CONNECTION);
+                    msg.arg1 = 1; // tearDown is true;
+                    msg.obj = Phone.REASON_DATA_DISABLED;
+                    sendMessage(msg);
                     return Phone.APN_REQUEST_STARTED;
                 }
             } else {
@@ -1235,10 +1239,9 @@
     protected void onRestoreDefaultApn() {
         if (DBG) Log.d(LOG_TAG, "Restore default APN");
         setEnabled(Phone.APN_TYPE_MMS, false);
-
+        mRequestedApnType = Phone.APN_TYPE_DEFAULT;
         if (!isApnTypeActive(Phone.APN_TYPE_DEFAULT)) {
             cleanUpConnection(true, Phone.REASON_RESTORE_DEFAULT_APN);
-            mRequestedApnType = Phone.APN_TYPE_DEFAULT;
         }
     }
 
diff --git a/tests/CoreTests/com/android/internal/telephony/PhoneNumberUtilsTest.java b/tests/CoreTests/com/android/internal/telephony/PhoneNumberUtilsTest.java
index e8bd239..577d384 100644
--- a/tests/CoreTests/com/android/internal/telephony/PhoneNumberUtilsTest.java
+++ b/tests/CoreTests/com/android/internal/telephony/PhoneNumberUtilsTest.java
@@ -93,6 +93,13 @@
             assertEquals(b[i], bRet[i]);
         }
 
+        bRet = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength("+17005550020");
+        assertEquals(8, bRet.length);
+        assertEquals(bRet[0], 7);
+        for (int i = 1; i < 8; i++) {
+            assertEquals(b[i - 1], bRet[i]);
+        }
+
         bRet = PhoneNumberUtils.networkPortionToCalledPartyBCD("7005550020");
         assertEquals("7005550020",
             PhoneNumberUtils.calledPartyBCDToString(bRet, 0, bRet.length));
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/FileList.java b/tests/DumpRenderTree/src/com/android/dumprendertree/FileList.java
index 0218317..e741177 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/FileList.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/FileList.java
@@ -23,7 +23,9 @@
 import java.util.Map;
 import java.io.File;
 
+import android.app.AlertDialog;
 import android.app.ListActivity;
+import android.content.DialogInterface;
 import android.view.KeyEvent;
 import android.view.View;
 import android.widget.ListView;
@@ -31,7 +33,7 @@
 import android.os.Bundle;
 
 
-public abstract class FileList extends ListActivity 
+public abstract class FileList extends ListActivity
 {
 	public boolean onKeyDown(int keyCode, KeyEvent event) {
 		switch (keyCode)
@@ -39,7 +41,7 @@
 			case KeyEvent.KEYCODE_DPAD_LEFT:
 				if (mPath.length() > mBaseLength) {
 					File f = new File(mPath);
-					mFocusFile = f.getName(); 
+					mFocusFile = f.getName();
 					mFocusIndex = 0;
 					f = f.getParentFile();
 					mPath = f.getPath();
@@ -47,7 +49,7 @@
 					return true;
 				}
 				break;
-				
+
 			case KeyEvent.KEYCODE_DPAD_RIGHT:
 				{
 					Map map = (Map) getListView().getItemAtPosition(getListView().getSelectedItemPosition());
@@ -61,24 +63,24 @@
 					}
                     return true;
 				}
-	
+
 			default:
 				break;
 		}
 		return super.onKeyDown(keyCode, event);
 	}
 
-	public void onCreate(Bundle icicle) 
+	public void onCreate(Bundle icicle)
     {
         super.onCreate(icicle);
         setupPath();
         updateList();
     }
-    
+
     protected List getData()
     {
         List myData = new ArrayList<HashMap>();
-        
+
         File f = new File(mPath);
         if (!f.exists()) {
         	addItem(myData, "!LayoutTests path missing!", "");
@@ -103,10 +105,10 @@
 	        	    addItem(myData, files[i], path);
         	}
         }
-        
+
         return myData;
     }
-    
+
     protected void addItem(List<Map> data, String name, String path)
     {
         HashMap temp = new HashMap();
@@ -114,34 +116,58 @@
         temp.put("path", path);
         data.add(temp);
     }
-    
+
     protected void onListItemClick(ListView l, View v, int position, long id)
     {
-    	Map map = (Map) l.getItemAtPosition(position);
-    	String path = (String)map.get("path");
+        Map map = (Map) l.getItemAtPosition(position);
+        final String path = (String)map.get("path");
 
         if ((new File(path)).isDirectory()) {
-            mPath = path;
-            mFocusFile = null;
-            updateList();
+            final CharSequence[] items = {"Open", "Run"};
+            AlertDialog.Builder builder = new AlertDialog.Builder(this);
+            builder.setTitle("Select an Action");
+            builder.setSingleChoiceItems(items, -1,
+                    new DialogInterface.OnClickListener(){
+                public void onClick(DialogInterface dialog, int which) {
+                    switch (which) {
+                        case OPEN_DIRECTORY:
+                            dialog.dismiss();
+                            mPath = path;
+                            mFocusFile = null;
+                            updateList();
+                            break;
+                        case RUN_TESTS:
+                            dialog.dismiss();
+                            processDirectory(path, false);
+                            break;
+                    }
+                }
+            });
+            builder.create().show();
         } else {
             processFile(path, false);
         }
     }
-    
+
+    /*
+     * This function is called when the user has selected a directory in the
+     * list and wants to perform an action on it instead of navigating into
+     * the directory.
+     */
+    abstract void processDirectory(String path, boolean selection);
     /*
      * This function is called when the user has selected a file in the
      * file list. The selected file could be a file or a directory.
      * The flag indicates if this was from a selection or not.
      */
     abstract void processFile(String filename, boolean selection);
-    
+
     /*
      * This function is called when the file list is being built. Return
      * true if the file is to be added to the file list.
      */
     abstract boolean fileFilter(File f);
-    
+
     protected void updateList() {
         setListAdapter(new SimpleAdapter(this,
                 getData(),
@@ -152,16 +178,19 @@
         setTitle(title);
         getListView().setSelection(mFocusIndex);
     }
-    
-    protected void setupPath() 
+
+    protected void setupPath()
     {
     	mPath = "/sdcard/android/layout_tests";
     	mBaseLength = mPath.length();
     }
-    
+
     protected String mPath;
     protected int mBaseLength;
     protected String mFocusFile;
     protected int mFocusIndex;
-  
+    
+    private final static int OPEN_DIRECTORY = 0;
+    private final static int RUN_TESTS = 1;
+
 }
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/FsUtils.java b/tests/DumpRenderTree/src/com/android/dumprendertree/FsUtils.java
new file mode 100644
index 0000000..cc2f1f5
--- /dev/null
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/FsUtils.java
@@ -0,0 +1,80 @@
+package com.android.dumprendertree;
+
+import android.util.Log;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+
+public class FsUtils {
+
+    private static final String LOGTAG = "FsUtils";
+    private FsUtils() {
+        //no creation of instances
+    }
+
+    public static void findLayoutTestsRecursively(BufferedOutputStream bos,
+            String dir) throws IOException {
+        Log.v(LOGTAG, "Searching tests under " + dir);
+
+        File d = new File(dir);
+        if (!d.isDirectory()) {
+            throw new AssertionError("A directory expected, but got " + dir);
+        }
+
+        String[] files = d.list();
+        for (int i = 0; i < files.length; i++) {
+            String s = dir + "/" + files[i];
+            if (FileFilter.ignoreTest(s)) {
+                Log.v(LOGTAG, "  Ignoring: " + s);
+                continue;
+            }
+            if (s.toLowerCase().endsWith(".html")
+                    || s.toLowerCase().endsWith(".xml")) {
+                bos.write(s.getBytes());
+                bos.write('\n');
+                continue;
+            }
+
+            File f = new File(s);
+            if (f.isDirectory()) {
+                findLayoutTestsRecursively(bos, s);
+                continue;
+            }
+
+            Log.v(LOGTAG, "Skipping " + s);
+        }
+    }
+
+    public static void updateTestStatus(String statusFile, String s) {
+        try {
+            BufferedOutputStream bos = new BufferedOutputStream(
+                    new FileOutputStream(statusFile));
+            bos.write(s.getBytes());
+            bos.close();
+        } catch (Exception e) {
+            Log.e(LOGTAG, "Cannot update file " + statusFile);
+        }
+    }
+
+    public static String readTestStatus(String statusFile) {
+        // read out the test name it stopped last time.
+        String status = null;
+        File testStatusFile = new File(statusFile);
+        if(testStatusFile.exists()) {
+            try {
+                BufferedReader inReader = new BufferedReader(
+                        new FileReader(testStatusFile));
+                status = inReader.readLine();
+                inReader.close();
+            } catch (IOException e) {
+                Log.e(LOGTAG, "Error reading test status.", e);
+            }
+        }
+        return status;
+    }
+
+}
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java
index f169a26..a03490d 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java
@@ -178,15 +178,13 @@
     private void resumeTestList() {
         // read out the test name it stoped last time.
         try {
-            BufferedReader inReader = new BufferedReader(new FileReader(TEST_STATUS_FILE));
-            String line = inReader.readLine();
+            String line = FsUtils.readTestStatus(TEST_STATUS_FILE);
             for (int i = 0; i < mTestList.size(); i++) {
                 if (mTestList.elementAt(i).equals(line)) {
                     mTestList = new Vector<String>(mTestList.subList(i+1, mTestList.size()));
                     break;
                 }
             }
-            inReader.close();
         } catch (Exception e) {
             Log.e(LOGTAG, "Error reading " + TEST_STATUS_FILE);
         }
@@ -204,18 +202,7 @@
             Log.e(LOGTAG, "Fail to delete " + TEST_STATUS_FILE + " : " + e.getMessage());
         }
     }
-  
-    private void updateTestStatus(String s) {
-        // Write TEST_STATUS_FILE
-        try {
-            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(TEST_STATUS_FILE));
-            bos.write(s.getBytes());
-            bos.close();
-        } catch (Exception e) {
-            Log.e(LOGTAG, "Cannot update file " + TEST_STATUS_FILE);
-        }
-    }
-    
+
     private String getResultFile(String test) {
         String shortName = test.substring(0, test.lastIndexOf('.'));
         // Write actual results to result directory.
@@ -392,12 +379,12 @@
         // Run tests.
         for (int i = 0; i < mTestList.size(); i++) {
             String s = mTestList.elementAt(i);
-            updateTestStatus(s);
+            FsUtils.updateTestStatus(TEST_STATUS_FILE, s);
             // Run tests
             runTestAndWaitUntilDone(activity, s, runner.mTimeoutInMillis);
         }
 
-        updateTestStatus("#DONE");
+        FsUtils.updateTestStatus(TEST_STATUS_FILE, "#DONE");
         
         activity.finish();
     }
@@ -424,7 +411,7 @@
         try {
             File tests_list = new File(LAYOUT_TESTS_LIST_FILE);
             BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tests_list, false));
-            findTestsRecursively(bos, getTestPath());
+            FsUtils.findLayoutTestsRecursively(bos, getTestPath());
             bos.flush();
             bos.close();
        } catch (Exception e) {
@@ -432,38 +419,6 @@
        }
     }
 
-    private void findTestsRecursively(BufferedOutputStream bos, String dir) throws IOException {
-         Log.v(LOGTAG, "Searching tests under " + dir);
-         
-         File d = new File(dir);
-         if (!d.isDirectory()) {
-             throw new AssertionError("A directory expected, but got " + dir);
-         }
-         
-         String[] files = d.list();
-         for (int i = 0; i < files.length; i++) {
-             String s = dir + "/" + files[i];
-             if (FileFilter.ignoreTest(s)) {
-                 Log.v(LOGTAG, "  Ignoring: " + s);
-                 continue;
-             }
-             if (s.toLowerCase().endsWith(".html") 
-                 || s.toLowerCase().endsWith(".xml")) {
-                 bos.write(s.getBytes());
-                 bos.write('\n');
-                 continue;
-             }
-             
-             File f = new File(s);
-             if (f.isDirectory()) {
-                 findTestsRecursively(bos, s);
-                 continue;
-             }
-             
-             Log.v(LOGTAG, "Skipping " + s);
-        }
-    }
-    
     // Running all the layout tests at once sometimes
     // causes the dumprendertree to run out of memory.
     // So, additional tests are added to run the tests
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/Menu.java b/tests/DumpRenderTree/src/com/android/dumprendertree/Menu.java
index 00e0f89..e15ab65 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/Menu.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/Menu.java
@@ -17,19 +17,23 @@
 package com.android.dumprendertree;
 
 import android.content.Intent;
-import android.net.Uri;
 import android.os.Bundle;
 import android.util.Log;
 
+import java.io.BufferedOutputStream;
 import java.io.File;
+import java.io.FileOutputStream;
 
 public class Menu extends FileList {
-    
-    public void onCreate(Bundle icicle) 
-    {
+
+    private static final int MENU_START = 0x01;
+    private static String LOGTAG = "MenuActivity";
+    static final String LAYOUT_TESTS_LIST_FILE = "/sdcard/android/layout_tests_list.txt";
+
+    public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
     }
-    
+
     boolean fileFilter(File f) {
     	if (f.getName().startsWith("."))
     		return false;
@@ -41,14 +45,36 @@
     		return true;
     	return false;
     }
-    
-    void processFile(String filename, boolean selection)
-    {        
+
+    void processFile(String filename, boolean selection) {
         Intent intent = new Intent(Intent.ACTION_VIEW);
         intent.setClass(this, TestShellActivity.class);
         intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
         intent.putExtra(TestShellActivity.TEST_URL, "file://" + filename);
         startActivity(intent);
     }
+
+    @Override
+    void processDirectory(String path, boolean selection) {
+        generateTestList(path);
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.setClass(this, TestShellActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        intent.putExtra(TestShellActivity.UI_AUTO_TEST, LAYOUT_TESTS_LIST_FILE);
+        startActivity(intent);
+    }
+
+    private void generateTestList(String path) {
+        try {
+            File tests_list = new File(LAYOUT_TESTS_LIST_FILE);
+            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tests_list, false));
+            FsUtils.findLayoutTestsRecursively(bos, path);
+            bos.flush();
+            bos.close();
+       } catch (Exception e) {
+           Log.e(LOGTAG, "Error when creating test list: " + e.getMessage());
+       }
+    }
+
 }
 
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java b/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java
index 16973be..de39800 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java
@@ -45,7 +45,7 @@
 
         //always try to resume first, hence cleaning up status will be the
         //responsibility of driver scripts
-        String lastUrl = readTestStatus();
+        String lastUrl = FsUtils.readTestStatus(TEST_STATUS_FILE);
         if(lastUrl != null && !TEST_DONE.equals(lastUrl))
             fastForward(listReader, lastUrl);
 
@@ -62,7 +62,7 @@
                 continue;
             start = System.currentTimeMillis();
             Log.v(LOGTAG, "Testing URL: " + url);
-            updateTestStatus(url);
+            FsUtils.updateTestStatus(TEST_STATUS_FILE, url);
             activity.reset();
             //use message to send new URL to avoid interacting with
             //WebView in non-UI thread
@@ -92,7 +92,7 @@
             System.gc();
             System.gc();
         }
-        updateTestStatus(TEST_DONE);
+        FsUtils.updateTestStatus(TEST_STATUS_FILE, TEST_DONE);
         activity.finish();
         listReader.close();
     }
@@ -122,35 +122,6 @@
         }
     }
 
-    private void updateTestStatus(String s) {
-        // write last tested url into status file
-        try {
-            BufferedOutputStream bos = new BufferedOutputStream(
-                    new FileOutputStream(TEST_STATUS_FILE));
-            bos.write(s.getBytes());
-            bos.close();
-        } catch (IOException e) {
-            Log.e(LOGTAG, "Cannot update file " + TEST_STATUS_FILE, e);
-        }
-    }
-
-    private String readTestStatus() {
-        // read out the test name it stopped last time.
-        String status = null;
-        File testStatusFile = new File(TEST_STATUS_FILE);
-        if(testStatusFile.exists()) {
-            try {
-                BufferedReader inReader = new BufferedReader(
-                        new FileReader(testStatusFile));
-                status = inReader.readLine();
-                inReader.close();
-            } catch (IOException e) {
-                Log.e(LOGTAG, "Error reading test status.", e);
-            }
-        }
-        return status;
-    }
-
     private void fastForward(BufferedReader testListReader, String lastUrl) {
         //fastforward the BufferedReader to the position right after last url
         if(lastUrl == null)
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
index 1ba291c9..0d22eca 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
@@ -17,7 +17,10 @@
 package com.android.dumprendertree;
 
 import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.DialogInterface.OnClickListener;
 import android.graphics.Bitmap;
 import android.net.http.SslError;
 import android.os.Bundle;
@@ -35,21 +38,24 @@
 import android.webkit.WebViewClient;
 import android.widget.LinearLayout;
 
+import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileOutputStream;
+import java.io.FileReader;
 import java.io.IOException;
 import java.util.Vector;
 
 public class TestShellActivity extends Activity implements LayoutTestController {
-    
+
     static enum DumpDataType {DUMP_AS_TEXT, EXT_REPR, NO_OP}
-    
+
     public class AsyncHandler extends Handler {
         @Override
         public void handleMessage(Message msg) {
             if (msg.what == MSG_TIMEOUT) {
                 mTimedOut = true;
-                mCallback.timedOut(mWebView.getUrl());
+                if(mCallback != null)
+                    mCallback.timedOut(mWebView.getUrl());
                 requestWebKitData();
                 return;
             } else if (msg.what == MSG_WEBKIT_DATA) {
@@ -63,10 +69,10 @@
 
     public void requestWebKitData() {
         Message callback = mHandler.obtainMessage(MSG_WEBKIT_DATA);
-        
+
         if (mRequestedWebKitData)
             throw new AssertionError("Requested webkit data twice: " + mWebView.getUrl());
-        
+
         mRequestedWebKitData = true;
         switch (mDumpDataType) {
             case DUMP_AS_TEXT:
@@ -79,12 +85,12 @@
                 finished();
                 break;
         }
-    } 
+    }
 
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
-        
+
         LinearLayout contentView = new LinearLayout(this);
         contentView.setOrientation(LinearLayout.VERTICAL);
         setContentView(contentView);
@@ -133,59 +139,122 @@
         mWebView.addJavascriptInterface(mCallbackProxy, "layoutTestController");
         mWebView.addJavascriptInterface(mCallbackProxy, "eventSender");
         contentView.addView(mWebView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT, 0.0f));
- 
+
         mWebView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
-            
+
         mHandler = new AsyncHandler();
-        
+
         Intent intent = getIntent();
         if (intent != null) {
             executeIntent(intent);
         }
     }
-    
+
     @Override
     protected void onNewIntent(Intent intent) {
         super.onNewIntent(intent);
         executeIntent(intent);
     }
-    
+
     private void executeIntent(Intent intent) {
         resetTestStatus();
         if (!Intent.ACTION_VIEW.equals(intent.getAction())) {
             return;
         }
-        
+
         mTestUrl = intent.getStringExtra(TEST_URL);
-        if (mTestUrl == null)
+        if (mTestUrl == null) {
+            mUiAutoTestPath = intent.getStringExtra(UI_AUTO_TEST);
+            if(mUiAutoTestPath != null) {
+                beginUiAutoTest();
+            }
             return;
-        
+        }
+
         mResultFile = intent.getStringExtra(RESULT_FILE);
         mTimeoutInMillis = intent.getIntExtra(TIMEOUT_IN_MILLIS, 0);
 
         Log.v(LOGTAG, "  Loading " + mTestUrl);
         mWebView.loadUrl(mTestUrl);
-            
+
         if (mTimeoutInMillis > 0) {
             // Create a timeout timer
             Message m = mHandler.obtainMessage(MSG_TIMEOUT);
             mHandler.sendMessageDelayed(m, mTimeoutInMillis);
         }
     }
-    
+
+    private void beginUiAutoTest() {
+        try {
+            mTestListReader = new BufferedReader(
+                    new FileReader(mUiAutoTestPath));
+        } catch (IOException ioe) {
+            Log.e(LOGTAG, "Failed to open test list for read.", ioe);
+            finishUiAutoTest();
+            return;
+        }
+        moveToNextTest();
+    }
+
+    private void finishUiAutoTest() {
+        try {
+            if(mTestListReader != null)
+                mTestListReader.close();
+        } catch (IOException ioe) {
+            Log.w(LOGTAG, "Failed to close test list file.", ioe);
+        }
+        finished();
+    }
+
+    private void moveToNextTest() {
+        String url = null;
+        try {
+            url = mTestListReader.readLine();
+        } catch (IOException ioe) {
+            Log.e(LOGTAG, "Failed to read next test.", ioe);
+            finishUiAutoTest();
+            return;
+        }
+        if (url == null) {
+            mUiAutoTestPath = null;
+            finishUiAutoTest();
+            AlertDialog.Builder builder = new AlertDialog.Builder(this);
+            builder.setMessage("All tests finished. Exit?")
+                   .setCancelable(false)
+                   .setPositiveButton("Yes", new OnClickListener(){
+                       public void onClick(DialogInterface dialog, int which) {
+                           TestShellActivity.this.finish();
+                       }
+                   })
+                   .setNegativeButton("No", new OnClickListener(){
+                       public void onClick(DialogInterface dialog, int which) {
+                           dialog.cancel();
+                       }
+                   });
+            builder.create().show();
+            return;
+        }
+        url = "file://" + url;
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        intent.putExtra(TestShellActivity.TEST_URL, url);
+        intent.putExtra(TIMEOUT_IN_MILLIS, 10000);
+        executeIntent(intent);
+    }
+
     @Override
     protected void onStop() {
         super.onStop();
         mWebView.stopLoading();
     }
-    
+
     @Override
     protected void onDestroy() {
         super.onDestroy();
         mWebView.destroy();
         mWebView = null;
     }
-    
+
     @Override
     public void onLowMemory() {
         super.onLowMemory();
@@ -199,13 +268,13 @@
             finished();
             return;
         }
-        
+
         try {
             File parentDir = new File(mResultFile).getParentFile();
             if (!parentDir.exists()) {
                 parentDir.mkdirs();
             }
-            
+
             FileOutputStream os = new FileOutputStream(mResultFile);
             if (timeout) {
                 Log.w("Layout test: Timeout", mResultFile);
@@ -222,22 +291,27 @@
             os.flush();
             os.close();
         } catch (IOException ex) {
-            Log.e(LOGTAG, "Cannot write to " + mResultFile + ", " + ex.getMessage());          
+            Log.e(LOGTAG, "Cannot write to " + mResultFile + ", " + ex.getMessage());
         }
 
         finished();
     }
-    
+
     public void setCallback(TestShellCallback callback) {
         mCallback = callback;
     }
-    
+
     public void finished() {
-        if (mCallback != null) {
-            mCallback.finished();
+        if (mUiAutoTestPath != null) {
+            //don't really finish here
+            moveToNextTest();
+        } else {
+            if (mCallback != null) {
+                mCallback.finished();
+            }
         }
     }
-   
+
     public void setDefaultDumpDataType(DumpDataType defaultDumpDataType) {
         mDefaultDumpDataType = defaultDumpDataType;
     }
@@ -257,7 +331,7 @@
         String url = mWebView.getUrl();
         Log.v(LOGTAG, "waitUntilDone called: " + url);
     }
-    
+
     public void notifyDone() {
         String url = mWebView.getUrl();
         Log.v(LOGTAG, "notifyDone called: " + url);
@@ -266,7 +340,7 @@
             mChromeClient.onProgressChanged(mWebView, 100);
         }
     }
-    
+
     public void display() {
         mWebView.invalidate();
     }
@@ -332,7 +406,7 @@
     }
 
     public void queueScript(String scriptToRunInCurrentContext) {
-        mWebView.loadUrl("javascript:"+scriptToRunInCurrentContext);     
+        mWebView.loadUrl("javascript:"+scriptToRunInCurrentContext);
     }
 
     public void repaintSweepHorizontally() {
@@ -359,7 +433,7 @@
     public void testRepaint() {
         mWebView.invalidate();
     }
-    
+
     private final WebChromeClient mChromeClient = new WebChromeClient() {
         @Override
         public void onProgressChanged(WebView view, int newProgress) {
@@ -406,7 +480,7 @@
             result.confirm();
             return true;
         }
-        
+
         @Override
         public boolean onJsConfirm(WebView view, String url, String message,
                 JsResult result) {
@@ -419,7 +493,7 @@
             result.confirm();
             return true;
         }
-        
+
         @Override
         public boolean onJsPrompt(WebView view, String url, String message,
                 String defaultValue, JsPromptResult result) {
@@ -435,7 +509,7 @@
             return true;
         }
     };
-    
+
     private void resetTestStatus() {
         mWaitUntilDone = false;
         mDumpDataType = mDefaultDumpDataType;
@@ -444,17 +518,19 @@
         mRequestedWebKitData = false;
         mEventSender.resetMouse();
     }
-    
+
     private WebView mWebView;
     private WebViewEventSender mEventSender;
     private AsyncHandler mHandler;
     private TestShellCallback mCallback;
 
     private CallbackProxy mCallbackProxy;
-        
+
     private String mTestUrl;
     private String mResultFile;
     private int mTimeoutInMillis;
+    private String mUiAutoTestPath;
+    private BufferedReader mTestListReader;
 
     // States
     private boolean mTimedOut;
@@ -472,13 +548,14 @@
     private Vector mWebHistory;
 
     static final String TIMEOUT_STR = "**Test timeout";
-    
+
     static final int MSG_TIMEOUT = 0;
     static final int MSG_WEBKIT_DATA = 1;
 
     static final String LOGTAG="TestShell";
-    
+
     static final String TEST_URL = "TestUrl";
     static final String RESULT_FILE = "ResultFile";
     static final String TIMEOUT_IN_MILLIS = "TimeoutInMillis";
+    static final String UI_AUTO_TEST = "UiAutoTest";
 }