Merge "InputDevice filtering for jumpy screens. Updated ScaleGestureDetector for framework deprecations."
diff --git a/api/current.xml b/api/current.xml
index 1f7be9e..eae20e7 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -24220,6 +24220,17 @@
  visibility="public"
 >
 </field>
+<field name="CURSOR_EXTRA_KEY_IN_PROGRESS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;in_progress&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="EXTRA_DATA_KEY"
  type="java.lang.String"
  transient="false"
diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c
index a447f53..c8d1397 100644
--- a/cmds/installd/commands.c
+++ b/cmds/installd/commands.c
@@ -674,14 +674,19 @@
     return 0;
 }
 
-void mkinnerdirs(char* path, int basepos, mode_t mode, int uid, int gid)
+void mkinnerdirs(char* path, int basepos, mode_t mode, int uid, int gid,
+        struct stat* statbuf)
 {
     while (path[basepos] != 0) {
         if (path[basepos] == '/') {
             path[basepos] = 0;
-            LOGI("Making directory: %s\n", path);
-            if (mkdir(path, mode) == 0) {
-                chown(path, uid, gid);
+            if (lstat(path, statbuf) < 0) {
+                LOGI("Making directory: %s\n", path);
+                if (mkdir(path, mode) == 0) {
+                    chown(path, uid, gid);
+                } else {
+                    LOGW("Unable to make directory %s: %s\n", path, strerror(errno));
+                }
             }
             path[basepos] = '/';
             basepos++;
@@ -690,8 +695,8 @@
     }
 }
 
-int movefileordir(char* srcpath, char* dstpath, int dstuid, int dstgid,
-        struct stat* statbuf)
+int movefileordir(char* srcpath, char* dstpath, int dstbasepos,
+        int dstuid, int dstgid, struct stat* statbuf)
 {
     DIR *d;
     struct dirent *de;
@@ -706,8 +711,9 @@
     }
     
     if ((statbuf->st_mode&S_IFDIR) == 0) {
+        mkinnerdirs(dstpath, dstbasepos, S_IRWXU|S_IRWXG|S_IXOTH,
+                dstuid, dstgid, statbuf);
         LOGI("Renaming %s to %s (uid %d)\n", srcpath, dstpath, dstuid);
-        mkinnerdirs(dstpath, dstend-1, S_IRWXU|S_IRWXG|S_IXOTH, dstuid, dstgid);
         if (rename(srcpath, dstpath) >= 0) {
             if (chown(dstpath, dstuid, dstgid) < 0) {
                 LOGE("cannot chown %s: %s\n", dstpath, strerror(errno));
@@ -752,7 +758,7 @@
         strcpy(srcpath+srcend+1, name);
         strcpy(dstpath+dstend+1, name);
         
-        if (movefileordir(srcpath, dstpath, dstuid, dstgid, statbuf) != 0) {
+        if (movefileordir(srcpath, dstpath, dstbasepos, dstuid, dstgid, statbuf) != 0) {
             res = 1;
         }
         
@@ -834,7 +840,9 @@
                             LOGV("Move file: %s (from %s to %s)\n", buf+bufp, srcpkg, dstpkg);
                             if (!create_move_path(srcpath, PKG_DIR_PREFIX, srcpkg, buf+bufp) &&
                                     !create_move_path(dstpath, PKG_DIR_PREFIX, dstpkg, buf+bufp)) {
-                                movefileordir(srcpath, dstpath, dstuid, dstgid, &s);
+                                movefileordir(srcpath, dstpath,
+                                        strlen(dstpath)-strlen(buf+bufp),
+                                        dstuid, dstgid, &s);
                             }
                         }
                     } else {
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 67f9629..0ed572a 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -1300,6 +1300,14 @@
     public final static String EXTRA_SELECT_QUERY = "select_query";
 
     /**
+     * Boolean extra data key for a suggestion provider to return in {@link Cursor#getExtras} to
+     * indicate that the search is not complete yet. This can be used by the search UI
+     * to indicate that a search is in progress. The suggestion provider can return partial results
+     * this way and send a change notification on the cursor when more results are available.
+     */
+    public final static String CURSOR_EXTRA_KEY_IN_PROGRESS = "in_progress";
+
+    /**
      * Intent extra data key: Use this key with Intent.ACTION_SEARCH and
      * {@link android.content.Intent#getStringExtra content.Intent.getStringExtra()}
      * to obtain the action message that was defined for a particular search action key and/or
diff --git a/core/java/android/app/SuggestionsAdapter.java b/core/java/android/app/SuggestionsAdapter.java
index 07e9793..3e11a3f 100644
--- a/core/java/android/app/SuggestionsAdapter.java
+++ b/core/java/android/app/SuggestionsAdapter.java
@@ -159,19 +159,23 @@
          * for in app search we show the progress spinner until the cursor is returned with
          * the results.
          */
+        Cursor cursor = null;
         mSearchDialog.getWindow().getDecorView().post(mStartSpinnerRunnable);
         try {
-            final Cursor cursor = mSearchManager.getSuggestions(mSearchable, query, QUERY_LIMIT);
+            cursor = mSearchManager.getSuggestions(mSearchable, query, QUERY_LIMIT);
             // trigger fill window so the spinner stays up until the results are copied over and
             // closer to being ready
-            if (cursor != null) cursor.getCount();
-            return cursor;
+            if (cursor != null) {
+                cursor.getCount();
+                return cursor;
+            }
         } catch (RuntimeException e) {
             Log.w(LOG_TAG, "Search suggestions query threw an exception.", e);
-            return null;
-        } finally {
-            mSearchDialog.getWindow().getDecorView().post(mStopSpinnerRunnable);
         }
+        // If cursor is null or an exception was thrown, stop the spinner and return null.
+        // changeCursor doesn't get called if cursor is null
+        mSearchDialog.getWindow().getDecorView().post(mStopSpinnerRunnable);
+        return null;
     }
 
     public void close() {
@@ -180,6 +184,39 @@
         mClosed = true;
     }
 
+    @Override
+    public void notifyDataSetChanged() {
+        if (DBG) Log.d(LOG_TAG, "notifyDataSetChanged");
+        super.notifyDataSetChanged();
+
+        updateSpinnerState(getCursor());
+    }
+
+    @Override
+    public void notifyDataSetInvalidated() {
+        if (DBG) Log.d(LOG_TAG, "notifyDataSetInvalidated");
+        super.notifyDataSetInvalidated();
+
+        updateSpinnerState(getCursor());
+    }
+
+    private void updateSpinnerState(Cursor cursor) {
+        if (DBG) {
+            Log.d(LOG_TAG, "updateSpinnerState - extra = "
+                + (cursor != null
+                        ? cursor.getExtras().getBoolean(SearchManager.CURSOR_EXTRA_KEY_IN_PROGRESS)
+                        : null));
+        }
+        // Check if the Cursor indicates that the query is not complete and show the spinner
+        if (cursor != null
+                && cursor.getExtras().getBoolean(SearchManager.CURSOR_EXTRA_KEY_IN_PROGRESS)) {
+            mSearchDialog.getWindow().getDecorView().post(mStartSpinnerRunnable);
+            return;
+        }
+        // If cursor is null or is done, stop the spinner
+        mSearchDialog.getWindow().getDecorView().post(mStopSpinnerRunnable);
+    }
+
     /**
      * Cache columns.
      */
@@ -202,7 +239,8 @@
                 mText2UrlCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2_URL);
                 mIconName1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1);
                 mIconName2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2);
-                mBackgroundColorCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_BACKGROUND_COLOR);
+                mBackgroundColorCol =
+                        c.getColumnIndex(SearchManager.SUGGEST_COLUMN_BACKGROUND_COLOR);
             }
         } catch (Exception e) {
             Log.e(LOG_TAG, "error changing cursor and caching columns", e);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index b07bafcf..7a0337cd 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -967,8 +967,11 @@
                 String orig =sa.getNonResourceString(
                         com.android.internal.R.styleable.AndroidManifestOriginalPackage_name);
                 if (!pkg.packageName.equals(orig)) {
-                    pkg.mOriginalPackage = orig;
-                    pkg.mRealPackage = pkg.packageName;
+                    if (pkg.mOriginalPackages == null) {
+                        pkg.mOriginalPackages = new ArrayList<String>();
+                        pkg.mRealPackage = pkg.packageName;
+                    }
+                    pkg.mOriginalPackages.add(orig);
                 }
 
                 sa.recycle();
@@ -2579,7 +2582,7 @@
         public ArrayList<String> usesOptionalLibraries = null;
         public String[] usesLibraryFiles = null;
 
-        public String mOriginalPackage = null;
+        public ArrayList<String> mOriginalPackages = null;
         public String mRealPackage = null;
         public ArrayList<String> mAdoptPermissions = null;
         
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index c76aca1..a6668e7 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -364,7 +364,7 @@
     /**
      * Sets the persisted value for enabling/disabling Mobile data.
      *
-     * @param allowMobileData Whether the mobile data connection should be
+     * @param enabled Whether the mobile data connection should be
      *            used or not.
      * @hide
      */
@@ -418,22 +418,35 @@
     /**
      * {@hide}
      */
-    public boolean tether(String iface) {
+    public String[] getTetheringErroredIfaces() {
         try {
-            return mService.tether(iface);
+            return mService.getTetheringErroredIfaces();
         } catch (RemoteException e) {
-            return false;
+            return new String[0];
         }
     }
 
     /**
+     * @return error A TETHER_ERROR value indicating success or failure type
      * {@hide}
      */
-    public boolean untether(String iface) {
+    public int tether(String iface) {
+        try {
+            return mService.tether(iface);
+        } catch (RemoteException e) {
+            return TETHER_ERROR_SERVICE_UNAVAIL;
+        }
+    }
+
+    /**
+     * @return error A TETHER_ERROR value indicating success or failure type
+     * {@hide}
+     */
+    public int untether(String iface) {
         try {
             return mService.untether(iface);
         } catch (RemoteException e) {
-            return false;
+            return TETHER_ERROR_SERVICE_UNAVAIL;
         }
     }
 
@@ -469,4 +482,41 @@
             return new String[0];
         }
     }
+
+    /** {@hide} */
+    public static final int TETHER_ERROR_NO_ERROR           = 0;
+    /** {@hide} */
+    public static final int TETHER_ERROR_UNKNOWN_IFACE      = 1;
+    /** {@hide} */
+    public static final int TETHER_ERROR_SERVICE_UNAVAIL    = 2;
+    /** {@hide} */
+    public static final int TETHER_ERROR_UNSUPPORTED        = 3;
+    /** {@hide} */
+    public static final int TETHER_ERROR_UNAVAIL_IFACE      = 4;
+    /** {@hide} */
+    public static final int TETHER_ERROR_MASTER_ERROR       = 5;
+    /** {@hide} */
+    public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6;
+    /** {@hide} */
+    public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7;
+    /** {@hide} */
+    public static final int TETHER_ERROR_ENABLE_NAT_ERROR     = 8;
+    /** {@hide} */
+    public static final int TETHER_ERROR_DISABLE_NAT_ERROR    = 9;
+    /** {@hide} */
+    public static final int TETHER_ERROR_IFACE_CFG_ERROR      = 10;
+
+    /**
+     * @param iface The name of the interface we're interested in
+     * @return error The error code of the last error tethering or untethering the named
+     *               interface
+     * {@hide}
+     */
+    public int getLastTetherError(String iface) {
+        try {
+            return mService.getLastTetherError(iface);
+        } catch (RemoteException e) {
+            return TETHER_ERROR_SERVICE_UNAVAIL;
+        }
+   }
 }
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 2514693..b05c2ed 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -55,9 +55,11 @@
 
     void setMobileDataEnabled(boolean enabled);
 
-    boolean tether(String iface);
+    int tether(String iface);
 
-    boolean untether(String iface);
+    int untether(String iface);
+
+    int getLastTetherError(String iface);
 
     boolean isTetheringSupported();
 
@@ -65,6 +67,8 @@
 
     String[] getTetheredIfaces();
 
+    String[] getTetheringErroredIfaces();
+
     String[] getTetherableUsbRegexs();
 
     String[] getTetherableWifiRegexs();
diff --git a/core/java/android/widget/ExpandableListConnector.java b/core/java/android/widget/ExpandableListConnector.java
index 9c43e9b..01d3a4a 100644
--- a/core/java/android/widget/ExpandableListConnector.java
+++ b/core/java/android/widget/ExpandableListConnector.java
@@ -67,7 +67,7 @@
     private int mMaxExpGroupCount = Integer.MAX_VALUE;
 
     /** Change observer used to have ExpandableListAdapter changes pushed to us */
-    private DataSetObserver mDataSetObserver = new MyDataSetObserver();
+    private final DataSetObserver mDataSetObserver = new MyDataSetObserver();
 
     /**
      * Constructs the connector
@@ -849,7 +849,7 @@
      * position to either a) group position for groups, or b) child position for
      * children
      */
-    static class GroupMetadata implements Parcelable, Comparable {
+    static class GroupMetadata implements Parcelable, Comparable<GroupMetadata> {
         final static int REFRESH = -1;
         
         /** This group's flat list position */
@@ -885,12 +885,12 @@
             return gm;
         }
         
-        public int compareTo(Object another) {
-            if (another == null || !(another instanceof GroupMetadata)) {
-                throw new ClassCastException();
+        public int compareTo(GroupMetadata another) {
+            if (another == null) {
+                throw new IllegalArgumentException();
             }
             
-            return gPos - ((GroupMetadata) another).gPos;
+            return gPos - another.gPos;
         }
 
         public int describeContents() {
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index 9cc8bd5..8bd797b 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -389,7 +389,8 @@
         // Only proceed as possible child if the divider isn't above all items (if it is above
         // all items, then the item below it has to be a group)
         if (flatListPosition >= 0) {
-            PositionMetadata pos = mConnector.getUnflattenedPos(flatListPosition);
+            final int adjustedPosition = getFlatPositionForConnector(flatListPosition);
+            PositionMetadata pos = mConnector.getUnflattenedPos(adjustedPosition);
             // If this item is a child, or it is a non-empty group that is expanded
             if ((pos.position.type == ExpandableListPosition.CHILD) || (pos.isExpanded() &&
                     pos.groupMetadata.lastChildFlPos != pos.groupMetadata.flPos)) {
@@ -482,11 +483,37 @@
         return mAdapter;
     }
     
+    /**
+     * @param position An absolute (including header and footer) flat list position.
+     * @return true if the position corresponds to a header or a footer item.
+     */
     private boolean isHeaderOrFooterPosition(int position) {
         final int footerViewsStart = mItemCount - getFooterViewsCount();
         return (position < getHeaderViewsCount() || position >= footerViewsStart);
     }
 
+    /**
+     * Converts an absolute item flat position into a group/child flat position, shifting according
+     * to the number of header items.
+     * 
+     * @param flatListPosition The absolute flat position
+     * @return A group/child flat position as expected by the connector.
+     */
+    private int getFlatPositionForConnector(int flatListPosition) {
+        return flatListPosition - getHeaderViewsCount();
+    }
+
+    /**
+     * Converts a group/child flat position into an absolute flat position, that takes into account
+     * the possible headers.
+     * 
+     * @param flatListPosition The child/group flat position
+     * @return An absolute flat position.
+     */
+    private int getAbsoluteFlatPosition(int flatListPosition) {
+        return flatListPosition + getHeaderViewsCount();
+    }
+
     @Override
     public boolean performItemClick(View v, int position, long id) {
         // Ignore clicks in header/footers
@@ -496,8 +523,8 @@
         }
         
         // Internally handle the item click
-        final int headerViewsCount = getHeaderViewsCount();
-        return handleItemClick(v, position - headerViewsCount, id);
+        final int adjustedPosition = getFlatPositionForConnector(position);
+        return handleItemClick(v, adjustedPosition, id);
     }
     
     /**
@@ -711,8 +738,8 @@
             return PACKED_POSITION_VALUE_NULL;
         }
 
-        final int shiftedPosition = flatListPosition - getHeaderViewsCount();
-        PositionMetadata pm = mConnector.getUnflattenedPos(shiftedPosition);
+        final int adjustedPosition = getFlatPositionForConnector(flatListPosition);
+        PositionMetadata pm = mConnector.getUnflattenedPos(adjustedPosition);
         long packedPos = pm.position.getPackedPosition();
         pm.recycle();
         return packedPos;
@@ -732,9 +759,9 @@
     public int getFlatListPosition(long packedPosition) {
         PositionMetadata pm = mConnector.getFlattenedPos(ExpandableListPosition
                 .obtainPosition(packedPosition));
-        int retValue = pm.position.flatListPos;
+        final int flatListPosition = pm.position.flatListPos;
         pm.recycle();
-        return retValue + getHeaderViewsCount();
+        return getAbsoluteFlatPosition(flatListPosition);
     }
 
     /**
@@ -783,7 +810,8 @@
                 .obtainGroupPosition(groupPosition);
         PositionMetadata pm = mConnector.getFlattenedPos(elGroupPos);
         elGroupPos.recycle();
-        super.setSelection(pm.position.flatListPos);
+        final int absoluteFlatPosition = getAbsoluteFlatPosition(pm.position.flatListPos);
+        super.setSelection(absoluteFlatPosition);
         pm.recycle();
     }
     
@@ -819,7 +847,8 @@
             }
         }
         
-        super.setSelection(flatChildPos.position.flatListPos);
+        int absoluteFlatPosition = getAbsoluteFlatPosition(flatChildPos.position.flatListPos);
+        super.setSelection(absoluteFlatPosition);
         
         elChildPos.recycle();
         flatChildPos.recycle();
@@ -937,7 +966,7 @@
             return new AdapterContextMenuInfo(view, flatListPosition, id);
         }
 
-        final int adjustedPosition = flatListPosition - getHeaderViewsCount();
+        final int adjustedPosition = getFlatPositionForConnector(flatListPosition);
         PositionMetadata pm = mConnector.getUnflattenedPos(adjustedPosition);
         ExpandableListPosition pos = pm.position;
         pm.recycle();
diff --git a/core/java/com/android/internal/app/TetherActivity.java b/core/java/com/android/internal/app/TetherActivity.java
index 5d71231..7f83b2b 100644
--- a/core/java/com/android/internal/app/TetherActivity.java
+++ b/core/java/com/android/internal/app/TetherActivity.java
@@ -62,6 +62,7 @@
         // determine if we advertise tethering or untethering
         ConnectivityManager cm =
                 (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
+
         mTethered = cm.getTetheredIfaces().length;
         int tetherable = cm.getTetherableIfaces().length;
         if ((mTethered == 0) && (tetherable == 0)) {
@@ -116,7 +117,7 @@
      * {@inheritDoc}
      */
     public void onClick(DialogInterface dialog, int which) {
-        boolean error =  false;
+        int error = ConnectivityManager.TETHER_ERROR_NO_ERROR;
 
         if (which == POSITIVE_BUTTON) {
             ConnectivityManager cm =
@@ -130,24 +131,17 @@
                 for (String t : tetherable) {
                     for (String r : usbRegexs) {
                         if (t.matches(r)) {
-                            if (!cm.tether(t))
-                                error = true;
+                            error = cm.tether(t);
                             break;
                         }
                     }
                 }
-                if (error) {
-                    showTetheringError();
-                }
+                showTetheringError(error);
             } else {
                 for (String t : tethered) {
-                    if (!cm.untether(t)) {
-                        error = true;
-                    }
+                    error = cm.untether(t);
                 }
-                if (error) {
-                    showUnTetheringError();
-                }
+                showUnTetheringError(error);
             }
         }
         // No matter what, finish the activity
@@ -163,14 +157,23 @@
         }
     }
 
-    private void showTetheringError() {
-        Toast.makeText(this, com.android.internal.R.string.tether_error_message,
-                Toast.LENGTH_LONG).show();
+    private void showTetheringError(int error) {
+        switch(error) {
+        case ConnectivityManager.TETHER_ERROR_NO_ERROR:
+            return;
+        default:
+            Toast.makeText(this, com.android.internal.R.string.tether_error_message,
+                    Toast.LENGTH_LONG).show();
+        }
     }
 
-    private void showUnTetheringError() {
-        Toast.makeText(this, com.android.internal.R.string.tether_stop_error_message,
-                Toast.LENGTH_LONG).show();
+    private void showUnTetheringError(int error) {
+        switch(error) {
+        case ConnectivityManager.TETHER_ERROR_NO_ERROR:
+            return;
+        default:
+            Toast.makeText(this, com.android.internal.R.string.tether_stop_error_message,
+                    Toast.LENGTH_LONG).show();
+        }
     }
-
 }
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 35ea0cc..d34599f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2249,8 +2249,10 @@
     <string name="tether_button">Tether</string>
     <!-- See TETHER.   This is the button text to ignore the plugging in of the phone.. -->
     <string name="tether_button_cancel">Cancel</string>
-    <!-- See TETHER.  If there was an error mounting, this is the text. -->
-    <string name="tether_error_message">There is a problem tethering.</string>
+
+    <!-- See TETHER.  If there was a recoverable error, this is the text. -->
+    <string name="tether_error_message">We\'ve encountered a problem turning on Tethering.  Please try again.</string>
+
     <!-- TETHER: When the user connects the phone to a computer, we show a notification asking if he wants to share his cellular network connection.  This is the title -->
     <string name="tether_available_notification_title">USB tethering available</string>
     <!-- See USB_STORAGE. This is the message. -->
@@ -2270,7 +2272,8 @@
     <string name="tether_stop_button">Disconnect</string>
     <!-- See TETHER_STOP.   This is the button text to cancel disconnecting the tether. -->
     <string name="tether_stop_button_cancel">Cancel</string>
-    <!-- See TETHER_STOP_DIALOG.  If there was an error disconnect, this is the text. -->
+
+    <!-- See TETHER_STOP.  If there was an error disconnect, this is the text. -->
     <string name="tether_stop_error_message">We\'ve encountered a problem turning off Tethering. Please try again.</string>
 
     <!-- Strings for car mode notification -->
diff --git a/libs/rs/RenderScript.h b/libs/rs/RenderScript.h
index cd8361c..d280f50 100644
--- a/libs/rs/RenderScript.h
+++ b/libs/rs/RenderScript.h
@@ -202,6 +202,12 @@
     RS_PRIMITIVE_TRIANGLE_FAN
 };
 
+enum RsError {
+    RS_ERROR_NONE,
+    RS_ERROR_BAD_SHADER,
+    RS_ERROR_BAD_SCRIPT
+};
+
 #ifndef NO_RS_FUNCS
 #include "rsgApiFuncDecl.h"
 #endif
diff --git a/libs/rs/rs.spec b/libs/rs/rs.spec
index 4d97c0f..cb9937c 100644
--- a/libs/rs/rs.spec
+++ b/libs/rs/rs.spec
@@ -36,6 +36,11 @@
 	param int32_t bits
 }
 
+ContextGetError {
+	param RsError *err
+	ret const char *
+	}
+
 ContextSetPriority {
 	param int32_t priority
 	}
diff --git a/libs/rs/rsContext.cpp b/libs/rs/rsContext.cpp
index cc3a74fb..d8a9a99 100644
--- a/libs/rs/rsContext.cpp
+++ b/libs/rs/rsContext.cpp
@@ -178,6 +178,11 @@
     uint32_t ret = runScript(mRootScript.get(), 0);
 
     checkError("runRootScript");
+    if (mError != RS_ERROR_NONE) {
+        // If we have an error condition we stop rendering until
+        // somthing changes that might fix it.
+        ret = 0;
+    }
     return ret;
 }
 
@@ -240,10 +245,13 @@
     }
 }
 
-void Context::setupCheck()
+bool Context::setupCheck()
 {
     if (checkVersion2_0()) {
-        mShaderCache.lookup(this, mVertex.get(), mFragment.get());
+        if (!mShaderCache.lookup(this, mVertex.get(), mFragment.get())) {
+            LOGE("Context::setupCheck() 1 fail");
+            return false;
+        }
 
         mFragmentStore->setupGL2(this, &mStateFragmentStore);
         mFragment->setupGL2(this, &mStateFragment, &mShaderCache);
@@ -256,6 +264,7 @@
         mRaster->setupGL(this, &mStateRaster);
         mVertex->setupGL(this, &mStateVertex);
     }
+    return true;
 }
 
 static bool getProp(const char *str)
@@ -389,6 +398,9 @@
     mUseDepth = useDepth;
     mPaused = false;
     mObjHead = NULL;
+    mError = RS_ERROR_NONE;
+    mErrorMsg = NULL;
+
     memset(&mEGL, 0, sizeof(mEGL));
     memset(&mGL, 0, sizeof(mGL));
     mIsGraphicsContext = isGraphics;
@@ -764,6 +776,23 @@
     mIO.mToClient.shutdown();
 }
 
+const char * Context::getError(RsError *err)
+{
+    *err = mError;
+    mError = RS_ERROR_NONE;
+    if (*err != RS_ERROR_NONE) {
+        return mErrorMsg;
+    }
+    return NULL;
+}
+
+void Context::setError(RsError e, const char *msg)
+{
+    mError = e;
+    mErrorMsg = msg;
+}
+
+
 void Context::dumpDebug() const
 {
     LOGE("RS Context debug %p", this);
@@ -874,6 +903,15 @@
     ObjectBase::dumpAll(rsc);
 }
 
+const char * rsi_ContextGetError(Context *rsc, RsError *e)
+{
+    const char *msg = rsc->getError(e);
+    if (*e != RS_ERROR_NONE) {
+        LOGE("RS Error %i %s", *e, msg);
+    }
+    return msg;
+}
+
 }
 }
 
diff --git a/libs/rs/rsContext.h b/libs/rs/rsContext.h
index 04bd748..82c3687 100644
--- a/libs/rs/rsContext.h
+++ b/libs/rs/rsContext.h
@@ -93,7 +93,7 @@
     const ProgramRaster * getRaster() {return mRaster.get();}
     const ProgramVertex * getVertex() {return mVertex.get();}
 
-    void setupCheck();
+    bool setupCheck();
     bool checkDriver() const {return mEGL.mSurface != 0;}
 
     void pause();
@@ -160,6 +160,8 @@
 
     void dumpDebug() const;
     void checkError(const char *) const;
+    const char * getError(RsError *);
+    void setError(RsError e, const char *msg);
 
     mutable const ObjectBase * mObjHead;
 
@@ -211,6 +213,8 @@
     bool mExit;
     bool mUseDepth;
     bool mPaused;
+    RsError mError;
+    const char *mErrorMsg;
 
     pthread_t mThreadId;
     pid_t mNativeThreadId;
diff --git a/libs/rs/rsProgram.cpp b/libs/rs/rsProgram.cpp
index 656a3c3..478a6dc 100644
--- a/libs/rs/rsProgram.cpp
+++ b/libs/rs/rsProgram.cpp
@@ -39,6 +39,7 @@
     mInputCount = 0;
     mOutputCount = 0;
     mConstantCount = 0;
+    mIsValid = false;
 }
 
 Program::Program(Context *rsc, const char * shaderText, uint32_t shaderLength,
@@ -216,6 +217,7 @@
                 }
                 glDeleteShader(mShaderID);
                 mShaderID = 0;
+                rsc->setError(RS_ERROR_BAD_SHADER, "Error returned from GL driver loading shader text,");
                 return false;
             }
         }
@@ -224,6 +226,7 @@
     if (rsc->props.mLogShaders) {
         LOGV("--Shader load result %x ", glGetError());
     }
+    mIsValid = true;
     return true;
 }
 
diff --git a/libs/rs/rsProgram.h b/libs/rs/rsProgram.h
index a34e89f..86f85fb 100644
--- a/libs/rs/rsProgram.h
+++ b/libs/rs/rsProgram.h
@@ -59,6 +59,8 @@
     String8 getGLSLOutputString() const;
     String8 getGLSLConstantString() const;
 
+    bool isValid() const {return mIsValid;}
+
 protected:
     // Components not listed in "in" will be passed though
     // unless overwritten by components in out.
@@ -68,6 +70,7 @@
     uint32_t mInputCount;
     uint32_t mOutputCount;
     uint32_t mConstantCount;
+    bool mIsValid;
 
     ObjectBaseRef<Allocation> mConstants[MAX_UNIFORMS];
 
diff --git a/libs/rs/rsScript.cpp b/libs/rs/rsScript.cpp
index cb1436b..a33933b 100644
--- a/libs/rs/rsScript.cpp
+++ b/libs/rs/rsScript.cpp
@@ -96,6 +96,10 @@
 void rsi_ScriptInvoke(Context *rsc, RsScript vs, uint32_t slot)
 {
     Script *s = static_cast<Script *>(vs);
+    if (s->mEnviroment.mInvokables[slot] == NULL) {
+        rsc->setError(RS_ERROR_BAD_SCRIPT, "Calling invoke on bad script");
+        return;
+    }
     s->setupScript();
     s->mEnviroment.mInvokables[slot]();
 }
diff --git a/libs/rs/rsScriptC.cpp b/libs/rs/rsScriptC.cpp
index b7e0b86..1f23773 100644
--- a/libs/rs/rsScriptC.cpp
+++ b/libs/rs/rsScriptC.cpp
@@ -62,6 +62,11 @@
 
 uint32_t ScriptC::run(Context *rsc, uint32_t launchIndex)
 {
+    if (mProgram.mScript == NULL) {
+        rsc->setError(RS_ERROR_BAD_SCRIPT, "Attempted to run bad script");
+        return 0;
+    }
+
     Context::ScriptTLSStruct * tls =
     (Context::ScriptTLSStruct *)pthread_getspecific(Context::gThreadTLSKey);
     rsAssert(tls);
@@ -154,7 +159,9 @@
         ACCchar buf[4096];
         ACCsizei len;
         accGetScriptInfoLog(s->mAccScript, sizeof(buf), &len, buf);
-        LOGV(buf);
+        LOGE(buf);
+        rsc->setError(RS_ERROR_BAD_SCRIPT, "Error compiling user script.");
+        return;
     }
 
     if (s->mProgram.mInit) {
diff --git a/libs/rs/rsScriptC_Lib.cpp b/libs/rs/rsScriptC_Lib.cpp
index 235c153..202ca3d 100644
--- a/libs/rs/rsScriptC_Lib.cpp
+++ b/libs/rs/rsScriptC_Lib.cpp
@@ -683,7 +683,9 @@
                         float x2, float y2, float z2)
 {
     GET_TLS();
-    rsc->setupCheck();
+    if (!rsc->setupCheck()) {
+        return;
+    }
 
     float vtx[] = { x1, y1, z1, x2, y2, z2 };
     VertexArray va;
@@ -700,7 +702,9 @@
 static void SC_drawPoint(float x, float y, float z)
 {
     GET_TLS();
-    rsc->setupCheck();
+    if (!rsc->setupCheck()) {
+        return;
+    }
 
     float vtx[] = { x, y, z };
 
@@ -725,7 +729,9 @@
                                  float u4, float v4)
 {
     GET_TLS();
-    rsc->setupCheck();
+    if (!rsc->setupCheck()) {
+        return;
+    }
 
     //LOGE("Quad");
     //LOGE("%4.2f, %4.2f, %4.2f", x1, y1, z1);
@@ -782,7 +788,9 @@
         float cx0, float cy0, float cx1, float cy1)
 {
     GET_TLS();
-    rsc->setupCheck();
+    if (!rsc->setupCheck()) {
+        return;
+    }
 
     GLint crop[4] = {cx0, cy0, cx1, cy1};
     glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
@@ -831,7 +839,9 @@
 {
     GET_TLS();
     SimpleMesh *sm = static_cast<SimpleMesh *>(vsm);
-    rsc->setupCheck();
+    if (!rsc->setupCheck()) {
+        return;
+    }
     sm->render(rsc);
 }
 
@@ -839,7 +849,9 @@
 {
     GET_TLS();
     SimpleMesh *sm = static_cast<SimpleMesh *>(vsm);
-    rsc->setupCheck();
+    if (!rsc->setupCheck()) {
+        return;
+    }
     sm->renderRange(rsc, start, len);
 }
 
diff --git a/libs/rs/rsShaderCache.cpp b/libs/rs/rsShaderCache.cpp
index 3a1f370..4711d1b 100644
--- a/libs/rs/rsShaderCache.cpp
+++ b/libs/rs/rsShaderCache.cpp
@@ -123,6 +123,8 @@
                 }
             }
             glDeleteProgram(pgm);
+            rsc->setError(RS_ERROR_BAD_SHADER, "Error linking GL Programs");
+            return false;
         }
         if (vtx->isUserProgram()) {
             for (uint32_t ct=0; ct < vtx->getAttribCount(); ct++) {
@@ -146,6 +148,7 @@
         }
     }
 
+    e->mIsValid = true;
     //LOGV("SC made program %i", e->program);
     glUseProgram(e->program);
     mEntryCount++;
diff --git a/libs/rs/rsShaderCache.h b/libs/rs/rsShaderCache.h
index 7aa8183..df99ccc 100644
--- a/libs/rs/rsShaderCache.h
+++ b/libs/rs/rsShaderCache.h
@@ -56,6 +56,7 @@
         int32_t mFragAttribSlots[Program::MAX_ATTRIBS];
         int32_t mFragUniformSlots[Program::MAX_UNIFORMS];
         bool mUserVertexProgram;
+        bool mIsValid;
     } entry_t;
     entry_t *mEntries;
     entry_t *mCurrent;
diff --git a/libs/rs/spec.l b/libs/rs/spec.l
index d81d47e..6a9010fe 100644
--- a/libs/rs/spec.l
+++ b/libs/rs/spec.l
@@ -148,6 +148,9 @@
     BEGIN(api_entry2);
     }
 
+<api_entry2>"*" {
+    currType->ptrLevel ++;
+    }
 
 <api_entry2>"}" {
     apiCount++;
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index a36ee85..e586869 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -450,10 +450,20 @@
         VolumeStreamState streamState = mStreamStates[streamType];
         if (streamState.setIndex(index, lastAudible) || force) {
             // Post message to set system volume (it in turn will post a message
-            // to persist). Do not change volume if stream is muted.
-            if (streamState.muteCount() == 0) {
+            // to persist).
+            // If stream is muted or we are in silent mode and stream is affected by ringer mode
+            // and the new volume is not 0, just persist the new volume but do not change
+            // current value
+            if (streamState.muteCount() == 0 &&
+                (mRingerMode == AudioManager.RINGER_MODE_NORMAL ||
+                !isStreamAffectedByRingerMode(streamType) ||
+                index == 0)) {
                 sendMsg(mAudioHandler, MSG_SET_SYSTEM_VOLUME, streamType, SENDMSG_NOOP, 0, 0,
                         streamState, 0);
+            } else {
+                // Post a persist volume msg
+                sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, streamType,
+                        SENDMSG_REPLACE, 0, 1, streamState, PERSIST_DELAY);
             }
         }
     }
@@ -512,7 +522,7 @@
                 if (!isStreamAffectedByRingerMode(streamType)) continue;
                 // Bring back last audible volume
                 setStreamVolumeInt(streamType, mStreamStates[streamType].mLastAudibleIndex,
-                                   false, false);
+                                   true, false);
             }
         } else {
             for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
@@ -524,7 +534,7 @@
                     // to non affected by ringer mode. Does not arm to do it for streams that
                     // are not affected as well.
                     setStreamVolumeInt(streamType, mStreamStates[streamType].mLastAudibleIndex,
-                            false, false);
+                            true, false);
                 }
             }
         }
@@ -1269,14 +1279,18 @@
 
             // Post a persist volume msg
             sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, streamState.mStreamType,
-                    SENDMSG_REPLACE, 0, 0, streamState, PERSIST_DELAY);
+                    SENDMSG_REPLACE, 1, 1, streamState, PERSIST_DELAY);
         }
 
-        private void persistVolume(VolumeStreamState streamState) {
-            System.putInt(mContentResolver, streamState.mVolumeIndexSettingName,
-                    (streamState.mIndex + 5)/ 10);
-            System.putInt(mContentResolver, streamState.mLastAudibleVolumeIndexSettingName,
+        private void persistVolume(VolumeStreamState streamState, boolean current, boolean lastAudible) {
+            if (current) {
+                System.putInt(mContentResolver, streamState.mVolumeIndexSettingName,
+                              (streamState.mIndex + 5)/ 10);
+            }
+            if (lastAudible) {
+                System.putInt(mContentResolver, streamState.mLastAudibleVolumeIndexSettingName,
                     (streamState.mLastAudibleIndex + 5) / 10);
+            }
         }
 
         private void persistRingerMode() {
@@ -1361,7 +1375,7 @@
                     break;
 
                 case MSG_PERSIST_VOLUME:
-                    persistVolume((VolumeStreamState) msg.obj);
+                    persistVolume((VolumeStreamState) msg.obj, (msg.arg1 != 0), (msg.arg2 != 0));
                     break;
 
                 case MSG_PERSIST_RINGER_MODE:
@@ -1469,7 +1483,7 @@
                         //  and persist with no delay as there might be registered observers of the persisted
                         //  notification volume.
                         sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, AudioSystem.STREAM_NOTIFICATION,
-                                SENDMSG_REPLACE, 0, 0, mStreamStates[AudioSystem.STREAM_NOTIFICATION], 0);
+                                SENDMSG_REPLACE, 1, 1, mStreamStates[AudioSystem.STREAM_NOTIFICATION], 0);
                     }
                 }
             }
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 67b6200..a1c45dc 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -1457,15 +1457,36 @@
     }
 
     // javadoc from interface
-    public boolean tether(String iface) {
+    public int tether(String iface) {
         enforceTetherChangePermission();
-        return isTetheringSupported() && mTethering.tether(iface);
+
+        if (isTetheringSupported()) {
+            return mTethering.tether(iface);
+        } else {
+            return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
+        }
     }
 
     // javadoc from interface
-    public boolean untether(String iface) {
+    public int untether(String iface) {
         enforceTetherChangePermission();
-        return isTetheringSupported() && mTethering.untether(iface);
+
+        if (isTetheringSupported()) {
+            return mTethering.untether(iface);
+        } else {
+            return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
+        }
+    }
+
+    // javadoc from interface
+    public int getLastTetherError(String iface) {
+        enforceTetherAccessPermission();
+
+        if (isTetheringSupported()) {
+            return mTethering.getLastTetherError(iface);
+        } else {
+            return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
+        }
     }
 
     // TODO - proper iface API for selection by property, inspection, etc
@@ -1499,6 +1520,11 @@
         return mTethering.getTetheredIfaces();
     }
 
+    public String[] getTetheringErroredIfaces() {
+        enforceTetherAccessPermission();
+        return mTethering.getErroredIfaces();
+    }
+
     // if ro.tether.denied = true we default to no tethering
     // gservices could set the secure setting to 1 though to enable it on a build where it
     // had previously been turned off.
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index b41a7d9..664f028 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -2372,10 +2372,10 @@
         synchronized (mPackages) {
             // Look to see if we already know about this package.
             String oldName = mSettings.mRenamedPackages.get(pkg.packageName);
-            if (oldName != null && oldName.equals(pkg.mOriginalPackage)) {
+            if (pkg.mOriginalPackages != null && pkg.mOriginalPackages.contains(oldName)) {
                 // This package has been renamed to its original name.  Let's
                 // use that.
-                ps = mSettings.peekPackageLP(pkg.mOriginalPackage);
+                ps = mSettings.peekPackageLP(oldName);
             }
             // If there was no original package, see one for the real package name.
             if (ps == null) {
@@ -2645,7 +2645,7 @@
 
         if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) == 0) {
             // Only system apps can use these features.
-            pkg.mOriginalPackage = null;
+            pkg.mOriginalPackages = null;
             pkg.mRealPackage = null;
             pkg.mAdoptPermissions = null;
         }
@@ -2727,22 +2727,22 @@
             }
 
             if (false) {
-                if (pkg.mOriginalPackage != null) {
+                if (pkg.mOriginalPackages != null) {
                     Log.w(TAG, "WAITING FOR DEBUGGER");
                     Debug.waitForDebugger();
-                    Log.i(TAG, "Package " + pkg.packageName + " from original package"
-                            + pkg.mOriginalPackage);
+                    Log.i(TAG, "Package " + pkg.packageName + " from original packages"
+                            + pkg.mOriginalPackages);
                 }
             }
             
             // Check if we are renaming from an original package name.
             PackageSetting origPackage = null;
             String realName = null;
-            if (pkg.mOriginalPackage != null) {
+            if (pkg.mOriginalPackages != null) {
                 // This package may need to be renamed to a previously
                 // installed name.  Let's check on that...
                 String renamed = mSettings.mRenamedPackages.get(pkg.mRealPackage);
-                if (pkg.mOriginalPackage.equals(renamed)) {
+                if (pkg.mOriginalPackages.contains(renamed)) {
                     // This package had originally been installed as the
                     // original name, and we have already taken care of
                     // transitioning to the new one.  Just update the new
@@ -2755,25 +2755,32 @@
                         pkg.setPackageName(renamed);
                     }
                     
-                } else if ((origPackage
-                        = mSettings.peekPackageLP(pkg.mOriginalPackage)) != null) {
-                    // We do have the package already installed under its
-                    // original name...  should we use it?
-                    if (!verifyPackageUpdate(origPackage, pkg)) {
-                        // New package is not compatible with original.
-                        origPackage = null;
-                    } else if (origPackage.sharedUser != null) {
-                        // Make sure uid is compatible between packages.
-                        if (!origPackage.sharedUser.name.equals(pkg.mSharedUserId)) {
-                            Log.w(TAG, "Unable to migrate data from " + origPackage.name
-                                    + " to " + pkg.packageName + ": old uid "
-                                    + origPackage.sharedUser.name
-                                    + " differs from " + pkg.mSharedUserId);
-                            origPackage = null;
+                } else {
+                    for (int i=pkg.mOriginalPackages.size()-1; i>=0; i--) {
+                        if ((origPackage=mSettings.peekPackageLP(
+                                pkg.mOriginalPackages.get(i))) != null) {
+                            // We do have the package already installed under its
+                            // original name...  should we use it?
+                            if (!verifyPackageUpdate(origPackage, pkg)) {
+                                // New package is not compatible with original.
+                                origPackage = null;
+                                continue;
+                            } else if (origPackage.sharedUser != null) {
+                                // Make sure uid is compatible between packages.
+                                if (!origPackage.sharedUser.name.equals(pkg.mSharedUserId)) {
+                                    Log.w(TAG, "Unable to migrate data from " + origPackage.name
+                                            + " to " + pkg.packageName + ": old uid "
+                                            + origPackage.sharedUser.name
+                                            + " differs from " + pkg.mSharedUserId);
+                                    origPackage = null;
+                                    continue;
+                                }
+                            } else {
+                                if (DEBUG_UPGRADE) Log.v(TAG, "Renaming new package "
+                                        + pkg.packageName + " to old name " + origPackage.name);
+                            }
+                            break;
                         }
-                    } else {
-                        if (DEBUG_UPGRADE) Log.v(TAG, "Renaming new package "
-                                + pkg.packageName + " to old name " + origPackage.name);
                     }
                 }
             }
@@ -5479,13 +5486,14 @@
             // Check if installing already existing package
             if ((pFlags&PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
                 String oldName = mSettings.mRenamedPackages.get(pkgName);
-                if (oldName != null && oldName.equals(pkg.mOriginalPackage)
+                if (pkg.mOriginalPackages != null
+                        && pkg.mOriginalPackages.contains(oldName)
                         && mPackages.containsKey(oldName)) {
                     // This package is derived from an original package,
                     // and this device has been updating from that original
                     // name.  We must continue using the original name, so
                     // rename the new package here.
-                    pkg.setPackageName(pkg.mOriginalPackage);
+                    pkg.setPackageName(oldName);
                     pkgName = pkg.packageName;
                     replace = true;
                 } else if (mPackages.containsKey(pkgName)) {
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index 5f37a42..ee54f73 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -40,6 +40,7 @@
 import android.os.ServiceManager;
 import android.provider.Settings;
 import android.util.Log;
+import android.widget.Toast;
 
 import com.android.internal.telephony.Phone;
 import com.android.internal.util.HierarchicalState;
@@ -236,7 +237,7 @@
         }
     }
 
-    public boolean tether(String iface) {
+    public int tether(String iface) {
         Log.d(TAG, "Tethering " + iface);
         TetherInterfaceSM sm = null;
         synchronized (mIfaces) {
@@ -244,21 +245,17 @@
         }
         if (sm == null) {
             Log.e(TAG, "Tried to Tether an unknown iface :" + iface + ", ignoring");
-            return false;
+            return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
         }
-        if (sm.isErrored()) {
-            Log.e(TAG, "Tried to Tether to an errored iface :" + iface + ", ignoring");
-            return false;
-        }
-        if (!sm.isAvailable()) {
+        if (!sm.isAvailable() && !sm.isErrored()) {
             Log.e(TAG, "Tried to Tether an unavailable iface :" + iface + ", ignoring");
-            return false;
+            return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
         }
         sm.sendMessage(sm.obtainMessage(TetherInterfaceSM.CMD_TETHER_REQUESTED));
-        return true;
+        return ConnectivityManager.TETHER_ERROR_NO_ERROR;
     }
 
-    public boolean untether(String iface) {
+    public int untether(String iface) {
         Log.d(TAG, "Untethering " + iface);
         TetherInterfaceSM sm = null;
         synchronized (mIfaces) {
@@ -266,14 +263,26 @@
         }
         if (sm == null) {
             Log.e(TAG, "Tried to Untether an unknown iface :" + iface + ", ignoring");
-            return false;
+            return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
         }
         if (sm.isErrored()) {
             Log.e(TAG, "Tried to Untethered an errored iface :" + iface + ", ignoring");
-            return false;
+            return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
         }
         sm.sendMessage(sm.obtainMessage(TetherInterfaceSM.CMD_TETHER_UNREQUESTED));
-        return true;
+        return ConnectivityManager.TETHER_ERROR_NO_ERROR;
+    }
+
+    public int getLastTetherError(String iface) {
+        TetherInterfaceSM sm = null;
+        synchronized (mIfaces) {
+            sm = mIfaces.get(iface);
+        }
+        if (sm == null) {
+            Log.e(TAG, "Tried to getLastTetherError on an unknown iface :" + iface + ", ignoring");
+            return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
+        }
+        return sm.getLastError();
     }
 
     private void sendTetherStateChangedBroadcast() {
@@ -430,6 +439,28 @@
         }
     }
 
+    private void showErrorToast(int error) {
+        int num;
+        switch(error) {
+        case ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR:
+        case ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR:
+        case ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR:
+        case ConnectivityManager.TETHER_ERROR_MASTER_ERROR:
+            num = com.android.internal.R.string.tether_error_message;
+            break;
+        case ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR:
+        case ConnectivityManager.TETHER_ERROR_DISABLE_NAT_ERROR:
+            num = com.android.internal.R.string.tether_stop_error_message;
+            break;
+        default:
+            // do nothing
+            return;
+        }
+        String text = mContext.getResources().getString(num) + " - EC" + error;
+        Log.e(TAG, text);
+        Toast.makeText(mContext, text, Toast.LENGTH_LONG).show();
+    }
+
     private class StateReceiver extends BroadcastReceiver {
         public void onReceive(Context content, Intent intent) {
             String action = intent.getAction();
@@ -542,10 +573,9 @@
                                 ifcg.interfaceFlags = ifcg.interfaceFlags.replace("down", "up");
                             } else {
                                 ifcg.interfaceFlags = ifcg.interfaceFlags.replace("up", "down");
-                                // TODO - clean this up - maybe a better regex?
-                                ifcg.interfaceFlags = ifcg.interfaceFlags.replace("running", "");
-                                ifcg.interfaceFlags = ifcg.interfaceFlags.replace("  "," ");
                             }
+                            ifcg.interfaceFlags = ifcg.interfaceFlags.replace("running", "");
+                            ifcg.interfaceFlags = ifcg.interfaceFlags.replace("  "," ");
                             service.setInterfaceConfig(iface, ifcg);
                         }
                     } catch (Exception e) {
@@ -611,6 +641,24 @@
         return retVal;
     }
 
+    public String[] getErroredIfaces() {
+        ArrayList<String> list = new ArrayList<String>();
+        synchronized (mIfaces) {
+            Set keys = mIfaces.keySet();
+            for (Object key : keys) {
+                TetherInterfaceSM sm = mIfaces.get(key);
+                if (sm.isErrored()) {
+                    list.add((String)key);
+                }
+            }
+        }
+        String[] retVal = new String[list.size()];
+        for (int i= 0; i< list.size(); i++) {
+            retVal[i] = list.get(i);
+        }
+        return retVal;
+    }
+
 
     class TetherInterfaceSM extends HierarchicalStateMachine {
         // notification from the master SM that it's in tether mode
@@ -637,8 +685,8 @@
         static final int CMD_STOP_TETHERING_ERROR        = 14;
         // notification from the master SM that it had trouble setting the DNS forwarders
         static final int CMD_SET_DNS_FORWARDERS_ERROR    = 15;
-        // a mechanism to transition self to error state from an enter function
-        static final int CMD_TRANSITION_TO_ERROR         = 16;
+        // a mechanism to transition self to another state from an enter function
+        static final int CMD_TRANSITION_TO_STATE         = 16;
 
         private HierarchicalState mDefaultState;
 
@@ -646,18 +694,11 @@
         private HierarchicalState mStartingState;
         private HierarchicalState mTetheredState;
 
-        private HierarchicalState mMasterTetherErrorState;
-        private HierarchicalState mTetherInterfaceErrorState;
-        private HierarchicalState mUntetherInterfaceErrorState;
-        private HierarchicalState mEnableNatErrorState;
-        private HierarchicalState mDisableNatErrorState;
-        private HierarchicalState mUsbConfigurationErrorState;
-
         private HierarchicalState mUnavailableState;
 
         private boolean mAvailable;
-        private boolean mErrored;
         private boolean mTethered;
+        int mLastError;
 
         String mIfaceName;
         boolean mUsb;
@@ -666,6 +707,7 @@
             super(name);
             mIfaceName = name;
             mUsb = usb;
+            setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
 
             mInitialState = new InitialState();
             addState(mInitialState);
@@ -673,18 +715,6 @@
             addState(mStartingState);
             mTetheredState = new TetheredState();
             addState(mTetheredState);
-            mMasterTetherErrorState = new MasterTetherErrorState();
-            addState(mMasterTetherErrorState);
-            mTetherInterfaceErrorState = new TetherInterfaceErrorState();
-            addState(mTetherInterfaceErrorState);
-            mUntetherInterfaceErrorState = new UntetherInterfaceErrorState();
-            addState(mUntetherInterfaceErrorState);
-            mEnableNatErrorState = new EnableNatErrorState();
-            addState(mEnableNatErrorState);
-            mDisableNatErrorState = new DisableNatErrorState();
-            addState(mDisableNatErrorState);
-            mUsbConfigurationErrorState = new UsbConfigurationErrorState();
-            addState(mUsbConfigurationErrorState);
             mUnavailableState = new UnavailableState();
             addState(mUnavailableState);
 
@@ -698,19 +728,30 @@
             if (current == mInitialState) res += "InitialState";
             if (current == mStartingState) res += "StartingState";
             if (current == mTetheredState) res += "TetheredState";
-            if (current == mMasterTetherErrorState) res += "MasterTetherErrorState";
-            if (current == mTetherInterfaceErrorState) res += "TetherInterfaceErrorState";
-            if (current == mUntetherInterfaceErrorState) res += "UntetherInterfaceErrorState";
-            if (current == mEnableNatErrorState) res += "EnableNatErrorState";
-            if (current == mDisableNatErrorState) res += "DisableNatErrorState";
-            if (current == mUsbConfigurationErrorState) res += "UsbConfigurationErrorState";
             if (current == mUnavailableState) res += "UnavailableState";
             if (mAvailable) res += " - Available";
             if (mTethered) res += " - Tethered";
-            if (mErrored) res += " - ERRORED";
+            res += " - lastError =" + mLastError;
             return res;
         }
 
+        public synchronized int getLastError() {
+            return mLastError;
+        }
+
+        private synchronized void setLastError(int error) {
+            mLastError = error;
+
+            if (isErrored()) {
+                if (mUsb) {
+                    // note everything's been unwound by this point so nothing to do on
+                    // further error..
+                    Tethering.this.configureUsbIface(false);
+                }
+                Tethering.this.showErrorToast(error);
+            }
+        }
+
         // synchronized between this getter and the following setter
         public synchronized boolean isAvailable() {
             return mAvailable;
@@ -731,18 +772,7 @@
 
         // synchronized between this getter and the following setter
         public synchronized boolean isErrored() {
-            return mErrored;
-        }
-
-        private void setErrored(boolean errored) {
-            synchronized (this) {
-                mErrored = errored;
-            }
-            if (errored && mUsb) {
-                // note everything's been unwound by this point so nothing to do on
-                // further error..
-                Tethering.this.configureUsbIface(false);
-            }
+            return (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR);
         }
 
         class InitialState extends HierarchicalState {
@@ -750,7 +780,6 @@
             public void enter() {
                 setAvailable(true);
                 setTethered(false);
-                setErrored(false);
                 sendTetherStateChangedBroadcast();
             }
 
@@ -760,6 +789,7 @@
                 boolean retValue = true;
                 switch (message.what) {
                     case CMD_TETHER_REQUESTED:
+                        setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
                         Message m = mTetherMasterSM.obtainMessage(
                                 TetherMasterSM.CMD_TETHER_MODE_REQUESTED);
                         m.obj = TetherInterfaceSM.this;
@@ -788,8 +818,10 @@
                         m.obj = TetherInterfaceSM.this;
                         mTetherMasterSM.sendMessage(m);
 
-                        m = obtainMessage(CMD_TRANSITION_TO_ERROR);
-                        m.obj = mUsbConfigurationErrorState;
+                        setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
+
+                        m = obtainMessage(CMD_TRANSITION_TO_STATE);
+                        m.obj = mInitialState;
                         sendMessageAtFrontOfQueue(m);
                         return;
                     }
@@ -809,7 +841,8 @@
                         mTetherMasterSM.sendMessage(m);
                         if (mUsb) {
                             if (!Tethering.this.configureUsbIface(false)) {
-                                transitionTo(mUsbConfigurationErrorState);
+                                setLastErrorAndTransitionToInitialState(
+                                    ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
                                 break;
                             }
                         }
@@ -824,7 +857,8 @@
                     case CMD_START_TETHERING_ERROR:
                     case CMD_STOP_TETHERING_ERROR:
                     case CMD_SET_DNS_FORWARDERS_ERROR:
-                        transitionTo(mMasterTetherErrorState);
+                        setLastErrorAndTransitionToInitialState(
+                                ConnectivityManager.TETHER_ERROR_MASTER_ERROR);
                         break;
                     case CMD_INTERFACE_DOWN:
                         m = mTetherMasterSM.obtainMessage(
@@ -833,7 +867,7 @@
                         mTetherMasterSM.sendMessage(m);
                         transitionTo(mUnavailableState);
                         break;
-                   case CMD_TRANSITION_TO_ERROR:
+                   case CMD_TRANSITION_TO_STATE:
                        HierarchicalState s = (HierarchicalState)(message.obj);
                        transitionTo(s);
                        break;
@@ -853,16 +887,24 @@
                 try {
                     service.tetherInterface(mIfaceName);
                 } catch (Exception e) {
-                    Message m = obtainMessage(CMD_TRANSITION_TO_ERROR);
-                    m.obj = mTetherInterfaceErrorState;
+                    setLastError(ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR);
+
+                    Message m = obtainMessage(CMD_TRANSITION_TO_STATE);
+                    m.obj = mInitialState;
                     sendMessageAtFrontOfQueue(m);
                     return;
                 }
                 try {
                     service.enableNat(mIfaceName, mUpstreamIfaceName);
                 } catch (Exception e) {
-                    Message m = obtainMessage(CMD_TRANSITION_TO_ERROR);
-                    m.obj = mEnableNatErrorState;
+                    try {
+                        service.untetherInterface(mIfaceName);
+                    } catch (Exception ee) {}
+
+                    setLastError(ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR);
+
+                    Message m = obtainMessage(CMD_TRANSITION_TO_STATE);
+                    m.obj = mInitialState;
                     sendMessageAtFrontOfQueue(m);
                     return;
                 }
@@ -890,13 +932,19 @@
                         try {
                             service.disableNat(mIfaceName, mUpstreamIfaceName);
                         } catch (Exception e) {
-                            transitionTo(mDisableNatErrorState);
+                            try {
+                                service.untetherInterface(mIfaceName);
+                            } catch (Exception ee) {}
+
+                            setLastErrorAndTransitionToInitialState(
+                                    ConnectivityManager.TETHER_ERROR_DISABLE_NAT_ERROR);
                             break;
                         }
                         try {
                             service.untetherInterface(mIfaceName);
                         } catch (Exception e) {
-                            transitionTo(mUntetherInterfaceErrorState);
+                            setLastErrorAndTransitionToInitialState(
+                                    ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR);
                             break;
                         }
                         Message m = mTetherMasterSM.obtainMessage(
@@ -906,13 +954,11 @@
                         if (message.what == CMD_TETHER_UNREQUESTED) {
                             if (mUsb) {
                                 if (!Tethering.this.configureUsbIface(false)) {
-                                    transitionTo(mUsbConfigurationErrorState);
-                                } else {
-                                    transitionTo(mInitialState);
+                                    setLastError(
+                                            ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
                                 }
-                            } else {
-                                transitionTo(mInitialState);
                             }
+                            transitionTo(mInitialState);
                         } else if (message.what == CMD_INTERFACE_DOWN) {
                             transitionTo(mUnavailableState);
                         }
@@ -932,30 +978,36 @@
                         try {
                             service.disableNat(mIfaceName, mUpstreamIfaceName);
                         } catch (Exception e) {
-                            transitionTo(mDisableNatErrorState);
+                            try {
+                                service.untetherInterface(mIfaceName);
+                            } catch (Exception ee) {}
+
+                            setLastErrorAndTransitionToInitialState(
+                                    ConnectivityManager.TETHER_ERROR_DISABLE_NAT_ERROR);
                             break;
                         }
                         try {
                             service.untetherInterface(mIfaceName);
                         } catch (Exception e) {
-                            transitionTo(mUntetherInterfaceErrorState);
+                            setLastErrorAndTransitionToInitialState(
+                                    ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR);
                             break;
                         }
                         if (error) {
-                            transitionTo(mMasterTetherErrorState);
+                            setLastErrorAndTransitionToInitialState(
+                                    ConnectivityManager.TETHER_ERROR_MASTER_ERROR);
                             break;
                         }
                         Log.d(TAG, "Tether lost upstream connection " + mIfaceName);
                         sendTetherStateChangedBroadcast();
                         if (mUsb) {
                             if (!Tethering.this.configureUsbIface(false)) {
-                                transitionTo(mUsbConfigurationErrorState);
-                                break;
+                                setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
                             }
                         }
                         transitionTo(mInitialState);
                         break;
-                    case CMD_TRANSITION_TO_ERROR:
+                    case CMD_TRANSITION_TO_STATE:
                         HierarchicalState s = (HierarchicalState)(message.obj);
                         transitionTo(s);
                         break;
@@ -971,7 +1023,7 @@
             @Override
             public void enter() {
                 setAvailable(false);
-                setErrored(false);
+                setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
                 setTethered(false);
                 sendTetherStateChangedBroadcast();
             }
@@ -990,95 +1042,11 @@
             }
         }
 
-
-        class ErrorState extends HierarchicalState {
-            int mErrorNotification;
-            @Override
-            public boolean processMessage(Message message) {
-                boolean retValue = true;
-                switch (message.what) {
-                    case CMD_TETHER_REQUESTED:
-                        sendTetherStateChangedBroadcast();
-                        break;
-                    default:
-                        retValue = false;
-                        break;
-                }
-                return retValue;
-            }
+        void setLastErrorAndTransitionToInitialState(int error) {
+            setLastError(error);
+            transitionTo(mInitialState);
         }
 
-        class MasterTetherErrorState extends ErrorState {
-            @Override
-            public void enter() {
-                Log.e(TAG, "Error in Master Tether state " + mIfaceName);
-                setAvailable(false);
-                setErrored(true);
-                sendTetherStateChangedBroadcast();
-            }
-        }
-
-        class TetherInterfaceErrorState extends ErrorState {
-            @Override
-            public void enter() {
-                Log.e(TAG, "Error trying to tether " + mIfaceName);
-                setAvailable(false);
-                setErrored(true);
-                sendTetherStateChangedBroadcast();
-            }
-        }
-
-        class UntetherInterfaceErrorState extends ErrorState {
-            @Override
-            public void enter() {
-                Log.e(TAG, "Error trying to untether " + mIfaceName);
-                setAvailable(false);
-                setErrored(true);
-                sendTetherStateChangedBroadcast();
-            }
-        }
-
-        class EnableNatErrorState extends ErrorState {
-            @Override
-            public void enter() {
-                Log.e(TAG, "Error trying to enable NAT " + mIfaceName);
-                setAvailable(false);
-                setErrored(true);
-
-                IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
-                INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
-                try {
-                    service.untetherInterface(mIfaceName);
-                } catch (Exception e) {}
-                sendTetherStateChangedBroadcast();
-            }
-        }
-
-
-        class DisableNatErrorState extends ErrorState {
-            @Override
-            public void enter() {
-                Log.e(TAG, "Error trying to disable NAT " + mIfaceName);
-                setAvailable(false);
-                setErrored(true);
-
-                IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
-                INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
-                try {
-                    service.untetherInterface(mIfaceName);
-                } catch (Exception e) {}
-                sendTetherStateChangedBroadcast();
-            }
-        }
-
-        class UsbConfigurationErrorState extends ErrorState {
-            @Override
-            public void enter() {
-                Log.e(TAG, "Error trying to configure USB " + mIfaceName);
-                setAvailable(false);
-                setErrored(true);
-            }
-        }
     }
 
     class TetherMasterSM extends HierarchicalStateMachine {