Merge "Remove "unlinklib" command from installd" into jb-mr1-dev
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 829620b..652356c 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -1146,6 +1146,31 @@
     public native final void setDisplayOrientation(int degrees);
 
     /**
+     * Enable or disable the default shutter sound when taking a picture.
+     *
+     * By default, the camera plays the system-defined camera shutter sound when
+     * {@link #takePicture} is called. Using this method, the shutter sound can
+     * be disabled. It is strongly recommended that an alternative shutter sound
+     * is played in the {@link ShutterCallback} when the system shutter sound is
+     * disabled.
+     *
+     * Note that devices may not always allow control of the camera shutter
+     * sound. If the shutter sound cannot be controlled, this method will return
+     * false.
+     *
+     * @param enabled whether the camera should play the system shutter sound
+     *                when {@link #takePicture takePicture} is called.
+     * @return true if the shutter sound state was successfully changed. False
+     *         if the shutter sound cannot be controlled; in this case, the
+     *         application should not play its own shutter sound since the
+     *         system shutter sound will play when a picture is taken.
+     * @see #takePicture
+     * @see ShutterCallback
+     * @hide
+     */
+    public native final boolean enableShutterSound(boolean enabled);
+
+    /**
      * Callback interface for zoom changes during a smooth zoom operation.
      *
      * @see #setZoomChangeListener(OnZoomChangeListener)
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index 9e4ec3c..fea427d 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -107,9 +107,6 @@
     // Key store handler when Chromium HTTP stack is used.
     private KeyStoreHandler mKeyStoreHandler = null;
 
-    // Implementation of the searchbox API.
-    private final SearchBoxImpl mSearchBox;
-
     // message ids
     // a message posted when a frame loading is completed
     static final int FRAME_COMPLETED = 1001;
@@ -255,8 +252,6 @@
         mDatabase = WebViewDatabaseClassic.getInstance(appContext);
         mWebViewCore = w;
 
-        mSearchBox = new SearchBoxImpl(mWebViewCore, mCallbackProxy);
-
         AssetManager am = context.getAssets();
         nativeCreateFrame(w, am, proxy.getBackForwardList());
 
@@ -1217,10 +1212,6 @@
         }
     }
 
-    /*package*/ SearchBox getSearchBox() {
-        return mSearchBox;
-    }
-
     /**
      * Called by JNI when processing the X-Auto-Login header.
      */
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index b47cba8..a326da2 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -117,11 +117,8 @@
     private static final int ADD_HISTORY_ITEM                     = 135;
     private static final int HISTORY_INDEX_CHANGED                = 136;
     private static final int AUTH_CREDENTIALS                     = 137;
-    private static final int NOTIFY_SEARCHBOX_LISTENERS           = 139;
     private static final int AUTO_LOGIN                           = 140;
     private static final int CLIENT_CERT_REQUEST                  = 141;
-    private static final int SEARCHBOX_IS_SUPPORTED_CALLBACK      = 142;
-    private static final int SEARCHBOX_DISPATCH_COMPLETE_CALLBACK = 143;
     private static final int PROCEEDED_AFTER_SSL_ERROR            = 144;
 
     // Message triggered by the client to resume execution
@@ -871,14 +868,6 @@
                         host, realm, username, password);
                 break;
             }
-            case NOTIFY_SEARCHBOX_LISTENERS: {
-                SearchBoxImpl searchBox = (SearchBoxImpl) mWebView.getSearchBox();
-
-                @SuppressWarnings("unchecked")
-                List<String> suggestions = (List<String>) msg.obj;
-                searchBox.handleSuggestions(msg.getData().getString("query"), suggestions);
-                break;
-            }
             case AUTO_LOGIN: {
                 if (mWebViewClient != null) {
                     String realm = msg.getData().getString("realm");
@@ -889,19 +878,6 @@
                 }
                 break;
             }
-            case SEARCHBOX_IS_SUPPORTED_CALLBACK: {
-                SearchBoxImpl searchBox = (SearchBoxImpl) mWebView.getSearchBox();
-                Boolean supported = (Boolean) msg.obj;
-                searchBox.handleIsSupportedCallback(supported);
-                break;
-            }
-            case SEARCHBOX_DISPATCH_COMPLETE_CALLBACK: {
-                SearchBoxImpl searchBox = (SearchBoxImpl) mWebView.getSearchBox();
-                Boolean success = (Boolean) msg.obj;
-                searchBox.handleDispatchCompleteCallback(msg.getData().getString("function"),
-                        msg.getData().getInt("id"), success);
-                break;
-            }
         }
     }
 
@@ -1629,29 +1605,6 @@
         return mContext instanceof Activity;
     }
 
-    void onSearchboxSuggestionsReceived(String query, List<String> suggestions) {
-        Message msg = obtainMessage(NOTIFY_SEARCHBOX_LISTENERS);
-        msg.obj = suggestions;
-        msg.getData().putString("query", query);
-
-        sendMessage(msg);
-    }
-
-    void onIsSupportedCallback(boolean isSupported) {
-        Message msg = obtainMessage(SEARCHBOX_IS_SUPPORTED_CALLBACK);
-        msg.obj = Boolean.valueOf(isSupported);
-        sendMessage(msg);
-    }
-
-    void onSearchboxDispatchCompleteCallback(String function, int id, boolean success) {
-        Message msg = obtainMessage(SEARCHBOX_DISPATCH_COMPLETE_CALLBACK);
-        msg.obj = Boolean.valueOf(success);
-        msg.getData().putString("function", function);
-        msg.getData().putInt("id", id);
-
-        sendMessage(msg);
-    }
-
     private synchronized void sendMessageToUiThreadSync(Message msg) {
         sendMessage(msg);
         WebCoreThreadWatchdog.pause();
diff --git a/core/java/android/webkit/SearchBox.java b/core/java/android/webkit/SearchBox.java
deleted file mode 100644
index 38a1740..0000000
--- a/core/java/android/webkit/SearchBox.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package android.webkit;
-
-import java.util.List;
-
-/**
- * Defines the interaction between the browser/renderer and the page running on
- * a given WebView frame, if the page supports the chromium SearchBox API.
- *
- * http://dev.chromium.org/searchbox
- *
- * The browser or container app can query the page for search results using
- * SearchBox.query() and receive suggestions by registering a listener on the
- * SearchBox object.
- *
- * @hide
- */
-public interface SearchBox {
-    /**
-     * Sets the current searchbox query. Note that the caller must call
-     * onchange() to ensure that the search page processes this query.
-     */
-    void setQuery(String query);
-
-    /**
-     * Verbatim is true if the caller suggests that the search page
-     * treat the current query as a verbatim search query (as opposed to a
-     * partially typed search query). As with setQuery, onchange() must be
-     * called to ensure that the search page processes the query.
-     */
-    void setVerbatim(boolean verbatim);
-
-    /**
-     * These attributes must contain the offset to the characters that immediately
-     * follow the start and end of the selection in the search box. If there is
-     * no such selection, then both selectionStart and selectionEnd must be the offset
-     * to the character that immediately follows the text entry cursor. In the case
-     * that there is no explicit text entry cursor, the cursor is
-     * implicitly at the end of the input.
-     */
-    void setSelection(int selectionStart, int selectionEnd);
-
-    /**
-     * Sets the dimensions of the view (if any) that overlaps the current
-     * window object. This is to ensure that the page renders results in
-     * a manner that allows them to not be obscured by such a view. Note
-     * that a call to onresize() is required if these dimensions change.
-     */
-    void setDimensions(int x, int y, int width, int height);
-
-    /**
-     * Notify the search page of any changes to the searchbox. Such as
-     * a change in the typed query (onchange), the user commiting a given query
-     * (onsubmit), or a change in size of a suggestions dropdown (onresize).
-     *
-     * @param listener an optional listener to notify of the success of the operation,
-     *      indicating if the javascript function existed and could be called or not.
-     *      It will be called on the UI thread.
-     */
-    void onchange(SearchBoxListener listener);
-    void onsubmit(SearchBoxListener listener);
-    void onresize(SearchBoxListener listener);
-    void oncancel(SearchBoxListener listener);
-
-    /**
-     * Add and remove listeners to the given Searchbox. Listeners are notified
-     * of any suggestions to the query that the underlying search engine might
-     * provide.
-     */
-    void addSearchBoxListener(SearchBoxListener l);
-    void removeSearchBoxListener(SearchBoxListener l);
-
-    /**
-     * Indicates if the searchbox API is supported in the current page.
-     */
-    void isSupported(IsSupportedCallback callback);
-
-    /**
-     * Listeners (if any) will be called on the thread that created the
-     * webview.
-     */
-    public abstract class SearchBoxListener {
-        public void onSuggestionsReceived(String query, List<String> suggestions) {}
-        public void onChangeComplete(boolean called) {}
-        public void onSubmitComplete(boolean called) {}
-        public void onResizeComplete(boolean called) {}
-        public void onCancelComplete(boolean called) {}
-    }
-
-    interface IsSupportedCallback {
-        void searchBoxIsSupported(boolean supported);
-    }
-}
diff --git a/core/java/android/webkit/SearchBoxImpl.java b/core/java/android/webkit/SearchBoxImpl.java
deleted file mode 100644
index 9942d25..0000000
--- a/core/java/android/webkit/SearchBoxImpl.java
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.webkit;
-
-import android.text.TextUtils;
-import android.util.Log;
-import android.webkit.WebViewCore.EventHub;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.json.JSONStringer;
-
-/**
- * The default implementation of the SearchBox interface. Implemented
- * as a java bridge object and a javascript adapter that is called into
- * by the page hosted in the frame.
- */
-final class SearchBoxImpl implements SearchBox {
-    private static final String TAG = "WebKit.SearchBoxImpl";
-
-    /* package */ static final String JS_INTERFACE_NAME = "searchBoxJavaBridge_";
-
-    /* package */ static final String JS_BRIDGE
-            = "(function()"
-            + "{"
-            + "if (!window.chrome) {"
-            + "  window.chrome = {};"
-            + "}"
-            + "if (!window.chrome.searchBox) {"
-            + "  var sb = window.chrome.searchBox = {};"
-            + "  sb.setSuggestions = function(suggestions) {"
-            + "    if (window.searchBoxJavaBridge_) {"
-            + "      window.searchBoxJavaBridge_.setSuggestions(JSON.stringify(suggestions));"
-            + "    }"
-            + "  };"
-            + "  sb.setValue = function(valueArray) { sb.value = valueArray[0]; };"
-            + "  sb.value = '';"
-            + "  sb.x = 0;"
-            + "  sb.y = 0;"
-            + "  sb.width = 0;"
-            + "  sb.height = 0;"
-            + "  sb.selectionStart = 0;"
-            + "  sb.selectionEnd = 0;"
-            + "  sb.verbatim = false;"
-            + "}"
-            + "})();";
-
-    private static final String SET_QUERY_SCRIPT
-            = "if (window.chrome && window.chrome.searchBox) {"
-            + "  window.chrome.searchBox.setValue(%s);"
-            + "}";
-
-    private static final String SET_VERBATIM_SCRIPT
-            =  "if (window.chrome && window.chrome.searchBox) {"
-            + "  window.chrome.searchBox.verbatim = %1$s;"
-            + "}";
-
-    private static final String SET_SELECTION_SCRIPT
-            = "if (window.chrome && window.chrome.searchBox) {"
-            + "  var f = window.chrome.searchBox;"
-            + "  f.selectionStart = %d"
-            + "  f.selectionEnd = %d"
-            + "}";
-
-    private static final String SET_DIMENSIONS_SCRIPT
-            = "if (window.chrome && window.chrome.searchBox) { "
-            + "  var f = window.chrome.searchBox;"
-            + "  f.x = %d;"
-            + "  f.y = %d;"
-            + "  f.width = %d;"
-            + "  f.height = %d;"
-            + "}";
-
-    private static final String DISPATCH_EVENT_SCRIPT
-            = "if (window.chrome && window.chrome.searchBox && window.chrome.searchBox.on%1$s) {"
-            + "  window.chrome.searchBox.on%1$s();"
-            + "  window.searchBoxJavaBridge_.dispatchCompleteCallback('%1$s', %2$d, true);"
-            + "} else {"
-            + "  window.searchBoxJavaBridge_.dispatchCompleteCallback('%1$s', %2$d, false);"
-            + "}";
-
-    private static final String EVENT_CHANGE = "change";
-    private static final String EVENT_SUBMIT = "submit";
-    private static final String EVENT_RESIZE = "resize";
-    private static final String EVENT_CANCEL = "cancel";
-
-    private static final String IS_SUPPORTED_SCRIPT
-            = "if (window.searchBoxJavaBridge_) {"
-            + "  if (window.chrome && window.chrome.sv) {"
-            + "    window.searchBoxJavaBridge_.isSupportedCallback(true);"
-            + "  } else {"
-            + "    window.searchBoxJavaBridge_.isSupportedCallback(false);"
-            + "  }}";
-
-    private final List<SearchBoxListener> mListeners;
-    private final WebViewCore mWebViewCore;
-    private final CallbackProxy mCallbackProxy;
-    private IsSupportedCallback mSupportedCallback;
-    private int mNextEventId = 1;
-    private final HashMap<Integer, SearchBoxListener> mEventCallbacks;
-
-    SearchBoxImpl(WebViewCore webViewCore, CallbackProxy callbackProxy) {
-        mListeners = new ArrayList<SearchBoxListener>();
-        mWebViewCore = webViewCore;
-        mCallbackProxy = callbackProxy;
-        mEventCallbacks = new HashMap<Integer, SearchBoxListener>();
-    }
-
-    @Override
-    public void setQuery(String query) {
-        final String formattedQuery = jsonSerialize(query);
-        if (formattedQuery != null) {
-            final String js = String.format(SET_QUERY_SCRIPT, formattedQuery);
-            dispatchJs(js);
-        }
-    }
-
-    @Override
-    public void setVerbatim(boolean verbatim) {
-        final String js = String.format(SET_VERBATIM_SCRIPT, String.valueOf(verbatim));
-        dispatchJs(js);
-    }
-
-
-    @Override
-    public void setSelection(int selectionStart, int selectionEnd) {
-        final String js = String.format(SET_SELECTION_SCRIPT, selectionStart, selectionEnd);
-        dispatchJs(js);
-    }
-
-    @Override
-    public void setDimensions(int x, int y, int width, int height) {
-        final String js = String.format(SET_DIMENSIONS_SCRIPT, x, y, width, height);
-        dispatchJs(js);
-    }
-
-    @Override
-    public void onchange(SearchBoxListener callback) {
-        dispatchEvent(EVENT_CHANGE, callback);
-    }
-
-    @Override
-    public void onsubmit(SearchBoxListener callback) {
-        dispatchEvent(EVENT_SUBMIT, callback);
-    }
-
-    @Override
-    public void onresize(SearchBoxListener callback) {
-        dispatchEvent(EVENT_RESIZE, callback);
-    }
-
-    @Override
-    public void oncancel(SearchBoxListener callback) {
-        dispatchEvent(EVENT_CANCEL, callback);
-    }
-
-    private void dispatchEvent(String eventName, SearchBoxListener callback) {
-        int eventId;
-        if (callback != null) {
-            synchronized(this) {
-                eventId = mNextEventId++;
-                mEventCallbacks.put(eventId, callback);
-            }
-        } else {
-            eventId = 0;
-        }
-        final String js = String.format(DISPATCH_EVENT_SCRIPT, eventName, eventId);
-        dispatchJs(js);
-    }
-
-    private void dispatchJs(String js) {
-        mWebViewCore.sendMessage(EventHub.EXECUTE_JS, js);
-    }
-
-    @Override
-    public void addSearchBoxListener(SearchBoxListener l) {
-        synchronized (mListeners) {
-            mListeners.add(l);
-        }
-    }
-
-    @Override
-    public void removeSearchBoxListener(SearchBoxListener l) {
-        synchronized (mListeners) {
-            mListeners.remove(l);
-        }
-    }
-
-    @Override
-    public void isSupported(IsSupportedCallback callback) {
-        mSupportedCallback = callback;
-        dispatchJs(IS_SUPPORTED_SCRIPT);
-    }
-
-    // Called by Javascript through the Java bridge.
-    public void isSupportedCallback(boolean isSupported) {
-        mCallbackProxy.onIsSupportedCallback(isSupported);
-    }
-
-    public void handleIsSupportedCallback(boolean isSupported) {
-        IsSupportedCallback callback = mSupportedCallback;
-        mSupportedCallback = null;
-        if (callback != null) {
-            callback.searchBoxIsSupported(isSupported);
-        }
-    }
-
-    // Called by Javascript through the Java bridge.
-    public void dispatchCompleteCallback(String function, int id, boolean successful) {
-        mCallbackProxy.onSearchboxDispatchCompleteCallback(function, id, successful);
-    }
-
-    public void handleDispatchCompleteCallback(String function, int id, boolean successful) {
-        if (id != 0) {
-            SearchBoxListener listener;
-            synchronized(this) {
-                listener = mEventCallbacks.get(id);
-                mEventCallbacks.remove(id);
-            }
-            if (listener != null) {
-                if (TextUtils.equals(EVENT_CHANGE, function)) {
-                    listener.onChangeComplete(successful);
-                } else if (TextUtils.equals(EVENT_SUBMIT, function)) {
-                    listener.onSubmitComplete(successful);
-                } else if (TextUtils.equals(EVENT_RESIZE, function)) {
-                    listener.onResizeComplete(successful);
-                } else if (TextUtils.equals(EVENT_CANCEL, function)) {
-                    listener.onCancelComplete(successful);
-                }
-            }
-        }
-    }
-
-    // This is used as a hackish alternative to javascript escaping.
-    // There appears to be no such functionality in the core framework.
-    private static String jsonSerialize(String query) {
-        JSONStringer stringer = new JSONStringer();
-        try {
-            stringer.array().value(query).endArray();
-        } catch (JSONException e) {
-            Log.w(TAG, "Error serializing query : " + query);
-            return null;
-        }
-        return stringer.toString();
-    }
-
-    // Called by Javascript through the Java bridge.
-    public void setSuggestions(String jsonArguments) {
-        if (jsonArguments == null) {
-            return;
-        }
-
-        String query = null;
-        List<String> suggestions = new ArrayList<String>();
-        try {
-            JSONObject suggestionsJson = new JSONObject(jsonArguments);
-            query = suggestionsJson.getString("query");
-
-            final JSONArray suggestionsArray = suggestionsJson.getJSONArray("suggestions");
-            for (int i = 0; i < suggestionsArray.length(); ++i) {
-                final JSONObject suggestion = suggestionsArray.getJSONObject(i);
-                final String value = suggestion.getString("value");
-                if (value != null) {
-                    suggestions.add(value);
-                }
-                // We currently ignore the "type" of the suggestion. This isn't
-                // documented anywhere in the API documents.
-                // final String type = suggestions.getString("type");
-            }
-        } catch (JSONException je) {
-            Log.w(TAG, "Error parsing json [" + jsonArguments + "], exception = " + je);
-            return;
-        }
-
-        mCallbackProxy.onSearchboxSuggestionsReceived(query, suggestions);
-    }
-
-    /* package */ void handleSuggestions(String query, List<String> suggestions) {
-        synchronized (mListeners) {
-            for (int i = mListeners.size() - 1; i >= 0; i--) {
-                mListeners.get(i).onSuggestionsReceived(query, suggestions);
-            }
-        }
-    }
-}
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index 351c4f3..10f7181 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -5367,10 +5367,7 @@
      * This is an implementation detail.
      */
     public SearchBox getSearchBox() {
-        if ((mWebViewCore == null) || (mWebViewCore.getBrowserFrame() == null)) {
-            return null;
-        }
-        return mWebViewCore.getBrowserFrame().getSearchBox();
+        return null;
     }
 
     /**
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 6cd8955..e1e97a1 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -781,6 +781,25 @@
     }
 }
 
+static jboolean android_hardware_Camera_enableShutterSound(JNIEnv *env, jobject thiz,
+        jboolean enabled)
+{
+    ALOGV("enableShutterSound");
+    sp<Camera> camera = get_native_camera(env, thiz, NULL);
+    if (camera == 0) return JNI_FALSE;
+
+    int32_t value = (enabled == JNI_TRUE) ? 1 : 0;
+    status_t rc = camera->sendCommand(CAMERA_CMD_ENABLE_SHUTTER_SOUND, value, 0);
+    if (rc == NO_ERROR) {
+        return JNI_TRUE;
+    } else if (rc == PERMISSION_DENIED) {
+        return JNI_FALSE;
+    } else {
+        jniThrowRuntimeException(env, "enable shutter sound failed");
+        return JNI_FALSE;
+    }
+}
+
 static void android_hardware_Camera_startFaceDetection(JNIEnv *env, jobject thiz,
         jint type)
 {
@@ -890,6 +909,9 @@
   { "setDisplayOrientation",
     "(I)V",
     (void *)android_hardware_Camera_setDisplayOrientation },
+  { "enableShutterSound",
+    "(Z)Z",
+    (void *)android_hardware_Camera_enableShutterSound },
   { "_startFaceDetection",
     "(I)V",
     (void *)android_hardware_Camera_startFaceDetection },
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index bba2252..f0e5510 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -923,5 +923,5 @@
     <bool name="config_syncstorageengine_masterSyncAutomatically">true</bool>
 
     <!--  Maximum number of supported users -->
-    <integer name="config_multiuserMaximumUsers">10</integer>
+    <integer name="config_multiuserMaximumUsers">1</integer>
 </resources>
diff --git a/docs/html/guide/google/play/publishing/multiple-apks.jd b/docs/html/guide/google/play/publishing/multiple-apks.jd
index e41817e..0619dfc 100644
--- a/docs/html/guide/google/play/publishing/multiple-apks.jd
+++ b/docs/html/guide/google/play/publishing/multiple-apks.jd
@@ -9,9 +9,7 @@
   <ul>
     <li>Simultaneously publish different APKs for different
 device configurations</li>
-    <li>Different APKs are distributed to different devices based on filters declared in the
-manifest file</li>
-    <li>You should publish multiple APKs only when it's not possible or reasonable to
+    <li>You should publish multiple APKs only when it's not possible to
 support all desired devices with a single APK</li>
   </ul>
 
@@ -39,16 +37,17 @@
       <li><a href="#TextureOptions">Supporting multiple GL textures</a></li>
       <li><a href="#ScreenOptions">Supporting multiple screens</a></li>
       <li><a href="#ApiLevelOptions">Supporting multiple API levels</a></li>
+      <li><a href="#CpuArchOptions">Supporting multiple CPU architectures</a></li>
     </ol>
   </li>
 </ol>
 
   <h2>See also</h2>
 <ol>
+  <li><a href="{@docRoot}guide/google/play/expansion-files.html">APK Expansion Files</a></li>
   <li><a href="{@docRoot}guide/google/play/filters.html">Filters on Google Play</a></li>
   <li><a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple Screens</a></li>
-  <li><a href="{@docRoot}tools/extras/support-library.html">Compatibility
-Package</a></li>
+  <li><a href="{@docRoot}tools/extras/support-library.html">Support Library</a></li>
   <li><a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#ApiLevels">Android API Levels</a></li>
 </ol>
 
@@ -76,14 +75,16 @@
 you publish your application for as many devices as possible, Google Play allows you to
 publish multiple APKs under the same application listing. Google Play then supplies each APK to
 the appropriate devices based on configuration support you've declared in the manifest file of each
-APK.</p>
+APK. </p>
 
 <p>By publishing your application with multiple APKs, you can:</p>
 
 <ul>
   <li>Support different OpenGL texture compression formats with each APK.</li>
-  <li>Support different screen configurations with each APK.</li>
+  <li>Support different screen sizes and densities with each APK.</li>
   <li>Support different platform versions with each APK.</li>
+  <li>Support different CPU architectures with each APK (such as for ARM, x86, and MIPS, when your
+  app uses the <a href="{@docRoot}tools/sdk/ndk/index.html">Android NDK</a>).</li>
 </ul>
 
 <p>Currently, these are the only device characteristics that Google Play supports for publishing
@@ -91,7 +92,8 @@
 
 <p class="note"><strong>Note:</strong> You should generally use multiple APKs to support
 different device configurations <strong>only when your APK is too large</strong> (greater than
-50MB). Using a single APK to support different configurations is always the best practice,
+50MB) due to the alternative resources needed for different device configurations.
+Using a single APK to support different configurations is always the best practice,
 because it makes the path for application updates simple and clear for users (and also makes
 your life simpler by avoiding development and publishing complexity). Read the section below about
 <a href="#SingleAPK">Using a Single APK Instead</a> to
@@ -283,14 +285,19 @@
     </ul>
   </div>
   </li>
+
+  <li><strong>CPU architecture (ABI)</strong>
+    <p>This is based on the native libraries included in each APK (which are
+    determined by the architectures you declare in the {@code Application.mk}
+    file) when using the Android NDK.</p></li>
 </ul>
 
 <p>Other manifest elements that enable <a
 href="{@docRoot}guide/google/play/filters.html">Google Play filters</a>&mdash;but are not
 listed above&mdash;are still applied for each APK as usual. However, Google Play does not allow
-you to publish multiple APKs based on variations of them. Thus, you cannot publish
-multiple APKs if the above listed filters are the same for each APK (but the APKs differ based on
-other characteristics in the manifest file). For
+you to publish separate APKs based on variations of those device characteristics. Thus, you cannot
+publish multiple APKs if the above listed filters are the same for each APK (but the APKs differ
+based on other characteristics in the manifest or APK). For
 example, you cannot provide different APKs that differ purely on the <a
 href="{@docRoot}guide/topics/manifest/uses-configuration-element.html">{@code
 &lt;uses-configuration&gt;}</a> characteristics.</p>
@@ -349,7 +356,8 @@
 get an update when they receive a system update.</li>
       <li>If you have one APK that's for API level 4 (and above) <em>and</em> small -
 large screens, and another APK for API level 8 (and above) <em>and</em> large - xlarge screens, then
-the version codes <strong>must increase</strong>. In this case, the API level filter is used to
+the version codes <strong>must increase</strong> in correlation with the API levels.
+In this case, the API level filter is used to
 distinguish each APK, but so is the screen size. Because the screen sizes overlap (both APKs
 support large screens), the version codes must still be in order. This ensures that a large screen
 device that receives a system update to API level 8 will receive an update for the second
@@ -360,6 +368,21 @@
 levels. Because there is no overlap within the screen size filter, there are no devices that
 could potentially move between these two APKs, so there's no need for the version codes to
 increase from the lower API level to the higher API level.</li>
+      <li>If you have one APK that's for API level 4 (and above) <em>and</em> ARMv7 CPUs,
+and another APK for API level 8 (and above) <em>and</em> ARMv5TE CPUs,
+then the version codes <strong>must increase</strong> in correlation with the API levels.
+In this case, the API level filter is used to
+distinguish each APK, but so is the CPU architecture. Because an APK with ARMv5TE libraries is
+compatible with devices that have an ARMv7 CPU, the APKs overlap on this characteristic.
+As such, the version code for the APK that supports API level 8 and above must be higher.
+This ensures that a device with an ARMv7 CPU that receives a system update to API level 8
+will receive an update for the second APK that's designed for API level 8.
+However, because this kind of update results in the ARMv7 device using an APK that's not
+fully optimized for that device's CPU, you should provide an
+APK for both the ARMv5TE and the ARMv7 architecture at each API level in order to optimize
+the app performance on each CPU.
+<strong>Note:</strong> This applies <em>only</em> when comparing APKs with the ARMv5TE and
+ARMv7 libraries, and not when comparing other native libraries.</li>
     </ul>
   </li>
 
@@ -384,7 +407,12 @@
 sizes small, normal, and large, while another APK supports sizes large and xlarge, there is an
 overlap, because both APKs support large screens. If you do not resolve this, then devices that
 qualify for both APKs (large screen devices in the example) will receive whichever APK has the
-highest version code.</li>
+highest version code.
+  <p class="note"><strong>Note:</strong> If you're creating separate APKs for different CPU
+  architectures, be aware that an APK for ARMv5TE will overlap with an APK for ARMv7. That is,
+  an APK designed for ARMv5TE is compatible with an ARMv7 device,
+but the reverse is not true (an APK with only the ARMv7 libraries is
+<em>not</em> compatible with an ARMv5TE device).</li>
 </ul>
 
 <p>When such conflicts occur, you will see a warning message, but you can still publish your
@@ -641,3 +669,17 @@
 }
 </pre>
 
+
+<h3 id="CpuArchOptions">Supporting multiple CPU architectures</h3>
+
+<p>When using the Android NDK, you can create a single APK that supports multiple CPU architectures
+by declaring each of the desired architectures with the {@code APP_ABI} variable in the
+<code>Application.mk</code> file.</p>
+
+<p>For example, here's an <code>Application.mk</code> file that declares support for three
+different CPU architectures:</p>
+
+<pre>
+APP_ABI := armeabi armeabi-v7a mips
+APP_PLATFORM := android-9
+</pre>
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index b5613f3..ea00ec8 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1164,7 +1164,7 @@
     /**
      * Indicates if current platform supports use of SCO for off call use cases.
      * Application wanted to use bluetooth SCO audio when the phone is not in call
-     * must first call thsi method to make sure that the platform supports this
+     * must first call this method to make sure that the platform supports this
      * feature.
      * @return true if bluetooth SCO can be used for audio when not in call
      *         false otherwise
@@ -1300,6 +1300,19 @@
     }
 
     /**
+     * @hide
+     * Signals whether remote submix audio rerouting is enabled.
+     */
+    public void setRemoteSubmixOn(boolean on, int address) {
+        IAudioService service = getService();
+        try {
+            service.setRemoteSubmixOn(on, address);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in setRemoteSubmixOn", e);
+        }
+    }
+
+    /**
      * Sets audio routing to the wired headset on or off.
      *
      * @param on set <var>true</var> to route audio to/from wired
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index aea8a88..e5c2a4d 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -151,6 +151,8 @@
     private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 21;
     private static final int MSG_SET_A2DP_CONNECTION_STATE = 22;
     // end of messages handled under wakelock
+    private static final int MSG_SET_RSX_CONNECTION_STATE = 23; // change remote submix connection
+    private static final int MSG_SET_FORCE_RSX_USE = 24;        // force remote submix audio routing
 
     // flags for MSG_PERSIST_VOLUME indicating if current and/or last audible volume should be
     // persisted
@@ -2109,6 +2111,33 @@
         }
     };
 
+    /** see AudioManager.setRemoteSubmixOn(boolean on) */
+    public void setRemoteSubmixOn(boolean on, int address) {
+        sendMsg(mAudioHandler, MSG_SET_RSX_CONNECTION_STATE,
+                SENDMSG_REPLACE /* replace with QUEUE when multiple addresses are supported */,
+                on ? 1 : 0 /*arg1*/,
+                address /*arg2*/,
+                null/*obj*/, 0/*delay*/);
+
+        // Note that we are  currently forcing use of remote submix as soon as corresponding device
+        //   is made available
+        sendMsg(mAudioHandler, MSG_SET_FORCE_RSX_USE, SENDMSG_REPLACE,
+                AudioSystem.FOR_MEDIA,
+                on ? AudioSystem.FORCE_REMOTE_SUBMIX : AudioSystem.FORCE_NONE,
+                null/*obj*/, 0/*delay*/);
+    }
+
+    private void onSetRsxConnectionState(int available, int address) {
+        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_REMOTE_SUBMIX,
+                available == 1 ?
+                        AudioSystem.DEVICE_STATE_AVAILABLE : AudioSystem.DEVICE_STATE_UNAVAILABLE,
+                String.valueOf(address) /*device_address*/);
+        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX,
+                available == 1 ?
+                        AudioSystem.DEVICE_STATE_AVAILABLE : AudioSystem.DEVICE_STATE_UNAVAILABLE,
+                String.valueOf(address) /*device_address*/);
+    }
+
     ///////////////////////////////////////////////////////////////////////////
     // Internal methods
     ///////////////////////////////////////////////////////////////////////////
@@ -3072,6 +3101,7 @@
 
                 case MSG_SET_FORCE_USE:
                 case MSG_SET_FORCE_BT_A2DP_USE:
+                case MSG_SET_FORCE_RSX_USE:
                     setForceUse(msg.arg1, msg.arg2);
                     break;
 
@@ -3134,6 +3164,10 @@
                     onRegisterVolumeObserverForRcc(msg.arg1 /* rccId */,
                             (IRemoteVolumeObserver)msg.obj /* rvo */);
                     break;
+
+                case MSG_SET_RSX_CONNECTION_STATE:
+                    onSetRsxConnectionState(msg.arg1/*available*/, msg.arg2/*address*/);
+                    break;
             }
         }
     }
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index f3a8558..129e84f 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -214,6 +214,7 @@
     public static final int DEVICE_OUT_REMOTE_SUBMIX = 0x8000;
 
     public static final int DEVICE_OUT_DEFAULT = DEVICE_BIT_DEFAULT;
+
     public static final int DEVICE_OUT_ALL = (DEVICE_OUT_EARPIECE |
                                               DEVICE_OUT_SPEAKER |
                                               DEVICE_OUT_WIRED_HEADSET |
@@ -352,7 +353,8 @@
     public static final int FORCE_ANALOG_DOCK = 8;
     public static final int FORCE_DIGITAL_DOCK = 9;
     public static final int FORCE_NO_BT_A2DP = 10;
-    private static final int NUM_FORCE_CONFIG = 11;
+    public static final int FORCE_REMOTE_SUBMIX = 11;
+    private static final int NUM_FORCE_CONFIG = 12;
     public static final int FORCE_DEFAULT = FORCE_NONE;
 
     // usage for setForceUse, must match AudioSystem::force_use
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 854eb3f..7ae61cd 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -108,6 +108,8 @@
 
     boolean isBluetoothA2dpOn();
 
+    oneway void setRemoteSubmixOn(boolean on, int address);
+
     int requestAudioFocus(int mainStreamType, int durationHint, IBinder cb, IAudioFocusDispatcher l,
             String clientId, String callingPackageName);
 
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 2d1be02..48bea52 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -177,6 +177,12 @@
          *  is applied.
          */
         public static final int VOICE_COMMUNICATION = 7;
+
+        /**
+         * @hide
+         * Audio source for remote submix.
+         */
+        public static final int REMOTE_SUBMIX_SOURCE = 8;
     }
 
     /**
@@ -291,7 +297,12 @@
      * Gets the maximum value for audio sources.
      * @see android.media.MediaRecorder.AudioSource
      */
-    public static final int getAudioSourceMax() { return AudioSource.VOICE_COMMUNICATION; }
+    public static final int getAudioSourceMax() {
+        // FIXME disable selection of the remote submxi source selection once test code
+        //       doesn't rely on it
+        return AudioSource.REMOTE_SUBMIX_SOURCE;
+        //return AudioSource.VOICE_COMMUNICATION;
+    }
 
     /**
      * Sets the video source to be used for recording. If this method is not
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 7532d79..79250fb 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -103,8 +103,8 @@
     private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
 
     private static final String STORAGE_WHERE = Files.FileColumns.STORAGE_ID + "=?";
-    private static final String FORMAT_WHERE = Files.FileColumns.PARENT + "=?";
-    private static final String PARENT_WHERE = Files.FileColumns.FORMAT + "=?";
+    private static final String FORMAT_WHERE = Files.FileColumns.FORMAT + "=?";
+    private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
     private static final String STORAGE_FORMAT_WHERE = STORAGE_WHERE + " AND "
                                             + Files.FileColumns.FORMAT + "=?";
     private static final String STORAGE_PARENT_WHERE = STORAGE_WHERE + " AND "
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 22a97bd..44fb49a 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -19,6 +19,7 @@
     <uses-permission android:name="android.permission.READ_PROFILE" />
     <uses-permission android:name="android.permission.READ_CONTACTS" />
     <uses-permission android:name="android.permission.CONFIGURE_WIFI_DISPLAY" />
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
 
     <!-- Networking and telephony -->
     <uses-permission android:name="android.permission.BLUETOOTH" />
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
index 0d7540b..2318921 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
@@ -186,7 +186,7 @@
         // TODO: Sets the view to be "awaiting" if not already awaiting
 
         // Change the system setting
-        Settings.System.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON,
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON,
                                 enabled ? 1 : 0);
 
         // Post the intent
diff --git a/services/java/com/android/server/pm/UserManagerService.java b/services/java/com/android/server/pm/UserManagerService.java
index 3391668..fc01f60 100644
--- a/services/java/com/android/server/pm/UserManagerService.java
+++ b/services/java/com/android/server/pm/UserManagerService.java
@@ -238,6 +238,18 @@
     }
 
     /**
+     * Check if we've hit the limit of how many users can be created.
+     */
+    private boolean isUserLimitReached() {
+        synchronized (mInstallLock) {
+            int nUsers = mUsers.size();
+            int userLimit = mContext.getResources().getInteger(
+                    com.android.internal.R.integer.config_multiuserMaximumUsers);
+            return nUsers >= userLimit;
+        }
+    }
+
+    /**
      * Enforces that only the system UID or root's UID or apps that have the
      * {@link android.Manifest.permission.MANAGE_USERS MANAGE_USERS}
      * permission can make certain calls to the UserManager.
@@ -522,6 +534,9 @@
     @Override
     public UserInfo createUser(String name, int flags) {
         checkManageUsersPermission("Only the system can create users");
+
+        if (isUserLimitReached()) return null;
+
         int userId = getNextAvailableId();
         UserInfo userInfo = new UserInfo(userId, name, null, flags);
         File userPath = new File(mBaseUserPath, Integer.toString(userId));
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index 3e20756..9977419 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -28,6 +28,10 @@
 public class ScanResult implements Parcelable {
     /** The network name. */
     public String SSID;
+
+    /** Ascii encoded SSID. This will replace SSID when we deprecate it. @hide */
+    public WifiSsid wifiSsid;
+
     /** The address of the access point. */
     public String BSSID;
     /**
@@ -52,15 +56,11 @@
      */
      public long timestamp;
 
-    /**
-     * We'd like to obtain the following attributes,
-     * but they are not reported via the socket
-     * interface, even though they are known
-     * internally by wpa_supplicant.
-     * {@hide}
-     */
-    public ScanResult(String SSID, String BSSID, String caps, int level, int frequency, long tsf) {
-        this.SSID = SSID;
+    /** {@hide} */
+    public ScanResult(WifiSsid wifiSsid, String BSSID, String caps, int level, int frequency,
+            long tsf) {
+        this.wifiSsid = wifiSsid;
+        this.SSID = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;
         this.BSSID = BSSID;
         this.capabilities = caps;
         this.level = level;
@@ -68,9 +68,11 @@
         this.timestamp = tsf;
     }
 
+
     /** copy constructor {@hide} */
     public ScanResult(ScanResult source) {
         if (source != null) {
+            wifiSsid = source.wifiSsid;
             SSID = source.SSID;
             BSSID = source.BSSID;
             capabilities = source.capabilities;
@@ -86,7 +88,7 @@
         String none = "<none>";
 
         sb.append("SSID: ").
-            append(SSID == null ? none : SSID).
+            append(wifiSsid == null ? WifiSsid.NONE : wifiSsid).
             append(", BSSID: ").
             append(BSSID == null ? none : BSSID).
             append(", capabilities: ").
@@ -108,7 +110,12 @@
 
     /** Implement the Parcelable interface {@hide} */
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeString(SSID);
+        if (wifiSsid != null) {
+            dest.writeInt(1);
+            wifiSsid.writeToParcel(dest, flags);
+        } else {
+            dest.writeInt(0);
+        }
         dest.writeString(BSSID);
         dest.writeString(capabilities);
         dest.writeInt(level);
@@ -120,8 +127,12 @@
     public static final Creator<ScanResult> CREATOR =
         new Creator<ScanResult>() {
             public ScanResult createFromParcel(Parcel in) {
+                WifiSsid wifiSsid = null;
+                if (in.readInt() == 1) {
+                    wifiSsid = WifiSsid.CREATOR.createFromParcel(in);
+                }
                 return new ScanResult(
-                    in.readString(),
+                    wifiSsid,
                     in.readString(),
                     in.readString(),
                     in.readInt(),
diff --git a/wifi/java/android/net/wifi/StateChangeResult.java b/wifi/java/android/net/wifi/StateChangeResult.java
index b15c4a6..c334b91 100644
--- a/wifi/java/android/net/wifi/StateChangeResult.java
+++ b/wifi/java/android/net/wifi/StateChangeResult.java
@@ -23,15 +23,16 @@
  * @hide
  */
 public class StateChangeResult {
-    StateChangeResult(int networkId, String SSID, String BSSID, SupplicantState state) {
+    StateChangeResult(int networkId, WifiSsid wifiSsid, String BSSID,
+            SupplicantState state) {
         this.state = state;
-        this.SSID = SSID;
+        this.wifiSsid= wifiSsid;
         this.BSSID = BSSID;
         this.networkId = networkId;
     }
 
     int networkId;
-    String SSID;
+    WifiSsid wifiSsid;
     String BSSID;
     SupplicantState state;
 }
diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java
index a2332e3..84506b6 100644
--- a/wifi/java/android/net/wifi/WifiConfigStore.java
+++ b/wifi/java/android/net/wifi/WifiConfigStore.java
@@ -1318,7 +1318,13 @@
 
         value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.ssidVarName);
         if (!TextUtils.isEmpty(value)) {
-            config.SSID = value;
+            if (value.charAt(0) != '"') {
+                config.SSID = "\"" + WifiSsid.createFromHex(value).toString() + "\"";
+                //TODO: convert a hex string that is not UTF-8 decodable to a P-formatted
+                //supplicant string
+            } else {
+                config.SSID = value;
+            }
         } else {
             config.SSID = null;
         }
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 0a846fd..c4fe1b4 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -524,6 +524,27 @@
     }
     */
 
+    /** {@hide} */
+    public String getPrintableSsid() {
+        if (SSID == null) return "";
+        final int length = SSID.length();
+        if (length > 2 && (SSID.charAt(0) == '"') && SSID.charAt(length - 1) == '"') {
+            return SSID.substring(1, length - 1);
+        }
+
+        /** The ascii-encoded string format is P"<ascii-encoded-string>"
+         * The decoding is implemented in the supplicant for a newly configured
+         * network.
+         */
+        if (length > 3 && (SSID.charAt(0) == 'P') && (SSID.charAt(1) == '"') &&
+                (SSID.charAt(length-1) == '"')) {
+            WifiSsid wifiSsid = WifiSsid.createFromAsciiEncoded(
+                    SSID.substring(2, length - 1));
+            return wifiSsid.toString();
+        }
+        return SSID;
+    }
+
     private static BitSet readBitSet(Parcel src) {
         int cardinality = src.readInt();
 
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 1f1cfdd..05db571 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -20,6 +20,7 @@
 import android.os.Parcel;
 import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkUtils;
+import android.text.TextUtils;
 
 import java.net.InetAddress;
 import java.net.Inet6Address;
@@ -31,6 +32,7 @@
  * is in the process of being set up.
  */
 public class WifiInfo implements Parcelable {
+    private static final String TAG = "WifiInfo";
     /**
      * This is the map described in the Javadoc comment above. The positions
      * of the elements of the array must correspond to the ordinal values
@@ -57,7 +59,7 @@
 
     private SupplicantState mSupplicantState;
     private String mBSSID;
-    private String mSSID;
+    private WifiSsid mWifiSsid;
     private int mNetworkId;
     private boolean mHiddenSSID;
     /** Received Signal Strength Indicator */
@@ -77,7 +79,7 @@
     private boolean mMeteredHint;
 
     WifiInfo() {
-        mSSID = null;
+        mWifiSsid = null;
         mBSSID = null;
         mNetworkId = -1;
         mSupplicantState = SupplicantState.UNINITIALIZED;
@@ -94,7 +96,7 @@
         if (source != null) {
             mSupplicantState = source.mSupplicantState;
             mBSSID = source.mBSSID;
-            mSSID = source.mSSID;
+            mWifiSsid = source.mWifiSsid;
             mNetworkId = source.mNetworkId;
             mHiddenSSID = source.mHiddenSSID;
             mRssi = source.mRssi;
@@ -105,21 +107,34 @@
         }
     }
 
-    void setSSID(String SSID) {
-        mSSID = SSID;
+    void setSSID(WifiSsid wifiSsid) {
+        mWifiSsid = wifiSsid;
         // network is considered not hidden by default
         mHiddenSSID = false;
     }
 
     /**
      * Returns the service set identifier (SSID) of the current 802.11 network.
-     * If the SSID is an ASCII string, it will be returned surrounded by double
-     * quotation marks.Otherwise, it is returned as a string of hex digits. The
+     * If the SSID can be decoded as UTF-8, it will be returned surrounded by double
+     * quotation marks. Otherwise, it is returned as a string of hex digits. The
      * SSID may be {@code null} if there is no network currently connected.
      * @return the SSID
      */
     public String getSSID() {
-        return mSSID;
+        if (mWifiSsid != null) {
+            String unicode = mWifiSsid.toString();
+            if (!TextUtils.isEmpty(unicode)) {
+                return "\"" + unicode + "\"";
+            } else {
+                return mWifiSsid.getHexString();
+            }
+        }
+        return WifiSsid.NONE;
+    }
+
+    /** @hide */
+    public WifiSsid getWifiSsid() {
+        return mWifiSsid;
     }
 
     void setBSSID(String BSSID) {
@@ -279,7 +294,7 @@
         StringBuffer sb = new StringBuffer();
         String none = "<none>";
 
-        sb.append("SSID: ").append(mSSID == null ? none : mSSID).
+        sb.append("SSID: ").append(mWifiSsid == null ? WifiSsid.NONE : mWifiSsid).
             append(", BSSID: ").append(mBSSID == null ? none : mBSSID).
             append(", MAC: ").append(mMacAddress == null ? none : mMacAddress).
             append(", Supplicant state: ").
@@ -308,7 +323,12 @@
         } else {
             dest.writeByte((byte)0);
         }
-        dest.writeString(getSSID());
+        if (mWifiSsid != null) {
+            dest.writeInt(1);
+            mWifiSsid.writeToParcel(dest, flags);
+        } else {
+            dest.writeInt(0);
+        }
         dest.writeString(mBSSID);
         dest.writeString(mMacAddress);
         dest.writeInt(mMeteredHint ? 1 : 0);
@@ -328,7 +348,9 @@
                         info.setInetAddress(InetAddress.getByAddress(in.createByteArray()));
                     } catch (UnknownHostException e) {}
                 }
-                info.setSSID(in.readString());
+                if (in.readInt() == 1) {
+                    info.mWifiSsid = WifiSsid.CREATOR.createFromParcel(in);
+                }
                 info.mBSSID = in.readString();
                 info.mMacAddress = in.readString();
                 info.mMeteredHint = in.readInt() != 0;
diff --git a/wifi/java/android/net/wifi/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java
index 17c930b..ab54a15 100644
--- a/wifi/java/android/net/wifi/WifiMonitor.java
+++ b/wifi/java/android/net/wifi/WifiMonitor.java
@@ -645,9 +645,12 @@
          * id=network-id state=new-state
          */
         private void handleSupplicantStateChange(String dataString) {
-            String SSID = null;
+            WifiSsid wifiSsid = null;
             int index = dataString.lastIndexOf("SSID=");
-            if (index != -1) SSID = dataString.substring(index + 5);
+            if (index != -1) {
+                wifiSsid = WifiSsid.createFromAsciiEncoded(
+                        dataString.substring(index + 5));
+            }
             String[] dataTokens = dataString.split(" ");
 
             String BSSID = null;
@@ -690,7 +693,7 @@
             if (newSupplicantState == SupplicantState.INVALID) {
                 Log.w(TAG, "Invalid supplicant state: " + newState);
             }
-            notifySupplicantStateChange(networkId, SSID, BSSID, newSupplicantState);
+            notifySupplicantStateChange(networkId, wifiSsid, BSSID, newSupplicantState);
         }
     }
 
@@ -739,13 +742,14 @@
      * Send the state machine a notification that the state of the supplicant
      * has changed.
      * @param networkId the configured network on which the state change occurred
-     * @param SSID network name
+     * @param wifiSsid network name
      * @param BSSID network address
      * @param newState the new {@code SupplicantState}
      */
-    void notifySupplicantStateChange(int networkId, String SSID, String BSSID, SupplicantState newState) {
+    void notifySupplicantStateChange(int networkId, WifiSsid wifiSsid, String BSSID,
+            SupplicantState newState) {
         mStateMachine.sendMessage(mStateMachine.obtainMessage(SUPPLICANT_STATE_CHANGE_EVENT,
-                new StateChangeResult(networkId, SSID, BSSID, newState)));
+                new StateChangeResult(networkId, wifiSsid, BSSID, newState)));
     }
 
     /**
diff --git a/wifi/java/android/net/wifi/WifiSsid.java b/wifi/java/android/net/wifi/WifiSsid.java
new file mode 100644
index 0000000..6f36111
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiSsid.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+
+/**
+ * Stores SSID octets and handles conversion.
+ *
+ * For Ascii encoded string, any octet < 32 or > 127 is encoded as
+ * a "\x" followed by the hex representation of the octet.
+ * Exception chars are ", \, \e, \n, \r, \t which are escaped by a \
+ * See src/utils/common.c for the implementation in the supplicant.
+ *
+ * @hide
+ */
+public class WifiSsid implements Parcelable {
+    private static final String TAG = "WifiSsid";
+
+    public ByteArrayOutputStream octets = new ByteArrayOutputStream(32);
+
+    private static final int HEX_RADIX = 16;
+    public static final String NONE = "<unknown ssid>";
+
+    private WifiSsid() {
+    }
+
+    public static WifiSsid createFromAsciiEncoded(String asciiEncoded) {
+        WifiSsid a = new WifiSsid();
+        a.convertToBytes(asciiEncoded);
+        return a;
+    }
+
+    public static WifiSsid createFromHex(String hexStr) {
+        WifiSsid a = new WifiSsid();
+        int length = 0;
+        if (hexStr == null) return a;
+
+        if (hexStr.startsWith("0x") || hexStr.startsWith("0X")) {
+            hexStr = hexStr.substring(2);
+        }
+
+        for (int i = 0; i < hexStr.length()-1; i += 2) {
+            int val;
+            try {
+                val = Integer.parseInt(hexStr.substring(i, i + 2), HEX_RADIX);
+            } catch(NumberFormatException e) {
+                val = 0;
+            }
+            a.octets.write(val);
+        }
+        return a;
+    }
+
+    /* This function is equivalent to printf_decode() at src/utils/common.c in
+     * the supplicant */
+    private void convertToBytes(String asciiEncoded) {
+        int i = 0;
+        int val = 0;
+        while (i< asciiEncoded.length()) {
+            char c = asciiEncoded.charAt(i);
+            switch (c) {
+                case '\\':
+                    i++;
+                    switch(asciiEncoded.charAt(i)) {
+                        case '\\':
+                            octets.write('\\');
+                            break;
+                        case '"':
+                            octets.write('"');
+                            break;
+                        case 'n':
+                            octets.write('\n');
+                            break;
+                        case 'r':
+                            octets.write('\r');
+                            break;
+                        case 't':
+                            octets.write('\t');
+                            break;
+                        case 'e':
+                            octets.write(27); //escape char
+                            break;
+                        case 'x':
+                            i++;
+                            try {
+                                val = Integer.parseInt(asciiEncoded.substring(i, i + 2), HEX_RADIX);
+                            } catch (NumberFormatException e) {
+                                val = -1;
+                            }
+                            if (val < 0) {
+                                val = Character.digit(asciiEncoded.charAt(i), HEX_RADIX);
+                                if (val < 0) break;
+                                octets.write(val);
+                                i++;
+                            } else {
+                                octets.write(val);
+                                i += 2;
+                            }
+                            break;
+                        case '0':
+                        case '1':
+                        case '2':
+                        case '3':
+                        case '4':
+                        case '5':
+                        case '6':
+                        case '7':
+                            val = asciiEncoded.charAt(i) - '0';
+                            i++;
+                            if (asciiEncoded.charAt(i) >= '0' && asciiEncoded.charAt(i) <= '7') {
+                                val = val * 8 + asciiEncoded.charAt(i) - '0';
+                                i++;
+                            }
+                            if (asciiEncoded.charAt(i) >= '0' && asciiEncoded.charAt(i) <= '7') {
+                                val = val * 8 + asciiEncoded.charAt(i) - '0';
+                                i++;
+                            }
+                            octets.write(val);
+                            break;
+                        default:
+                            break;
+                    }
+                    break;
+                default:
+                    octets.write(c);
+                    i++;
+                    break;
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        if (octets.size() <= 0) return "";
+        // TODO: Handle conversion to other charsets upon failure
+        Charset charset = Charset.forName("UTF-8");
+        CharsetDecoder decoder = charset.newDecoder()
+                .onMalformedInput(CodingErrorAction.REPLACE)
+                .onUnmappableCharacter(CodingErrorAction.REPLACE);
+        CharBuffer out = CharBuffer.allocate(32);
+
+        CoderResult result = decoder.decode(ByteBuffer.wrap(octets.toByteArray()), out, true);
+        out.flip();
+        if (result.isError()) {
+            return NONE;
+        }
+        return out.toString();
+    }
+
+    /** @hide */
+    public byte[] getOctets() {
+        return  octets.toByteArray();
+    }
+
+    /** @hide */
+    public String getHexString() {
+        String out = "0x";
+        byte[] ssidbytes = getOctets();
+        for (int i = 0; i < octets.size(); i++) {
+            out += String.format("%02x", ssidbytes[i]);
+        }
+        return out;
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(octets.size());
+        dest.writeByteArray(octets.toByteArray());
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public static final Creator<WifiSsid> CREATOR =
+        new Creator<WifiSsid>() {
+            public WifiSsid createFromParcel(Parcel in) {
+                WifiSsid ssid = new WifiSsid();
+                int length = in.readInt();
+                byte b[] = new byte[length];
+                in.readByteArray(b);
+                ssid.octets.write(b, 0, length);
+                return ssid;
+            }
+
+            public WifiSsid[] newArray(int size) {
+                return new WifiSsid[size];
+            }
+        };
+}
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 40111fa..2e50b08 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -1403,7 +1403,7 @@
         int freq = 0;
         long tsf = 0;
         String flags = "";
-        String ssid = "";
+        WifiSsid wifiSsid = null;
 
         if (scanResults == null) {
             return;
@@ -1441,22 +1441,26 @@
                 } else if (line.startsWith(FLAGS_STR)) {
                     flags = line.substring(FLAGS_STR.length());
                 } else if (line.startsWith(SSID_STR)) {
-                    ssid = line.substring(SSID_STR.length());
-                    if (ssid == null) ssid = "";
+                    wifiSsid = WifiSsid.createFromAsciiEncoded(
+                            line.substring(SSID_STR.length()));
                 } else if (line.startsWith(DELIMITER_STR)) {
                     if (bssid != null) {
+                        String ssid = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;
                         String key = bssid + ssid;
                         ScanResult scanResult = mScanResultCache.get(key);
                         if (scanResult != null) {
                             scanResult.level = level;
-                            scanResult.SSID = ssid;
+                            scanResult.wifiSsid = wifiSsid;
+                            // Keep existing API
+                            scanResult.SSID = (wifiSsid != null) ? wifiSsid.toString() :
+                                    WifiSsid.NONE;
                             scanResult.capabilities = flags;
                             scanResult.frequency = freq;
                             scanResult.timestamp = tsf;
                         } else {
                             scanResult =
                                 new ScanResult(
-                                        ssid, bssid, flags, level, freq, tsf);
+                                        wifiSsid, bssid, flags, level, freq, tsf);
                             mScanResultCache.put(key, scanResult);
                         }
                         mScanResults.add(scanResult);
@@ -1466,7 +1470,7 @@
                     freq = 0;
                     tsf = 0;
                     flags = "";
-                    ssid = "";
+                    wifiSsid = null;
                 }
             }
         }
@@ -1652,7 +1656,7 @@
         }
 
         mWifiInfo.setBSSID(stateChangeResult.BSSID);
-        mWifiInfo.setSSID(stateChangeResult.SSID);
+        mWifiInfo.setSSID(stateChangeResult.wifiSsid);
 
         mSupplicantStateTracker.sendMessage(Message.obtain(message));