Merge "Clarify the API documentation for WebSQL database." into jb-mr2-dev
diff --git a/Android.mk b/Android.mk
index b9cd7bf..5dc3523 100644
--- a/Android.mk
+++ b/Android.mk
@@ -102,6 +102,9 @@
 	core/java/android/bluetooth/IBluetoothManagerCallback.aidl \
 	core/java/android/bluetooth/IBluetoothPbap.aidl \
 	core/java/android/bluetooth/IBluetoothStateChangeCallback.aidl \
+	core/java/android/bluetooth/IBluetoothGatt.aidl \
+	core/java/android/bluetooth/IBluetoothGattCallback.aidl \
+	core/java/android/bluetooth/IBluetoothGattServerCallback.aidl \
 	core/java/android/content/IClipboard.aidl \
 	core/java/android/content/IContentService.aidl \
 	core/java/android/content/IIntentReceiver.aidl \
diff --git a/api/current.txt b/api/current.txt
index a89c30a..a4b4992 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5917,6 +5917,7 @@
     field public static final java.lang.String ACTION_USER_INITIALIZE = "android.intent.action.USER_INITIALIZE";
     field public static final java.lang.String ACTION_USER_PRESENT = "android.intent.action.USER_PRESENT";
     field public static final java.lang.String ACTION_VIEW = "android.intent.action.VIEW";
+    field public static final java.lang.String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
     field public static final java.lang.String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
     field public static final deprecated java.lang.String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
     field public static final java.lang.String ACTION_WEB_SEARCH = "android.intent.action.WEB_SEARCH";
@@ -27456,7 +27457,7 @@
     method public java.lang.String getUrl();
   }
 
-  public class WebIconDatabase {
+  public deprecated class WebIconDatabase {
     method public void close();
     method public static android.webkit.WebIconDatabase getInstance();
     method public void open(java.lang.String);
@@ -27466,7 +27467,7 @@
     method public void retainIconForPageUrl(java.lang.String);
   }
 
-  public static abstract interface WebIconDatabase.IconListener {
+  public static abstract deprecated interface WebIconDatabase.IconListener {
     method public abstract void onReceivedIcon(java.lang.String, android.graphics.Bitmap);
   }
 
@@ -27505,18 +27506,18 @@
     method public synchronized boolean getJavaScriptCanOpenWindowsAutomatically();
     method public synchronized boolean getJavaScriptEnabled();
     method public synchronized android.webkit.WebSettings.LayoutAlgorithm getLayoutAlgorithm();
-    method public boolean getLightTouchEnabled();
+    method public deprecated boolean getLightTouchEnabled();
     method public boolean getLoadWithOverviewMode();
     method public synchronized boolean getLoadsImagesAutomatically();
     method public boolean getMediaPlaybackRequiresUserGesture();
     method public synchronized int getMinimumFontSize();
     method public synchronized int getMinimumLogicalFontSize();
-    method public synchronized android.webkit.WebSettings.PluginState getPluginState();
+    method public deprecated synchronized android.webkit.WebSettings.PluginState getPluginState();
     method public deprecated synchronized boolean getPluginsEnabled();
     method public deprecated synchronized java.lang.String getPluginsPath();
     method public synchronized java.lang.String getSansSerifFontFamily();
     method public boolean getSaveFormData();
-    method public boolean getSavePassword();
+    method public deprecated boolean getSavePassword();
     method public synchronized java.lang.String getSerifFontFamily();
     method public synchronized java.lang.String getStandardFontFamily();
     method public deprecated synchronized android.webkit.WebSettings.TextSize getTextSize();
@@ -27528,7 +27529,7 @@
     method public abstract void setAllowFileAccessFromFileURLs(boolean);
     method public abstract void setAllowUniversalAccessFromFileURLs(boolean);
     method public synchronized void setAppCacheEnabled(boolean);
-    method public synchronized void setAppCacheMaxSize(long);
+    method public deprecated synchronized void setAppCacheMaxSize(long);
     method public synchronized void setAppCachePath(java.lang.String);
     method public synchronized void setBlockNetworkImage(boolean);
     method public synchronized void setBlockNetworkLoads(boolean);
@@ -27551,20 +27552,20 @@
     method public synchronized void setJavaScriptCanOpenWindowsAutomatically(boolean);
     method public synchronized void setJavaScriptEnabled(boolean);
     method public synchronized void setLayoutAlgorithm(android.webkit.WebSettings.LayoutAlgorithm);
-    method public void setLightTouchEnabled(boolean);
+    method public deprecated void setLightTouchEnabled(boolean);
     method public void setLoadWithOverviewMode(boolean);
     method public synchronized void setLoadsImagesAutomatically(boolean);
     method public void setMediaPlaybackRequiresUserGesture(boolean);
     method public synchronized void setMinimumFontSize(int);
     method public synchronized void setMinimumLogicalFontSize(int);
     method public void setNeedInitialFocus(boolean);
-    method public synchronized void setPluginState(android.webkit.WebSettings.PluginState);
+    method public deprecated synchronized void setPluginState(android.webkit.WebSettings.PluginState);
     method public deprecated synchronized void setPluginsEnabled(boolean);
     method public deprecated synchronized void setPluginsPath(java.lang.String);
-    method public synchronized void setRenderPriority(android.webkit.WebSettings.RenderPriority);
+    method public deprecated synchronized void setRenderPriority(android.webkit.WebSettings.RenderPriority);
     method public synchronized void setSansSerifFontFamily(java.lang.String);
     method public void setSaveFormData(boolean);
-    method public void setSavePassword(boolean);
+    method public deprecated void setSavePassword(boolean);
     method public synchronized void setSerifFontFamily(java.lang.String);
     method public synchronized void setStandardFontFamily(java.lang.String);
     method public synchronized void setSupportMultipleWindows(boolean);
@@ -27631,7 +27632,7 @@
     method public void getOrigins(android.webkit.ValueCallback<java.util.Map>);
     method public void getQuotaForOrigin(java.lang.String, android.webkit.ValueCallback<java.lang.Long>);
     method public void getUsageForOrigin(java.lang.String, android.webkit.ValueCallback<java.lang.Long>);
-    method public void setQuotaForOrigin(java.lang.String, long);
+    method public deprecated void setQuotaForOrigin(java.lang.String, long);
   }
 
   public static class WebStorage.Origin {
@@ -27674,7 +27675,7 @@
     method public void clearHistory();
     method public void clearMatches();
     method public void clearSslPreferences();
-    method public void clearView();
+    method public deprecated void clearView();
     method public android.webkit.WebBackForwardList copyBackForwardList();
     method public void destroy();
     method public void documentHasImages(android.os.Message);
@@ -27721,7 +27722,7 @@
     method public void requestImageRef(android.os.Message);
     method public android.webkit.WebBackForwardList restoreState(android.os.Bundle);
     method public void resumeTimers();
-    method public void savePassword(java.lang.String, java.lang.String, java.lang.String);
+    method public deprecated void savePassword(java.lang.String, java.lang.String, java.lang.String);
     method public android.webkit.WebBackForwardList saveState(android.os.Bundle);
     method public void saveWebArchive(java.lang.String);
     method public void saveWebArchive(java.lang.String, boolean, android.webkit.ValueCallback<java.lang.String>);
@@ -27737,7 +27738,7 @@
     method public void setVerticalScrollbarOverlay(boolean);
     method public void setWebChromeClient(android.webkit.WebChromeClient);
     method public void setWebViewClient(android.webkit.WebViewClient);
-    method public boolean showFindDialog(java.lang.String, boolean);
+    method public deprecated boolean showFindDialog(java.lang.String, boolean);
     method public void stopLoading();
     method public boolean zoomIn();
     method public boolean zoomOut();
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
old mode 100755
new mode 100644
index 6367e16..b00bf09
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1136,8 +1136,8 @@
     /**
      * Get the profile proxy object associated with the profile.
      *
-     * <p>Profile can be one of {@link BluetoothProfile#HEALTH}, {@link BluetoothProfile#HEADSET} or
-     * {@link BluetoothProfile#A2DP}. Clients must implements
+     * <p>Profile can be one of {@link BluetoothProfile#HEALTH}, {@link BluetoothProfile#HEADSET},
+     * or {@link BluetoothProfile#A2DP}. Clients must implement
      * {@link BluetoothProfile.ServiceListener} to get notified of
      * the connection status and to get the proxy object.
      *
@@ -1166,6 +1166,12 @@
         } else if (profile == BluetoothProfile.HEALTH) {
             BluetoothHealth health = new BluetoothHealth(context, listener);
             return true;
+        } else if (profile == BluetoothProfile.GATT) {
+            BluetoothGatt gatt = new BluetoothGatt(context, listener);
+            return true;
+        } else if (profile == BluetoothProfile.GATT_SERVER) {
+            BluetoothGattServer gattServer = new BluetoothGattServer(context, listener);
+            return true;
         } else {
             return false;
         }
@@ -1206,6 +1212,14 @@
                 BluetoothHealth health = (BluetoothHealth)proxy;
                 health.close();
                 break;
+           case BluetoothProfile.GATT:
+                BluetoothGatt gatt = (BluetoothGatt)proxy;
+                gatt.close();
+                break;
+            case BluetoothProfile.GATT_SERVER:
+                BluetoothGattServer gattServer = (BluetoothGattServer)proxy;
+                gattServer.close();
+                break;
         }
     }
 
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
new file mode 100644
index 0000000..1e12025
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -0,0 +1,1309 @@
+/*
+ * Copyright (C) 2013 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.bluetooth;
+
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProfile.ServiceListener;
+import android.bluetooth.IBluetoothManager;
+import android.bluetooth.IBluetoothStateChangeCallback;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Public API for the Bluetooth Gatt Profile.
+ *
+ * <p>This class provides Bluetooth Gatt functionality to enable communication
+ * with Bluetooth Smart or Smart Ready devices.
+ *
+ * <p>BluetoothGatt is a proxy object for controlling the Bluetooth Service
+ * via IPC.  Use {@link BluetoothAdapter#getProfileProxy} to get the
+ * BluetoothGatt proxy object.
+ *
+ * <p>To connect to a remote peripheral device, create a {@link BluetoothGattCallback}
+ * and call {@link #registerApp} to register your application. Gatt capable
+ * devices can be discovered using the {@link #startScan} function or the
+ * regular Bluetooth device discovery process.
+ * @hide
+ */
+public final class BluetoothGatt implements BluetoothProfile {
+    private static final String TAG = "BluetoothGatt";
+    private static final boolean DBG = true;
+
+    private Context mContext;
+    private ServiceListener mServiceListener;
+    private BluetoothAdapter mAdapter;
+    private IBluetoothGatt mService;
+    private BluetoothGattCallback mCallback;
+    private int mClientIf;
+    private boolean mAuthRetry = false;
+
+    private List<BluetoothGattService> mServices;
+
+    /** A Gatt operation completed successfully */
+    public static final int GATT_SUCCESS = 0;
+
+    /** Gatt read operation is not permitted */
+    public static final int GATT_READ_NOT_PERMITTED = 0x2;
+
+    /** Gatt write operation is not permitted */
+    public static final int GATT_WRITE_NOT_PERMITTED = 0x3;
+
+    /** Insufficient authentication for a given operation */
+    public static final int GATT_INSUFFICIENT_AUTHENTICATION = 0x5;
+
+    /** The given request is not supported */
+    public static final int GATT_REQUEST_NOT_SUPPORTED = 0x6;
+
+    /** Insufficient encryption for a given operation */
+    public static final int GATT_INSUFFICIENT_ENCRYPTION = 0xf;
+
+    /** A read or write operation was requested with an invalid offset */
+    public static final int GATT_INVALID_OFFSET = 0x7;
+
+    /** A write operation exceeds the maximum length of the attribute */
+    public static final int GATT_INVALID_ATTRIBUTE_LENGTH = 0xd;
+
+    /**
+     * No authentication required.
+     * @hide
+     */
+    /*package*/ static final int AUTHENTICATION_NONE = 0;
+
+    /**
+     * Authentication requested; no man-in-the-middle protection required.
+     * @hide
+     */
+    /*package*/ static final int AUTHENTICATION_NO_MITM = 1;
+
+    /**
+     * Authentication with man-in-the-middle protection requested.
+     * @hide
+     */
+    /*package*/ static final int AUTHENTICATION_MITM = 2;
+
+    /**
+     * Bluetooth state change handlers
+     */
+    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+        new IBluetoothStateChangeCallback.Stub() {
+            public void onBluetoothStateChange(boolean up) {
+                if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+                if (!up) {
+                    if (DBG) Log.d(TAG,"Unbinding service...");
+                    synchronized (mConnection) {
+                        mService = null;
+                        mContext.unbindService(mConnection);
+                    }
+                } else {
+                    synchronized (mConnection) {
+                        if (mService == null) {
+                            if (DBG) Log.d(TAG,"Binding service...");
+                            if (!mContext.bindService(new Intent(IBluetoothGatt.class.getName()),
+                                                      mConnection, 0)) {
+                                Log.e(TAG, "Could not bind to Bluetooth GATT Service");
+                            }
+                        }
+                    }
+                }
+            }
+        };
+
+    /**
+     * Service binder handling
+     */
+    private ServiceConnection mConnection = new ServiceConnection() {
+            public void onServiceConnected(ComponentName className, IBinder service) {
+                if (DBG) Log.d(TAG, "Proxy object connected");
+                mService = IBluetoothGatt.Stub.asInterface(service);
+                ServiceListener serviceListener = mServiceListener;
+                if (serviceListener != null) {
+                    serviceListener.onServiceConnected(BluetoothProfile.GATT, BluetoothGatt.this);
+                }
+            }
+            public void onServiceDisconnected(ComponentName className) {
+                if (DBG) Log.d(TAG, "Proxy object disconnected");
+                mService = null;
+                ServiceListener serviceListener = mServiceListener;
+                if (serviceListener != null) {
+                    serviceListener.onServiceDisconnected(BluetoothProfile.GATT);
+                }
+            }
+        };
+
+    /**
+     * Bluetooth GATT interface callbacks
+     */
+    private final IBluetoothGattCallback mBluetoothGattCallback =
+        new IBluetoothGattCallback.Stub() {
+            /**
+             * Application interface registered - app is ready to go
+             * @hide
+             */
+            public void onClientRegistered(int status, int clientIf) {
+                if (DBG) Log.d(TAG, "onClientRegistered() - status=" + status
+                    + " clientIf=" + clientIf);
+                mClientIf = clientIf;
+                try {
+                    mCallback.onAppRegistered(status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Client connection state changed
+             * @hide
+             */
+            public void onClientConnectionState(int status, int clientIf,
+                                                boolean connected, String address) {
+                if (DBG) Log.d(TAG, "onClientConnectionState() - status=" + status
+                                 + " clientIf=" + clientIf + " device=" + address);
+                try {
+                    mCallback.onConnectionStateChange(mAdapter.getRemoteDevice(address), status,
+                                                      connected ? BluetoothProfile.STATE_CONNECTED
+                                                      : BluetoothProfile.STATE_DISCONNECTED);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Callback reporting an LE scan result.
+             * @hide
+             */
+            public void onScanResult(String address, int rssi, byte[] advData) {
+                if (DBG) Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" +rssi);
+
+                try {
+                    mCallback.onScanResult(mAdapter.getRemoteDevice(address), rssi, advData);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * A new GATT service has been discovered.
+             * The service is added to the internal list and the search
+             * continues.
+             * @hide
+             */
+            public void onGetService(String address, int srvcType,
+                                     int srvcInstId, ParcelUuid srvcUuid) {
+                if (DBG) Log.d(TAG, "onGetService() - Device=" + address + " UUID=" + srvcUuid);
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                mServices.add(new BluetoothGattService(device, srvcUuid.getUuid(),
+                                                       srvcInstId, srvcType));
+            }
+
+            /**
+             * An included service has been found durig GATT discovery.
+             * The included service is added to the respective parent.
+             * @hide
+             */
+            public void onGetIncludedService(String address, int srvcType,
+                                             int srvcInstId, ParcelUuid srvcUuid,
+                                             int inclSrvcType, int inclSrvcInstId,
+                                             ParcelUuid inclSrvcUuid) {
+                if (DBG) Log.d(TAG, "onGetIncludedService() - Device=" + address
+                    + " UUID=" + srvcUuid + " Included=" + inclSrvcUuid);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                BluetoothGattService service = getService(device,
+                        srvcUuid.getUuid(), srvcInstId, srvcType);
+                BluetoothGattService includedService = getService(device,
+                        inclSrvcUuid.getUuid(), inclSrvcInstId, inclSrvcType);
+
+                if (service != null && includedService != null) {
+                    service.addIncludedService(includedService);
+                }
+            }
+
+            /**
+             * A new GATT characteristic has been discovered.
+             * Add the new characteristic to the relevant service and continue
+             * the remote device inspection.
+             * @hide
+             */
+            public void onGetCharacteristic(String address, int srvcType,
+                             int srvcInstId, ParcelUuid srvcUuid,
+                             int charInstId, ParcelUuid charUuid,
+                             int charProps) {
+                if (DBG) Log.d(TAG, "onGetCharacteristic() - Device=" + address + " UUID=" +
+                               charUuid);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                BluetoothGattService service = getService(device, srvcUuid.getUuid(),
+                                                          srvcInstId, srvcType);
+                if (service != null) {
+                    service.addCharacteristic(new BluetoothGattCharacteristic(
+                           service, charUuid.getUuid(), charInstId, charProps, 0));
+                }
+            }
+
+            /**
+             * A new GATT descriptor has been discovered.
+             * Finally, add the descriptor to the related characteristic.
+             * This should conclude the remote device update.
+             * @hide
+             */
+            public void onGetDescriptor(String address, int srvcType,
+                             int srvcInstId, ParcelUuid srvcUuid,
+                             int charInstId, ParcelUuid charUuid,
+                             ParcelUuid descUuid) {
+                if (DBG) Log.d(TAG, "onGetDescriptor() - Device=" + address + " UUID=" + descUuid);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                BluetoothGattService service = getService(device, srvcUuid.getUuid(),
+                                                          srvcInstId, srvcType);
+                if (service == null) return;
+
+                BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+                    charUuid.getUuid());
+                if (characteristic == null) return;
+
+                characteristic.addDescriptor(new BluetoothGattDescriptor(
+                    characteristic, descUuid.getUuid(), 0));
+            }
+
+            /**
+             * Remote search has been completed.
+             * The internal object structure should now reflect the state
+             * of the remote device database. Let the application know that
+             * we are done at this point.
+             * @hide
+             */
+            public void onSearchComplete(String address, int status) {
+                if (DBG) Log.d(TAG, "onSearchComplete() = Device=" + address + " Status=" + status);
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                try {
+                    mCallback.onServicesDiscovered(device, status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Remote characteristic has been read.
+             * Updates the internal value.
+             * @hide
+             */
+            public void onCharacteristicRead(String address, int status, int srvcType,
+                             int srvcInstId, ParcelUuid srvcUuid,
+                             int charInstId, ParcelUuid charUuid, byte[] value) {
+                if (DBG) Log.d(TAG, "onCharacteristicRead() - Device=" + address
+                            + " UUID=" + charUuid + " Status=" + status);
+
+                if ((status == GATT_INSUFFICIENT_AUTHENTICATION
+                  || status == GATT_INSUFFICIENT_ENCRYPTION)
+                  && mAuthRetry == false) {
+                    try {
+                        mAuthRetry = true;
+                        mService.readCharacteristic(mClientIf, address,
+                            srvcType, srvcInstId, srvcUuid,
+                            charInstId, charUuid, AUTHENTICATION_MITM);
+                        return;
+                    } catch (RemoteException e) {
+                        Log.e(TAG,"",e);
+                    }
+                }
+
+                mAuthRetry = false;
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                BluetoothGattService service = getService(device, srvcUuid.getUuid(),
+                                                          srvcInstId, srvcType);
+                if (service == null) return;
+
+                BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+                        charUuid.getUuid(), charInstId);
+                if (characteristic == null) return;
+
+                if (status == 0) characteristic.setValue(value);
+
+                try {
+                    mCallback.onCharacteristicRead(characteristic, status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Characteristic has been written to the remote device.
+             * Let the app know how we did...
+             * @hide
+             */
+            public void onCharacteristicWrite(String address, int status, int srvcType,
+                             int srvcInstId, ParcelUuid srvcUuid,
+                             int charInstId, ParcelUuid charUuid) {
+                if (DBG) Log.d(TAG, "onCharacteristicWrite() - Device=" + address
+                            + " UUID=" + charUuid + " Status=" + status);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                BluetoothGattService service = getService(device, srvcUuid.getUuid(),
+                                                          srvcInstId, srvcType);
+                if (service == null) return;
+
+                BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+                        charUuid.getUuid(), charInstId);
+                if (characteristic == null) return;
+
+                if ((status == GATT_INSUFFICIENT_AUTHENTICATION
+                  || status == GATT_INSUFFICIENT_ENCRYPTION)
+                  && mAuthRetry == false) {
+                    try {
+                        mAuthRetry = true;
+                        mService.writeCharacteristic(mClientIf, address,
+                            srvcType, srvcInstId, srvcUuid, charInstId, charUuid,
+                            characteristic.getWriteType(), AUTHENTICATION_MITM,
+                            characteristic.getValue());
+                        return;
+                    } catch (RemoteException e) {
+                        Log.e(TAG,"",e);
+                    }
+                }
+
+                mAuthRetry = false;
+
+                try {
+                    mCallback.onCharacteristicWrite(characteristic, status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Remote characteristic has been updated.
+             * Updates the internal value.
+             * @hide
+             */
+            public void onNotify(String address, int srvcType,
+                             int srvcInstId, ParcelUuid srvcUuid,
+                             int charInstId, ParcelUuid charUuid,
+                             byte[] value) {
+                if (DBG) Log.d(TAG, "onNotify() - Device=" + address + " UUID=" + charUuid);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                BluetoothGattService service = getService(device, srvcUuid.getUuid(),
+                                                          srvcInstId, srvcType);
+                if (service == null) return;
+
+                BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+                        charUuid.getUuid(), charInstId);
+                if (characteristic == null) return;
+
+                characteristic.setValue(value);
+
+                try {
+                    mCallback.onCharacteristicChanged(characteristic);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Descriptor has been read.
+             * @hide
+             */
+            public void onDescriptorRead(String address, int status, int srvcType,
+                             int srvcInstId, ParcelUuid srvcUuid,
+                             int charInstId, ParcelUuid charUuid,
+                             ParcelUuid descrUuid, byte[] value) {
+                if (DBG) Log.d(TAG, "onDescriptorRead() - Device=" + address + " UUID=" + charUuid);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                BluetoothGattService service = getService(device, srvcUuid.getUuid(),
+                                                          srvcInstId, srvcType);
+                if (service == null) return;
+
+                BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+                        charUuid.getUuid(), charInstId);
+                if (characteristic == null) return;
+
+                BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
+                        descrUuid.getUuid());
+                if (descriptor == null) return;
+
+                if (status == 0) descriptor.setValue(value);
+
+                if ((status == GATT_INSUFFICIENT_AUTHENTICATION
+                  || status == GATT_INSUFFICIENT_ENCRYPTION)
+                  && mAuthRetry == false) {
+                    try {
+                        mAuthRetry = true;
+                        mService.readDescriptor(mClientIf, address,
+                            srvcType, srvcInstId, srvcUuid, charInstId, charUuid,
+                            descrUuid, AUTHENTICATION_MITM);
+                    } catch (RemoteException e) {
+                        Log.e(TAG,"",e);
+                    }
+                }
+
+                mAuthRetry = true;
+
+                try {
+                    mCallback.onDescriptorRead(descriptor, status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Descriptor write operation complete.
+             * @hide
+             */
+            public void onDescriptorWrite(String address, int status, int srvcType,
+                             int srvcInstId, ParcelUuid srvcUuid,
+                             int charInstId, ParcelUuid charUuid,
+                             ParcelUuid descrUuid) {
+                if (DBG) Log.d(TAG, "onDescriptorWrite() - Device=" + address + " UUID=" + charUuid);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                BluetoothGattService service = getService(device, srvcUuid.getUuid(),
+                                                          srvcInstId, srvcType);
+                if (service == null) return;
+
+                BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+                        charUuid.getUuid(), charInstId);
+                if (characteristic == null) return;
+
+                BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
+                        descrUuid.getUuid());
+                if (descriptor == null) return;
+
+                if ((status == GATT_INSUFFICIENT_AUTHENTICATION
+                  || status == GATT_INSUFFICIENT_ENCRYPTION)
+                  && mAuthRetry == false) {
+                    try {
+                        mAuthRetry = true;
+                        mService.writeDescriptor(mClientIf, address,
+                            srvcType, srvcInstId, srvcUuid, charInstId, charUuid,
+                            descrUuid, characteristic.getWriteType(),
+                            AUTHENTICATION_MITM, descriptor.getValue());
+                    } catch (RemoteException e) {
+                        Log.e(TAG,"",e);
+                    }
+                }
+
+                mAuthRetry = false;
+
+                try {
+                    mCallback.onDescriptorWrite(descriptor, status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Prepared write transaction completed (or aborted)
+             * @hide
+             */
+            public void onExecuteWrite(String address, int status) {
+                if (DBG) Log.d(TAG, "onExecuteWrite() - Device=" + address
+                    + " status=" + status);
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                try {
+                    mCallback.onReliableWriteCompleted(device, status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Remote device RSSI has been read
+             * @hide
+             */
+            public void onReadRemoteRssi(String address, int rssi, int status) {
+                if (DBG) Log.d(TAG, "onReadRemoteRssi() - Device=" + address +
+                            " rssi=" + rssi + " status=" + status);
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                try {
+                    mCallback.onReadRemoteRssi(device, rssi, status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+        };
+
+    /**
+     * Create a BluetoothGatt proxy object.
+     */
+    /*package*/ BluetoothGatt(Context context, ServiceListener l) {
+        mContext = context;
+        mServiceListener = l;
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mServices = new ArrayList<BluetoothGattService>();
+
+        IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE);
+        if (b != null) {
+            IBluetoothManager mgr = IBluetoothManager.Stub.asInterface(b);
+            try {
+                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException re) {
+                Log.e(TAG, "Unable to register BluetoothStateChangeCallback", re);
+            }
+        } else {
+            Log.e(TAG, "Unable to get BluetoothManager interface.");
+            throw new RuntimeException("BluetoothManager inactive");
+        }
+
+        //Bind to the service only if the Bluetooth is ON
+        if(mAdapter.isEnabled()){
+            if (!context.bindService(new Intent(IBluetoothGatt.class.getName()), mConnection, 0)) {
+                Log.e(TAG, "Could not bind to Bluetooth Gatt Service");
+            }
+        }
+    }
+
+    /**
+     * Close the connection to the gatt service.
+     */
+    /*package*/ void close() {
+        if (DBG) Log.d(TAG, "close()");
+
+        unregisterApp();
+        mServiceListener = null;
+
+        IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE);
+        if (b != null) {
+            IBluetoothManager mgr = IBluetoothManager.Stub.asInterface(b);
+            try {
+                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException re) {
+                Log.e(TAG, "Unable to unregister BluetoothStateChangeCallback", re);
+            }
+        }
+
+        synchronized (mConnection) {
+            if (mService != null) {
+                mService = null;
+                mContext.unbindService(mConnection);
+            }
+        }
+    }
+
+    /**
+     * Returns a service by UUID, instance and type.
+     * @hide
+     */
+    /*package*/ BluetoothGattService getService(BluetoothDevice device, UUID uuid,
+                                                int instanceId, int type) {
+        for(BluetoothGattService svc : mServices) {
+            if (svc.getDevice().equals(device) &&
+                svc.getType() == type &&
+                svc.getInstanceId() == instanceId &&
+                svc.getUuid().equals(uuid)) {
+                return svc;
+            }
+        }
+        return null;
+    }
+
+
+    /**
+     * Register an application callback to start using Gatt.
+     *
+     * <p>This is an asynchronous call. The callback is used to notify
+     * success or failure if the function returns true.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param callback Gatt callback handler that will receive asynchronous
+     *          callbacks.
+     * @return true, if application was successfully registered.
+     */
+    public boolean registerApp(BluetoothGattCallback callback) {
+        if (DBG) Log.d(TAG, "registerApp()");
+        if (mService == null) return false;
+
+        mCallback = callback;
+        UUID uuid = UUID.randomUUID();
+        if (DBG) Log.d(TAG, "registerApp() - UUID=" + uuid);
+
+        try {
+            mService.registerClient(new ParcelUuid(uuid), mBluetoothGattCallback);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Unregister the current application and callbacks.
+     */
+    public void unregisterApp() {
+        if (DBG) Log.d(TAG, "unregisterApp() - mClientIf=" + mClientIf);
+        if (mService == null || mClientIf == 0) return;
+
+        try {
+            mCallback = null;
+            mService.unregisterClient(mClientIf);
+            mClientIf = 0;
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+    }
+
+    /**
+     * Starts a scan for Bluetooth LE devices.
+     *
+     * <p>Results of the scan are reported using the
+     * {@link BluetoothGattCallback#onScanResult} callback.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return true, if the scan was started successfully
+     */
+    public boolean startScan() {
+        if (DBG) Log.d(TAG, "startScan()");
+        if (mService == null || mClientIf == 0) return false;
+
+        try {
+            mService.startScan(mClientIf, false);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Starts a scan for Bluetooth LE devices, looking for devices that
+     * advertise given services.
+     *
+     * <p>Devices which advertise all specified services are reported using the
+     * {@link BluetoothGattCallback#onScanResult} callback.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param serviceUuids Array of services to look for
+     * @return true, if the scan was started successfully
+     */
+    public boolean startScan(UUID[] serviceUuids) {
+        if (DBG) Log.d(TAG, "startScan() - with UUIDs");
+        if (mService == null || mClientIf == 0) return false;
+
+        try {
+            ParcelUuid[] uuids = new ParcelUuid[serviceUuids.length];
+            for(int i = 0; i != uuids.length; ++i) {
+                uuids[i] = new ParcelUuid(serviceUuids[i]);
+            }
+            mService.startScanWithUuids(mClientIf, false, uuids);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Stops an ongoing Bluetooth LE device scan.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     */
+    public void stopScan() {
+        if (DBG) Log.d(TAG, "stopScan()");
+        if (mService == null || mClientIf == 0) return;
+
+        try {
+            mService.stopScan(mClientIf, false);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+    }
+
+    /**
+     * Initiate a connection to a Bluetooth Gatt capable device.
+     *
+     * <p>The connection may not be established right away, but will be
+     * completed when the remote device is available. A
+     * {@link BluetoothGattCallback#onConnectionStateChange} callback will be
+     * invoked when the connection state changes as a result of this function.
+     *
+     * <p>The autoConnect paramter determines whether to actively connect to
+     * the remote device, or rather passively scan and finalize the connection
+     * when the remote device is in range/available. Generally, the first ever
+     * connection to a device should be direct (autoConnect set to false) and
+     * subsequent connections to known devices should be invoked with the
+     * autoConnect parameter set to false.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote device to connect to
+     * @param autoConnect Whether to directly connect to the remote device (false)
+     *                    or to automatically connect as soon as the remote
+     *                    device becomes available (true).
+     * @return true, if the connection attempt was initiated successfully
+     */
+    public boolean connect(BluetoothDevice device, boolean autoConnect) {
+        if (DBG) Log.d(TAG, "connect() - device: " + device.getAddress() + ", auto: " + autoConnect);
+        if (mService == null || mClientIf == 0) return false;
+
+        try {
+            mService.clientConnect(mClientIf, device.getAddress(),
+                                   autoConnect ? false : true); // autoConnect is inverse of "isDirect"
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Disconnects an established connection, or cancels a connection attempt
+     * currently in progress.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote device
+     */
+    public void cancelConnection(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "cancelOpen() - device: " + device.getAddress());
+        if (mService == null || mClientIf == 0) return;
+
+        try {
+            mService.clientDisconnect(mClientIf, device.getAddress());
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+    }
+
+    /**
+     * Discovers services offered by a remote device as well as their
+     * characteristics and descriptors.
+     *
+     * <p>This is an asynchronous operation. Once service discovery is completed,
+     * the {@link BluetoothGattCallback#onServicesDiscovered} callback is
+     * triggered. If the discovery was successful, the remote services can be
+     * retrieved using the {@link #getServices} function.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote device to explore
+     * @return true, if the remote service discovery has been started
+     */
+    public boolean discoverServices(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "discoverServices() - device: " + device.getAddress());
+        if (mService == null || mClientIf == 0) return false;
+
+        mServices.clear();
+
+        try {
+            mService.discoverServices(mClientIf, device.getAddress());
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns a list of GATT services offered by the remote device.
+     *
+     * <p>This function requires that service discovery has been completed
+     * for the given device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote device
+     * @return List of services on the remote device. Returns an empty list
+     *         if service discovery has not yet been performed.
+     */
+    public List<BluetoothGattService> getServices(BluetoothDevice device) {
+        List<BluetoothGattService> result =
+                new ArrayList<BluetoothGattService>();
+
+        for (BluetoothGattService service : mServices) {
+            if (service.getDevice().equals(device)) {
+                result.add(service);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns a {@link BluetoothGattService}, if the requested UUID is
+     * supported by the remote device.
+     *
+     * <p>This function requires that service discovery has been completed
+     * for the given device.
+     *
+     * <p>If multiple instances of the same service (as identified by UUID)
+     * exist, the first instance of the service is returned.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote device
+     * @param uuid UUID of the requested service
+     * @return BluetoothGattService if supported, or null if the requested
+     *         service is not offered by the remote device.
+     */
+    public BluetoothGattService getService(BluetoothDevice device, UUID uuid) {
+        for (BluetoothGattService service : mServices) {
+            if (service.getDevice().equals(device) &&
+                service.getUuid().equals(uuid)) {
+                return service;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Reads the requested characteristic from the associated remote device.
+     *
+     * <p>This is an asynchronous operation. The result of the read operation
+     * is reported by the {@link BluetoothGattCallback#onCharacteristicRead}
+     * callback.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param characteristic Characteristic to read from the remote device
+     * @return true, if the read operation was initiated successfully
+     */
+    public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {
+        if ((characteristic.getProperties() &
+                BluetoothGattCharacteristic.PROPERTY_READ) == 0) return false;
+
+        if (DBG) Log.d(TAG, "readCharacteristic() - uuid: " + characteristic.getUuid());
+        if (mService == null || mClientIf == 0) return false;
+
+        BluetoothGattService service = characteristic.getService();
+        if (service == null) return false;
+
+        BluetoothDevice device = service.getDevice();
+        if (device == null) return false;
+
+        try {
+            mService.readCharacteristic(mClientIf, device.getAddress(),
+                service.getType(), service.getInstanceId(),
+                new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
+                new ParcelUuid(characteristic.getUuid()), AUTHENTICATION_NONE);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Writes a given characteristic and it's values to the associated remote
+     * device.
+     *
+     * <p>Once the write operation has been completed, the
+     * {@link BluetoothGattCallback#onCharacteristicWrite} callback is invoked,
+     * reporting the result of the operation.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param characteristic Characteristic to write on the remote device
+     * @return true, if the write operation was initiated successfully
+     */
+    public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
+        if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == 0
+            && (characteristic.getProperties() &
+                BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == 0) return false;
+
+        if (DBG) Log.d(TAG, "writeCharacteristic() - uuid: " + characteristic.getUuid());
+        if (mService == null || mClientIf == 0) return false;
+
+        BluetoothGattService service = characteristic.getService();
+        if (service == null) return false;
+
+        BluetoothDevice device = service.getDevice();
+        if (device == null) return false;
+
+        try {
+            mService.writeCharacteristic(mClientIf, device.getAddress(),
+                service.getType(), service.getInstanceId(),
+                new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
+                new ParcelUuid(characteristic.getUuid()),
+                characteristic.getWriteType(), AUTHENTICATION_NONE,
+                characteristic.getValue());
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Reads the value for a given descriptor from the associated remote device.
+     *
+     * <p>Once the read operation has been completed, the
+     * {@link BluetoothGattCallback#onDescriptorRead} callback is
+     * triggered, signaling the result of the operation.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param descriptor Descriptor value to read from the remote device
+     * @return true, if the read operation was initiated successfully
+     */
+    public boolean readDescriptor(BluetoothGattDescriptor descriptor) {
+        if (DBG) Log.d(TAG, "readDescriptor() - uuid: " + descriptor.getUuid());
+        if (mService == null || mClientIf == 0) return false;
+
+        BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
+        if (characteristic == null) return false;
+
+        BluetoothGattService service = characteristic.getService();
+        if (service == null) return false;
+
+        BluetoothDevice device = service.getDevice();
+        if (device == null) return false;
+
+        try {
+            mService.readDescriptor(mClientIf, device.getAddress(),
+                service.getType(), service.getInstanceId(),
+                new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
+                new ParcelUuid(characteristic.getUuid()),
+                new ParcelUuid(descriptor.getUuid()), AUTHENTICATION_NONE);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Write the value of a given descriptor to the associated remote device.
+     *
+     * <p>A {@link BluetoothGattCallback#onDescriptorWrite} callback is
+     * triggered to report the result of the write operation.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param descriptor Descriptor to write to the associated remote device
+     * @return true, if the write operation was initiated successfully
+     */
+    public boolean writeDescriptor(BluetoothGattDescriptor descriptor) {
+        if (DBG) Log.d(TAG, "writeDescriptor() - uuid: " + descriptor.getUuid());
+        if (mService == null || mClientIf == 0) return false;
+
+        BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
+        if (characteristic == null) return false;
+
+        BluetoothGattService service = characteristic.getService();
+        if (service == null) return false;
+
+        BluetoothDevice device = service.getDevice();
+        if (device == null) return false;
+
+        try {
+            mService.writeDescriptor(mClientIf, device.getAddress(),
+                service.getType(), service.getInstanceId(),
+                new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
+                new ParcelUuid(characteristic.getUuid()),
+                new ParcelUuid(descriptor.getUuid()),
+                characteristic.getWriteType(), AUTHENTICATION_NONE,
+                descriptor.getValue());
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Initiates a reliable write transaction for a given remote device.
+     *
+     * <p>Once a reliable write transaction has been initiated, all calls
+     * to {@link #writeCharacteristic} are sent to the remote device for
+     * verification and queued up for atomic execution. The application will
+     * receive an {@link BluetoothGattCallback#onCharacteristicWrite} callback
+     * in response to every {@link #writeCharacteristic} call and is responsible
+     * for verifying if the value has been transmitted accurately.
+     *
+     * <p>After all characteristics have been queued up and verified,
+     * {@link #executeReliableWrite} will execute all writes. If a characteristic
+     * was not written correctly, calling {@link #abortReliableWrite} will
+     * cancel the current transaction without commiting any values on the
+     * remote device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote device
+     * @return true, if the reliable write transaction has been initiated
+     */
+    public boolean beginReliableWrite(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "beginReliableWrite() - device: " + device.getAddress());
+        if (mService == null || mClientIf == 0) return false;
+
+        try {
+            mService.beginReliableWrite(mClientIf, device.getAddress());
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Executes a reliable write transaction for a given remote device.
+     *
+     * <p>This function will commit all queued up characteristic write
+     * operations for a given remote device.
+     *
+     * <p>A {@link BluetoothGattCallback#onReliableWriteCompleted} callback is
+     * invoked to indicate whether the transaction has been executed correctly.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote device
+     * @return true, if the request to execute the transaction has been sent
+     */
+    public boolean executeReliableWrite(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "executeReliableWrite() - device: " + device.getAddress());
+        if (mService == null || mClientIf == 0) return false;
+
+        try {
+            mService.endReliableWrite(mClientIf, device.getAddress(), true);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Cancels a reliable write transaction for a given device.
+     *
+     * <p>Calling this function will discard all queued characteristic write
+     * operations for a given remote device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote device
+     */
+    public void abortReliableWrite(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "abortReliableWrite() - device: " + device.getAddress());
+        if (mService == null || mClientIf == 0) return;
+
+        try {
+            mService.endReliableWrite(mClientIf, device.getAddress(), false);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+    }
+
+    /**
+     * Enable or disable notifications/indications for a given characteristic.
+     *
+     * <p>Once notifications are enabled for a characteristic, a
+     * {@link BluetoothGattCallback#onCharacteristicChanged} callback will be
+     * triggered if the remote device indicates that the given characteristic
+     * has changed.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param characteristic The characteristic for which to enable notifications
+     * @param enable Set to true to enable notifications/indications
+     * @return true, if the requested notification status was set successfully
+     */
+    public boolean setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
+                                              boolean enable) {
+        if (DBG) Log.d(TAG, "setCharacteristicNotification() - uuid: " + characteristic.getUuid()
+                         + " enable: " + enable);
+        if (mService == null || mClientIf == 0) return false;
+
+        BluetoothGattService service = characteristic.getService();
+        if (service == null) return false;
+
+        BluetoothDevice device = service.getDevice();
+        if (device == null) return false;
+
+        try {
+            mService.registerForNotification(mClientIf, device.getAddress(),
+                service.getType(), service.getInstanceId(),
+                new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
+                new ParcelUuid(characteristic.getUuid()),
+                enable);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Clears the internal cache and forces a refresh of the services from the
+     * remote device.
+     * @hide
+     */
+    public boolean refresh(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "refresh() - device: " + device.getAddress());
+        if (mService == null || mClientIf == 0) return false;
+
+        try {
+            mService.refreshDevice(mClientIf, device.getAddress());
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Read the RSSI for a connected remote device.
+     *
+     * <p>The {@link BluetoothGattCallback#onReadRemoteRssi} callback will be
+     * invoked when the RSSI value has been read.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote device
+     * @return true, if the RSSI value has been requested successfully
+     */
+    public boolean readRemoteRssi(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "readRssi() - device: " + device.getAddress());
+        if (mService == null || mClientIf == 0) return false;
+
+        try {
+            mService.readRemoteRssi(mClientIf, device.getAddress());
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Get the current connection state of the profile.
+     *
+     * <p>This is not specific to any application configuration but represents
+     * the connection state of the local Bluetooth adapter for this profile.
+     * This can be used by applications like status bar which would just like
+     * to know the state of the local adapter.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote bluetooth device.
+     * @return State of the profile connection. One of
+     *               {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
+     *               {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}
+     */
+    @Override
+    public int getConnectionState(BluetoothDevice device) {
+        if (DBG) Log.d(TAG,"getConnectionState()");
+        if (mService == null) return STATE_DISCONNECTED;
+
+        List<BluetoothDevice> connectedDevices = getConnectedDevices();
+        for(BluetoothDevice connectedDevice : connectedDevices) {
+            if (device.equals(connectedDevice)) {
+                return STATE_CONNECTED;
+            }
+        }
+
+        return STATE_DISCONNECTED;
+    }
+
+    /**
+     * Get connected devices for the Gatt profile.
+     *
+     * <p> Return the set of devices which are in state {@link #STATE_CONNECTED}
+     *
+     * <p>This is not specific to any application configuration but represents
+     * the connection state of the local Bluetooth adapter for this profile.
+     * This can be used by applications like status bar which would just like
+     * to know the state of the local adapter.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return List of devices. The list will be empty on error.
+     */
+    @Override
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (DBG) Log.d(TAG,"getConnectedDevices");
+
+        List<BluetoothDevice> connectedDevices = new ArrayList<BluetoothDevice>();
+        if (mService == null) return connectedDevices;
+
+        try {
+            connectedDevices = mService.getDevicesMatchingConnectionStates(
+                new int[] { BluetoothProfile.STATE_CONNECTED });
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+
+        return connectedDevices;
+    }
+
+    /**
+     * Get a list of devices that match any of the given connection
+     * states.
+     *
+     * <p> If none of the devices match any of the given states,
+     * an empty list will be returned.
+     *
+     * <p>This is not specific to any application configuration but represents
+     * the connection state of the local Bluetooth adapter for this profile.
+     * This can be used by applications like status bar which would just like
+     * to know the state of the local adapter.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param states Array of states. States can be one of
+     *              {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
+     *              {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING},
+     * @return List of devices. The list will be empty on error.
+     */
+    @Override
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (DBG) Log.d(TAG,"getDevicesMatchingConnectionStates");
+
+        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
+        if (mService == null) return devices;
+
+        try {
+            devices = mService.getDevicesMatchingConnectionStates(states);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+
+        return devices;
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothGattCallback.java b/core/java/android/bluetooth/BluetoothGattCallback.java
new file mode 100644
index 0000000..afa4539
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattCallback.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2013 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.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+
+import android.util.Log;
+
+/**
+ * This abstract class is used to implement {@link BluetoothGatt} callbacks.
+ * @hide
+ */
+public abstract class BluetoothGattCallback {
+    /**
+     * Callback to inform change in registration state of the  application.
+     *
+     * @param status Returns {@link BluetoothGatt#GATT_SUCCESS} if the application
+     *               was successfully registered.
+     */
+    public void onAppRegistered(int status) {
+    }
+
+    /**
+     * Callback reporting an LE device found during a device scan initiated
+     * by the {@link BluetoothGatt#startScan} function.
+     *
+     * @param device Identifies the remote device
+     * @param rssi The RSSI value for the remote device as reported by the
+     *             Bluetooth hardware. 0 if no RSSI value is available.
+     * @param scanRecord The content of the advertisement record offered by
+     *                   the remote device.
+     */
+    public void onScanResult(BluetoothDevice device, int rssi, byte[] scanRecord) {
+    }
+
+    /**
+     * Callback indicating when a remote device has been connected or disconnected.
+     *
+     * @param device Remote device that has been connected or disconnected.
+     * @param status Status of the connect or disconnect operation.
+     * @param newState Returns the new connection state. Can be one of
+     *                  {@link BluetoothProfile#STATE_DISCONNECTED} or
+     *                  {@link BluetoothProfile#STATE_CONNECTED}
+     */
+    public void onConnectionStateChange(BluetoothDevice device, int status,
+                                        int newState) {
+    }
+
+    /**
+     * Callback invoked when the list of remote services, characteristics and
+     * descriptors for the remote device have been updated.
+     *
+     * @param device Remote device
+     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the remote device
+     *               has been explored successfully.
+     */
+    public void onServicesDiscovered(BluetoothDevice device, int status) {
+    }
+
+    /**
+     * Callback reporting the result of a characteristic read operation.
+     *
+     * @param characteristic Characteristic that was read from the associated
+     *                       remote device.
+     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation
+     *               was completed successfully.
+     */
+    public void onCharacteristicRead(BluetoothGattCharacteristic characteristic,
+                                     int status) {
+    }
+
+    /**
+     * Callback indicating the result of a characteristic write operation.
+     *
+     * <p>If this callback is invoked while a reliable write transaction is
+     * in progress, the value of the characteristic represents the value
+     * reported by the remote device. An application should compare this
+     * value to the desired value to be written. If the values don't match,
+     * the application must abort the reliable write transaction.
+     *
+     * @param characteristic Characteristic that was written to the associated
+     *                       remote device.
+     * @param status The result of the write operation
+     */
+    public void onCharacteristicWrite(BluetoothGattCharacteristic characteristic,
+                               int status) {
+    }
+
+    /**
+     * Callback triggered as a result of a remote characteristic notification.
+     *
+     * @param characteristic Characteristic that has been updated as a result
+     *                       of a remote notification event.
+     */
+    public void onCharacteristicChanged(BluetoothGattCharacteristic characteristic) {
+    }
+
+    /**
+     * Callback reporting the result of a descriptor read operation.
+     *
+     * @param descriptor Descriptor that was read from the associated
+     *                       remote device.
+     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation
+     *               was completed successfully
+     */
+    public void onDescriptorRead(BluetoothGattDescriptor descriptor,
+                                     int status) {
+    }
+
+    /**
+     * Callback indicating the result of a descriptor write operation.
+     *
+     * @param descriptor Descriptor that was writte to the associated
+     *                       remote device.
+     * @param status The result of the write operation
+     */
+    public void onDescriptorWrite(BluetoothGattDescriptor descriptor,
+                               int status) {
+    }
+
+    /**
+     * Callback invoked when a reliable write transaction has been completed.
+     *
+     * @param device Remote device
+     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the reliable write
+     *               transaction was executed successfully
+     */
+    public void onReliableWriteCompleted(BluetoothDevice device, int status) {
+    }
+
+    /**
+     * Callback reporting the RSSI for a remote device connection.
+     *
+     * This callback is triggered in response to the
+     * {@link BluetoothGatt#readRemoteRssi} function.
+     *
+     * @param device Identifies the remote device
+     * @param rssi The RSSI value for the remote device
+     * @param status 0 if the RSSI was read successfully
+     */
+    public void onReadRemoteRssi(BluetoothDevice device, int rssi, int status) {
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
new file mode 100644
index 0000000..18492ab
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
@@ -0,0 +1,670 @@
+/*
+ * Copyright (C) 2013 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.bluetooth;
+
+import java.util.ArrayList;
+import java.util.IllegalFormatConversionException;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Represents a Bluetooth Gatt Characteristic
+ * @hide
+ */
+public class BluetoothGattCharacteristic {
+
+    /**
+     * Characteristic proprty: Characteristic is broadcastable.
+     */
+    public static final int PROPERTY_BROADCAST = 0x01;
+
+    /**
+     * Characteristic property: Characteristic is readable.
+     */
+    public static final int PROPERTY_READ = 0x02;
+
+    /**
+     * Characteristic property: Characteristic can be written without response.
+     */
+    public static final int PROPERTY_WRITE_NO_RESPONSE = 0x04;
+
+    /**
+     * Characteristic property: Characteristic can be written.
+     */
+    public static final int PROPERTY_WRITE = 0x08;
+
+    /**
+     * Characteristic property: Characteristic supports notification
+     */
+    public static final int PROPERTY_NOTIFY = 0x10;
+
+    /**
+     * Characteristic property: Characteristic supports indication
+     */
+    public static final int PROPERTY_INDICATE = 0x20;
+
+    /**
+     * Characteristic property: Characteristic supports write with signature
+     */
+    public static final int PROPERTY_SIGNED_WRITE = 0x40;
+
+    /**
+     * Characteristic property: Characteristic has extended properties
+     */
+    public static final int PROPERTY_EXTENDED_PROPS = 0x80;
+
+    /**
+     * Characteristic read permission
+     */
+    public static final int PERMISSION_READ = 0x01;
+
+    /**
+     * Characteristic permission: Allow encrypted read operations
+     */
+    public static final int PERMISSION_READ_ENCRYPTED = 0x02;
+
+    /**
+     * Characteristic permission: Allow reading with man-in-the-middle protection
+     */
+    public static final int PERMISSION_READ_ENCRYPTED_MITM = 0x04;
+
+    /**
+     * Characteristic write permission
+     */
+    public static final int PERMISSION_WRITE = 0x10;
+
+    /**
+     * Characteristic permission: Allow encrypted writes
+     */
+    public static final int PERMISSION_WRITE_ENCRYPTED = 0x20;
+
+    /**
+     * Characteristic permission: Allow encrypted writes with man-in-the-middle
+     * protection
+     */
+    public static final int PERMISSION_WRITE_ENCRYPTED_MITM = 0x40;
+
+    /**
+     * Characteristic permission: Allow signed write operations
+     */
+    public static final int PERMISSION_WRITE_SIGNED = 0x80;
+
+    /**
+     * Characteristic permission: Allow signed write operations with
+     * man-in-the-middle protection
+     */
+    public static final int PERMISSION_WRITE_SIGNED_MITM = 0x100;
+
+    /**
+     * Write characteristic, requesting acknoledgement by the remote device
+     */
+    public static final int WRITE_TYPE_DEFAULT = 0x02;
+
+    /**
+     * Wrtite characteristic without requiring a response by the remote device
+     */
+    public static final int WRITE_TYPE_NO_RESPONSE = 0x01;
+
+    /**
+     * Write characteristic including and authenticated signature
+     */
+    public static final int WRITE_TYPE_SIGNED = 0x04;
+
+    /**
+     * Characteristic value format type uint8
+     */
+    public static final int FORMAT_UINT8 = 0x11;
+
+    /**
+     * Characteristic value format type uint16
+     */
+    public static final int FORMAT_UINT16 = 0x12;
+
+    /**
+     * Characteristic value format type uint32
+     */
+    public static final int FORMAT_UINT32 = 0x14;
+
+    /**
+     * Characteristic value format type sint8
+     */
+    public static final int FORMAT_SINT8 = 0x21;
+
+    /**
+     * Characteristic value format type sint16
+     */
+    public static final int FORMAT_SINT16 = 0x22;
+
+    /**
+     * Characteristic value format type sint32
+     */
+    public static final int FORMAT_SINT32 = 0x24;
+
+    /**
+     * Characteristic value format type sfloat (16-bit float)
+     */
+    public static final int FORMAT_SFLOAT = 0x32;
+
+    /**
+     * Characteristic value format type float (32-bit float)
+     */
+    public static final int FORMAT_FLOAT = 0x34;
+
+
+    /**
+     * The UUID of this characteristic.
+     * @hide
+     */
+    protected UUID mUuid;
+
+    /**
+     * Instance ID for this characteristic.
+     * @hide
+     */
+    protected int mInstance;
+
+    /**
+     * Characteristic properties.
+     * @hide
+     */
+    protected int mProperties;
+
+    /**
+     * Characteristic permissions.
+     * @hide
+     */
+    protected int mPermissions;
+
+    /**
+     * Key size (default = 16).
+     * @hide
+     */
+    protected int mKeySize = 16;
+
+    /**
+     * Write type for this characteristic.
+     * See WRITE_TYPE_* constants.
+     * @hide
+     */
+    protected int mWriteType;
+
+    /**
+     * Back-reference to the service this characteristic belongs to.
+     * @hide
+     */
+    protected BluetoothGattService mService;
+
+    /**
+     * The cached value of this characteristic.
+     * @hide
+     */
+    protected byte[] mValue;
+
+    /**
+     * List of descriptors included in this characteristic.
+     */
+    protected List<BluetoothGattDescriptor> mDescriptors;
+
+    /**
+     * Create a new BluetoothGattCharacteristic
+     * @hide
+     */
+    /*package*/ BluetoothGattCharacteristic(BluetoothGattService service,
+                                            UUID uuid, int instanceId,
+                                            int properties, int permissions) {
+        mUuid = uuid;
+        mInstance = instanceId;
+        mProperties = properties;
+        mPermissions = permissions;
+        mService = service;
+        mValue = null;
+        mDescriptors = new ArrayList<BluetoothGattDescriptor>();
+
+        if ((mProperties & PROPERTY_WRITE_NO_RESPONSE) != 0) {
+            mWriteType = WRITE_TYPE_NO_RESPONSE;
+        } else {
+            mWriteType = WRITE_TYPE_DEFAULT;
+        }
+    }
+
+    /**
+     * Returns the deisred key size.
+     * @hide
+     */
+    /*package*/ int getKeySize() {
+        return mKeySize;
+    }
+
+    /**
+     * Add a descriptor to this characteristic
+     * @hide
+     */
+    /*package*/ void addDescriptor(BluetoothGattDescriptor descriptor) {
+        mDescriptors.add(descriptor);
+    }
+
+    /**
+     * Returns the service this characteristic belongs to.
+     * @return The asscociated service
+     */
+    public BluetoothGattService getService() {
+        return mService;
+    }
+
+    /**
+     * Returns the UUID of this characteristic
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return UUID of this characteristic
+     */
+    public UUID getUuid() {
+        return mUuid;
+    }
+
+    /**
+     * Returns the instance ID for this characteristic.
+     *
+     * <p>If a remote device offers multiple characteristics with the same UUID,
+     * the instance ID is used to distuinguish between characteristics.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return Instance ID of this characteristic
+     */
+    public int getInstanceId() {
+        return mInstance;
+    }
+
+    /**
+     * Returns the properties of this characteristic.
+     *
+     * <p>The properties contain a bit mask of property flags indicating
+     * the features of this characteristic.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return Properties of this characteristic
+     */
+    public int getProperties() {
+        return mProperties;
+    }
+
+    /**
+     * Returns the permissions for this characteristic.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return Permissions of this characteristic
+     */
+    public int getPermissions() {
+        return mPermissions;
+    }
+
+    /**
+     * Gets the write type for this characteristic.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return Write type for this characteristic
+     */
+    public int getWriteType() {
+        return mWriteType;
+    }
+
+    /**
+     * Set the write type for this characteristic
+     *
+     * <p>Setting the write type of a characteristic determines how the
+     * {@link BluetoothGatt#writeCharacteristic} function write this
+     * characteristic.
+     *
+     * <p>The default write type for a characteristic is
+     * {@link #WRITE_TYPE_DEFAULT}.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param writeType The write type to for this characteristic. Can be one
+     *                  of:
+     *                  {@link #WRITE_TYPE_DEFAULT},
+     *                  {@link #WRITE_TYPE_NO_RESPONSE} or
+     *                  {@link #WRITE_TYPE_SIGNED}.
+     */
+    public void setWriteType(int writeType) {
+        mWriteType = writeType;
+    }
+
+    /**
+     * Returns a list of descriptors for this characteristic.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return Descriptors for this characteristic
+     */
+    public List<BluetoothGattDescriptor> getDescriptors() {
+        return mDescriptors;
+    }
+
+    /**
+     * Returns a descriptor with a given UUID out of the list of
+     * descriptors for this characteristic.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return Gatt descriptor object or null if no descriptor with the
+     *         given UUID was found.
+     */
+    public BluetoothGattDescriptor getDescriptor(UUID uuid) {
+        for(BluetoothGattDescriptor descriptor : mDescriptors) {
+            if (descriptor.getUuid().equals(uuid)) {
+                return descriptor;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get the stored value for this characteristic.
+     *
+     * <p>This function returns the stored value for this characteristic as
+     * retrieved by calling {@link BluetoothGatt#readCharacteristic}. To cached
+     * value of the characteristic is updated as a result of a read characteristic
+     * operation or if a characteristic update notification has been received.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return Cached value of the characteristic
+     */
+    public byte[] getValue() {
+        return mValue;
+    }
+
+    /**
+     * Return the stored value of this characteristic.
+     *
+     * <p>The formatType parameter determines how the characteristic value
+     * is to be interpreted. For example, settting formatType to
+     * {@link #FORMAT_UINT16} specifies that the first two bytes of the
+     * characteristic value at the given offset are interpreted to generate the
+     * return value.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param formatType The format type used to interpret the characteristic
+     *                   value.
+     * @param offset Offset at which the integer value can be found.
+     * @return Cached value of the characteristic or null of offset exceeds
+     *         value size.
+     */
+    public Integer getIntValue(int formatType, int offset) {
+        if ((offset + getTypeLen(formatType)) > mValue.length) return null;
+
+        switch (formatType) {
+            case FORMAT_UINT8:
+                return unsignedByteToInt(mValue[offset]);
+
+            case FORMAT_UINT16:
+                return unsignedBytesToInt(mValue[offset], mValue[offset+1]);
+
+            case FORMAT_UINT32:
+                return unsignedBytesToInt(mValue[offset],   mValue[offset+1],
+                                          mValue[offset+2], mValue[offset+3]);
+            case FORMAT_SINT8:
+                return unsignedToSigned(unsignedByteToInt(mValue[offset]), 8);
+
+            case FORMAT_SINT16:
+                return unsignedToSigned(unsignedBytesToInt(mValue[offset],
+                                                           mValue[offset+1]), 16);
+
+            case FORMAT_SINT32:
+                return unsignedToSigned(unsignedBytesToInt(mValue[offset],
+                        mValue[offset+1], mValue[offset+2], mValue[offset+3]), 32);
+        }
+
+        return null;
+    }
+
+    /**
+     * Return the stored value of this characteristic.
+     * <p>See {@link #getValue} for details.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param formatType The format type used to interpret the characteristic
+     *                   value.
+     * @param offset Offset at which the float value can be found.
+     * @return Cached value of the characteristic at a given offset or null
+     *         if the requested offset exceeds the value size.
+     */
+    public Float getFloatValue(int formatType, int offset) {
+        if ((offset + getTypeLen(formatType)) > mValue.length) return null;
+
+        switch (formatType) {
+            case FORMAT_SFLOAT:
+                return bytesToFloat(mValue[offset], mValue[offset+1]);
+
+            case FORMAT_FLOAT:
+                return bytesToFloat(mValue[offset],   mValue[offset+1],
+                                    mValue[offset+2], mValue[offset+3]);
+        }
+
+        return null;
+    }
+
+    /**
+     * Return the stored value of this characteristic.
+     * <p>See {@link #getValue} for details.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     * @param offset Offset at which the string value can be found.
+     * @return Cached value of the characteristic
+     */
+    public String getStringValue(int offset) {
+        if (offset > mValue.length) return null;
+        byte[] strBytes = new byte[mValue.length - offset];
+        for (int i=0; i != (mValue.length-offset); ++i) strBytes[i] = mValue[offset+i];
+        return new String(strBytes);
+    }
+
+    /**
+     * Updates the locally stored value of this characteristic.
+     *
+     * <p>This function modifies the locally stored cached value of this
+     * characteristic. To send the value to the remote device, call
+     * {@link BluetoothGatt#writeCharacteristic} to send the value to the
+     * remote device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param value New value for this characteristic
+     * @return true if the locally stored value has been set, false if the
+     *              requested value could not be stored locally.
+     */
+    public boolean setValue(byte[] value) {
+        mValue = value;
+        return true;
+    }
+
+    /**
+     * Set the locally stored value of this characteristic.
+     * <p>See {@link #setValue(byte[])} for details.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param value New value for this characteristic
+     * @param formatType Integer format type used to transform the value parameter
+     * @param offset Offset at which the value should be placed
+     * @return true if the locally stored value has been set
+     */
+    public boolean setValue(int value, int formatType, int offset) {
+        int len = offset + getTypeLen(formatType);
+        if (mValue == null) mValue = new byte[len];
+        if (len > mValue.length) return false;
+
+        switch (formatType) {
+            case FORMAT_SINT8:
+                value = intToSignedBits(value, 8);
+                // Fall-through intended
+            case FORMAT_UINT8:
+                mValue[offset] = (byte)(value & 0xFF);
+                break;
+
+            case FORMAT_SINT16:
+                value = intToSignedBits(value, 16);
+                // Fall-through intended
+            case FORMAT_UINT16:
+                mValue[offset++] = (byte)(value & 0xFF);
+                mValue[offset] = (byte)((value >> 8) & 0xFF);
+                break;
+
+            case FORMAT_SINT32:
+                value = intToSignedBits(value, 32);
+                // Fall-through intended
+            case FORMAT_UINT32:
+                mValue[offset++] = (byte)(value & 0xFF);
+                mValue[offset++] = (byte)((value >> 8) & 0xFF);
+                mValue[offset] = (byte)((value >> 16) & 0xFF);
+                break;
+
+            default:
+                return false;
+        }
+        return true;
+    }
+
+    /**
+     * Set the locally stored value of this characteristic.
+     * <p>See {@link #setValue(byte[])} for details.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     * @param mantissa Mantissa for this characteristic
+     * @param exponent  exponent value for this characteristic
+     * @param formatType Float format type used to transform the value parameter
+     * @param offset Offset at which the value should be placed
+     * @return true if the locally stored value has been set
+     */
+    public boolean setValue(int mantissa, int exponent, int formatType, int offset) {
+        int len = offset + getTypeLen(formatType);
+        if (mValue == null) mValue = new byte[len];
+        if (len > mValue.length) return false;
+
+        switch (formatType) {
+            case FORMAT_SFLOAT:
+                mantissa = intToSignedBits(mantissa, 12);
+                exponent = intToSignedBits(exponent, 4);
+                mValue[offset++] = (byte)(mantissa & 0xFF);
+                mValue[offset] = (byte)((mantissa >> 8) & 0x0F);
+                mValue[offset] += (byte)((exponent & 0x0F) << 4);
+                break;
+
+            case FORMAT_FLOAT:
+                mantissa = intToSignedBits(mantissa, 24);
+                exponent = intToSignedBits(exponent, 8);
+                mValue[offset++] = (byte)(mantissa & 0xFF);
+                mValue[offset++] = (byte)((mantissa >> 8) & 0xFF);
+                mValue[offset++] = (byte)((mantissa >> 16) & 0xFF);
+                mValue[offset] += (byte)(exponent & 0xFF);
+                break;
+
+            default:
+                return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Set the locally stored value of this characteristic.
+     * <p>See {@link #setValue(byte[])} for details.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     * @param value New value for this characteristic
+     * @return true if the locally stored value has been set
+     */
+    public boolean setValue(String value) {
+        mValue = value.getBytes();
+        return true;
+    }
+
+    /**
+     * Returns the size of a give value type.
+     * @hide
+     */
+    private int getTypeLen(int formatType) {
+        return formatType & 0xF;
+    }
+
+    /**
+     * Convert a signed byte to an unsigned int.
+     * @hide
+     */
+    private int unsignedByteToInt(byte b) {
+        return b & 0xFF;
+    }
+
+    /**
+     * Convert signed bytes to a 16-bit unsigned int.
+     * @hide
+     */
+    private int unsignedBytesToInt(byte b0, byte b1) {
+        return (unsignedByteToInt(b0) + (unsignedByteToInt(b1) << 8));
+    }
+
+    /**
+     * Convert signed bytes to a 32-bit unsigned int.
+     * @hide
+     */
+    private int unsignedBytesToInt(byte b0, byte b1, byte b2, byte b3) {
+        return (unsignedByteToInt(b0) + (unsignedByteToInt(b1) << 8))
+             + (unsignedByteToInt(b2) << 16) + (unsignedByteToInt(b3) << 24);
+    }
+
+    /**
+     * Convert signed bytes to a 16-bit short float value.
+     * @hide
+     */
+    private float bytesToFloat(byte b0, byte b1) {
+        int mantissa = unsignedToSigned(unsignedByteToInt(b0)
+                        + ((unsignedByteToInt(b1) & 0x0F) << 8), 12);
+        int exponent = unsignedToSigned(unsignedByteToInt(b1) >> 4, 4);
+        return (float)(mantissa * Math.pow(10, exponent));
+    }
+
+    /**
+     * Convert signed bytes to a 32-bit short float value.
+     * @hide
+     */
+    private float bytesToFloat(byte b0, byte b1, byte b2, byte b3) {
+        int mantissa = unsignedToSigned(unsignedByteToInt(b0)
+                        + (unsignedByteToInt(b1) << 8)
+                        + (unsignedByteToInt(b2) << 16), 24);
+        return (float)(mantissa * Math.pow(10, b3));
+    }
+
+    /**
+     * Convert an unsigned integer value to a two's-complement encoded
+     * signed value.
+     * @hide
+     */
+    private int unsignedToSigned(int unsigned, int size) {
+        if ((unsigned & (1 << size-1)) != 0) {
+            unsigned = -1 * ((1 << size-1) - (unsigned & ((1 << size-1) - 1)));
+        }
+        return unsigned;
+    }
+
+    /**
+     * Convert an integer into the signed bits of a given length.
+     * @hide
+     */
+    private int intToSignedBits(int i, int size) {
+        if (i < 0) {
+            i = (1 << size-1) + (i & ((1 << size-1) - 1));
+        }
+        return i;
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothGattDescriptor.java b/core/java/android/bluetooth/BluetoothGattDescriptor.java
new file mode 100644
index 0000000..ba1f28a
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattDescriptor.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2013 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.bluetooth;
+
+import java.util.UUID;
+
+/**
+ * Represents a Bluetooth Gatt Descriptor
+ * @hide
+ */
+public class BluetoothGattDescriptor {
+
+    /**
+     * Value used to enable notification for a client configuration descriptor
+     */
+    public static final byte[] ENABLE_NOTIFICATION_VALUE = {0x01, 0x00};
+
+    /**
+     * Value used to enable indication for a client configuration descriptor
+     */
+    public static final byte[] ENABLE_INDICATION_VALUE = {0x02, 0x00};
+
+    /**
+     * Value used to disable notifications or indicatinos
+     */
+    public static final byte[] DISABLE_NOTIFICATION_VALUE = {0x00, 0x00};
+
+    /**
+     * Descriptor read permission
+     */
+    public static final int PERMISSION_READ = 0x01;
+
+    /**
+     * Descriptor permission: Allow encrypted read operations
+     */
+    public static final int PERMISSION_READ_ENCRYPTED = 0x02;
+
+    /**
+     * Descriptor permission: Allow reading with man-in-the-middle protection
+     */
+    public static final int PERMISSION_READ_ENCRYPTED_MITM = 0x04;
+
+    /**
+     * Descriptor write permission
+     */
+    public static final int PERMISSION_WRITE = 0x10;
+
+    /**
+     * Descriptor permission: Allow encrypted writes
+     */
+    public static final int PERMISSION_WRITE_ENCRYPTED = 0x20;
+
+    /**
+     * Descriptor permission: Allow encrypted writes with man-in-the-middle
+     * protection
+     */
+    public static final int PERMISSION_WRITE_ENCRYPTED_MITM = 0x40;
+
+    /**
+     * Descriptor permission: Allow signed write operations
+     */
+    public static final int PERMISSION_WRITE_SIGNED = 0x80;
+
+    /**
+     * Descriptor permission: Allow signed write operations with
+     * man-in-the-middle protection
+     */
+    public static final int PERMISSION_WRITE_SIGNED_MITM = 0x100;
+
+    /**
+     * The UUID of this descriptor.
+     * @hide
+     */
+    protected UUID mUuid;
+
+    /**
+     * Permissions for this descriptor
+     * @hide
+     */
+    protected int mPermissions;
+
+    /**
+     * Back-reference to the characteristic this descriptor belongs to.
+     * @hide
+     */
+    protected BluetoothGattCharacteristic mCharacteristic;
+
+    /**
+     * The value for this descriptor.
+     * @hide
+     */
+    protected byte[] mValue;
+
+    /**
+     * Create a new BluetoothGattDescriptor.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param characteristic The characteristic this descriptor belongs to
+     * @param uuid The UUID for this descriptor
+     * @param permissions Permissions for this descriptor
+     */
+    /*package*/ BluetoothGattDescriptor(BluetoothGattCharacteristic characteristic, UUID uuid,
+                                    int permissions) {
+        mCharacteristic = characteristic;
+        mUuid = uuid;
+        mPermissions = permissions;
+    }
+
+    /**
+     * Returns the characteristic this descriptor belongs to.
+     * @return The characteristic.
+     */
+    public BluetoothGattCharacteristic getCharacteristic() {
+        return mCharacteristic;
+    }
+
+    /**
+     * Returns the UUID of this descriptor.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return UUID of this descriptor
+     */
+    public UUID getUuid() {
+        return mUuid;
+    }
+
+    /**
+     * Returns the permissions for this descriptor.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return Permissions of this descriptor
+     */
+    public int getPermissions() {
+        return mPermissions;
+    }
+
+    /**
+     * Returns the stored value for this descriptor
+     *
+     * <p>This function returns the stored value for this descriptor as
+     * retrieved by calling {@link BluetoothGatt#readDescriptor}. To cached
+     * value of the descriptor is updated as a result of a descriptor read
+     * operation.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return Cached value of the descriptor
+     */
+    public byte[] getValue() {
+        return mValue;
+    }
+
+    /**
+     * Updates the locally stored value of this descriptor.
+     *
+     * <p>This function modifies the locally stored cached value of this
+     * descriptor. To send the value to the remote device, call
+     * {@link BluetoothGatt#writeDescriptor} to send the value to the
+     * remote device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param value New value for this descriptor
+     * @return true if the locally stored value has been set, false if the
+     *              requested value could not be stored locally.
+     */
+    public boolean setValue(byte[] value) {
+        mValue = value;
+        return true;
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
new file mode 100644
index 0000000..91a1a94
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -0,0 +1,900 @@
+/*
+ * Copyright (C) 2013 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.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProfile.ServiceListener;
+import android.bluetooth.IBluetoothManager;
+import android.bluetooth.IBluetoothStateChangeCallback;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Public API for the Bluetooth Gatt Profile server role.
+ *
+ * <p>This class provides Bluetooth Gatt server role functionality,
+ * allowing applications to create and advertise Bluetooth Smart services
+ * and characteristics.
+ *
+ * <p>BluetoothGattServer is a proxy object for controlling the Bluetooth Service
+ * via IPC.  Use {@link BluetoothAdapter#getProfileProxy} to get the
+ * BluetoothGatt proxy object.
+ * @hide
+ */
+public final class BluetoothGattServer implements BluetoothProfile {
+    private static final String TAG = "BluetoothGattServer";
+    private static final boolean DBG = true;
+
+    private Context mContext;
+    private ServiceListener mServiceListener;
+    private BluetoothAdapter mAdapter;
+    private IBluetoothGatt mService;
+    private BluetoothGattServerCallback mCallback;
+    private int mServerIf;
+
+    private List<BluetoothGattService> mServices;
+
+    /**
+     * Bluetooth state change handlers
+     */
+    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+        new IBluetoothStateChangeCallback.Stub() {
+            public void onBluetoothStateChange(boolean up) {
+                if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+                if (!up) {
+                    if (DBG) Log.d(TAG,"Unbinding service...");
+                    synchronized (mConnection) {
+                        try {
+                            mService = null;
+                            mContext.unbindService(mConnection);
+                        } catch (Exception re) {
+                            Log.e(TAG,"",re);
+                        }
+                    }
+                } else {
+                    synchronized (mConnection) {
+                        try {
+                            if (mService == null) {
+                                if (DBG) Log.d(TAG,"Binding service...");
+                                if (!mContext.bindService(new
+                                        Intent(IBluetoothGatt.class.getName()),
+                                        mConnection, 0)) {
+                                    Log.e(TAG, "Could not bind to Bluetooth GATT Service");
+                                }
+                            }
+                        } catch (Exception re) {
+                            Log.e(TAG,"",re);
+                        }
+                    }
+                }
+            }
+        };
+
+    /**
+     * Service binder handling
+     */
+    private ServiceConnection mConnection = new ServiceConnection() {
+            public void onServiceConnected(ComponentName className, IBinder service) {
+                if (DBG) Log.d(TAG, "Proxy object connected");
+                mService = IBluetoothGatt.Stub.asInterface(service);
+                ServiceListener serviceListner = mServiceListener;
+                if (serviceListner != null) {
+                    serviceListner.onServiceConnected(BluetoothProfile.GATT_SERVER,
+                                                      BluetoothGattServer.this);
+                }
+            }
+            public void onServiceDisconnected(ComponentName className) {
+                if (DBG) Log.d(TAG, "Proxy object disconnected");
+                mService = null;
+                ServiceListener serviceListner = mServiceListener;
+                if (serviceListner != null) {
+                    serviceListner.onServiceDisconnected(BluetoothProfile.GATT_SERVER);
+                }
+            }
+        };
+
+    /**
+     * Bluetooth GATT interface callbacks
+     */
+    private final IBluetoothGattServerCallback mBluetoothGattServerCallback =
+        new IBluetoothGattServerCallback.Stub() {
+            /**
+             * Application interface registered - app is ready to go
+             * @hide
+             */
+            public void onServerRegistered(int status, int serverIf) {
+                if (DBG) Log.d(TAG, "onServerRegistered() - status=" + status
+                    + " serverIf=" + serverIf);
+                mServerIf = serverIf;
+                try {
+                    mCallback.onAppRegistered(status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Callback reporting an LE scan result.
+             * @hide
+             */
+            public void onScanResult(String address, int rssi, byte[] advData) {
+                if (DBG) Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" +rssi);
+
+                try {
+                    mCallback.onScanResult(mAdapter.getRemoteDevice(address),
+                                           rssi, advData);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Server connection state changed
+             * @hide
+             */
+            public void onServerConnectionState(int status, int serverIf,
+                                                boolean connected, String address) {
+                if (DBG) Log.d(TAG, "onServerConnectionState() - status=" + status
+                    + " serverIf=" + serverIf + " device=" + address);
+                try {
+                    mCallback.onConnectionStateChange(mAdapter.getRemoteDevice(address), status,
+                                                      connected ? BluetoothProfile.STATE_CONNECTED :
+                                                      BluetoothProfile.STATE_DISCONNECTED);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Service has been added
+             * @hide
+             */
+            public void onServiceAdded(int status, int srvcType,
+                                       int srvcInstId, ParcelUuid srvcId) {
+                UUID srvcUuid = srvcId.getUuid();
+                if (DBG) Log.d(TAG, "onServiceAdded() - service=" + srvcUuid
+                    + "status=" + status);
+
+                BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
+                if (service == null) return;
+
+                try {
+                    mCallback.onServiceAdded((int)status, service);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Remote client characteristic read request.
+             * @hide
+             */
+            public void onCharacteristicReadRequest(String address, int transId,
+                            int offset, boolean isLong, int srvcType, int srvcInstId,
+                            ParcelUuid srvcId, int charInstId, ParcelUuid charId) {
+                UUID srvcUuid = srvcId.getUuid();
+                UUID charUuid = charId.getUuid();
+                if (DBG) Log.d(TAG, "onCharacteristicReadRequest() - "
+                    + "service=" + srvcUuid + ", characteristic=" + charUuid);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
+                if (service == null) return;
+
+                BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+                    charUuid);
+                if (characteristic == null) return;
+
+                try {
+                    mCallback.onCharacteristicReadRequest(device, transId, offset, characteristic);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Remote client descriptor read request.
+             * @hide
+             */
+            public void onDescriptorReadRequest(String address, int transId,
+                            int offset, boolean isLong, int srvcType, int srvcInstId,
+                            ParcelUuid srvcId, int charInstId, ParcelUuid charId,
+                            ParcelUuid descrId) {
+                UUID srvcUuid = srvcId.getUuid();
+                UUID charUuid = charId.getUuid();
+                UUID descrUuid = descrId.getUuid();
+                if (DBG) Log.d(TAG, "onCharacteristicReadRequest() - "
+                    + "service=" + srvcUuid + ", characteristic=" + charUuid
+                    + "descriptor=" + descrUuid);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
+                if (service == null) return;
+
+                BluetoothGattCharacteristic characteristic = service.getCharacteristic(charUuid);
+                if (characteristic == null) return;
+
+                BluetoothGattDescriptor descriptor = characteristic.getDescriptor(descrUuid);
+                if (descriptor == null) return;
+
+                try {
+                    mCallback.onDescriptorReadRequest(device, transId, offset, descriptor);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Remote client characteristic write request.
+             * @hide
+             */
+            public void onCharacteristicWriteRequest(String address, int transId,
+                            int offset, int length, boolean isPrep, boolean needRsp,
+                            int srvcType, int srvcInstId, ParcelUuid srvcId,
+                            int charInstId, ParcelUuid charId, byte[] value) {
+                UUID srvcUuid = srvcId.getUuid();
+                UUID charUuid = charId.getUuid();
+                if (DBG) Log.d(TAG, "onCharacteristicWriteRequest() - "
+                    + "service=" + srvcUuid + ", characteristic=" + charUuid);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
+                if (service == null) return;
+
+                BluetoothGattCharacteristic characteristic = service.getCharacteristic(charUuid);
+                if (characteristic == null) return;
+
+                try {
+                    mCallback.onCharacteristicWriteRequest(device, transId, characteristic,
+                                                           isPrep, needRsp, offset, value);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+
+            }
+
+            /**
+             * Remote client descriptor write request.
+             * @hide
+             */
+            public void onDescriptorWriteRequest(String address, int transId,
+                            int offset, int length, boolean isPrep, boolean needRsp,
+                            int srvcType, int srvcInstId, ParcelUuid srvcId,
+                            int charInstId, ParcelUuid charId, ParcelUuid descrId,
+                            byte[] value) {
+                UUID srvcUuid = srvcId.getUuid();
+                UUID charUuid = charId.getUuid();
+                UUID descrUuid = descrId.getUuid();
+                if (DBG) Log.d(TAG, "onDescriptorWriteRequest() - "
+                    + "service=" + srvcUuid + ", characteristic=" + charUuid
+                    + "descriptor=" + descrUuid);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+
+                BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
+                if (service == null) return;
+
+                BluetoothGattCharacteristic characteristic = service.getCharacteristic(charUuid);
+                if (characteristic == null) return;
+
+                BluetoothGattDescriptor descriptor = characteristic.getDescriptor(descrUuid);
+                if (descriptor == null) return;
+
+                try {
+                    mCallback.onDescriptorWriteRequest(device, transId, descriptor,
+                                                       isPrep, needRsp, offset, value);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Execute pending writes.
+             * @hide
+             */
+            public void onExecuteWrite(String address, int transId,
+                                       boolean execWrite) {
+                if (DBG) Log.d(TAG, "onExecuteWrite() - "
+                    + "device=" + address + ", transId=" + transId
+                    + "execWrite=" + execWrite);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                if (device == null) return;
+
+                try {
+                    mCallback.onExecuteWrite(device, transId, execWrite);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+        };
+
+    /**
+     * Create a BluetoothGattServer proxy object.
+     */
+    /*package*/ BluetoothGattServer(Context context, ServiceListener l) {
+        mContext = context;
+        mServiceListener = l;
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mServices = new ArrayList<BluetoothGattService>();
+
+        IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE);
+        if (b != null) {
+            IBluetoothManager mgr = IBluetoothManager.Stub.asInterface(b);
+            try {
+                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException re) {
+                Log.e(TAG, "Unable to register BluetoothStateChangeCallback", re);
+            }
+        } else {
+            Log.e(TAG, "Unable to get BluetoothManager interface.");
+            throw new RuntimeException("BluetoothManager inactive");
+        }
+
+        //Bind to the service only if the Bluetooth is ON
+        if(mAdapter.isEnabled()){
+            if (!context.bindService(new Intent(IBluetoothGatt.class.getName()), mConnection, 0)) {
+                Log.e(TAG, "Could not bind to Bluetooth Gatt Service");
+            }
+        }
+    }
+
+    /**
+     * Close the connection to the gatt service.
+     */
+    /*package*/ void close() {
+        if (DBG) Log.d(TAG, "close()");
+
+        unregisterApp();
+        mServiceListener = null;
+
+        IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE);
+        if (b != null) {
+            IBluetoothManager mgr = IBluetoothManager.Stub.asInterface(b);
+            try {
+                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException re) {
+                Log.e(TAG, "Unable to unregister BluetoothStateChangeCallback", re);
+            }
+        }
+
+        synchronized (mConnection) {
+            if (mService != null) {
+                try {
+                    mService = null;
+                    mContext.unbindService(mConnection);
+                } catch (Exception re) {
+                    Log.e(TAG,"",re);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns a service by UUID, instance and type.
+     * @hide
+     */
+    /*package*/ BluetoothGattService getService(UUID uuid, int instanceId, int type) {
+        for(BluetoothGattService svc : mServices) {
+            if (svc.getType() == type &&
+                svc.getInstanceId() == instanceId &&
+                svc.getUuid().equals(uuid)) {
+                return svc;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Register an application callback to start using Gatt.
+     *
+     * <p>This is an asynchronous call. The callback is used to notify
+     * success or failure if the function returns true.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param callback Gatt callback handler that will receive asynchronous
+     *          callbacks.
+     * @return true, if application was successfully registered.
+     */
+    public boolean registerApp(BluetoothGattServerCallback callback) {
+        if (DBG) Log.d(TAG, "registerApp()");
+        if (mService == null) return false;
+
+        mCallback = callback;
+        UUID uuid = UUID.randomUUID();
+        if (DBG) Log.d(TAG, "registerApp() - UUID=" + uuid);
+
+        try {
+            mService.registerServer(new ParcelUuid(uuid), mBluetoothGattServerCallback);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Unregister the current application and callbacks.
+     */
+    public void unregisterApp() {
+        if (DBG) Log.d(TAG, "unregisterApp() - mServerIf=" + mServerIf);
+        if (mService == null || mServerIf == 0) return;
+
+        try {
+            mCallback = null;
+            mService.unregisterServer(mServerIf);
+            mServerIf = 0;
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+    }
+
+    /**
+     * Starts a scan for Bluetooth LE devices.
+     *
+     * <p>Results of the scan are reported using the
+     * {@link BluetoothGattServerCallback#onScanResult} callback.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return true, if the scan was started successfully
+     */
+    public boolean startScan() {
+        if (DBG) Log.d(TAG, "startScan()");
+        if (mService == null || mServerIf == 0) return false;
+
+        try {
+            mService.startScan(mServerIf, true);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Starts a scan for Bluetooth LE devices, looking for devices that
+     * advertise given services.
+     *
+     * <p>Devices which advertise all specified services are reported using the
+     * {@link BluetoothGattServerCallback#onScanResult} callback.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param serviceUuids Array of services to look for
+     * @return true, if the scan was started successfully
+     */
+    public boolean startScan(UUID[] serviceUuids) {
+        if (DBG) Log.d(TAG, "startScan() - with UUIDs");
+        if (mService == null || mServerIf == 0) return false;
+
+        try {
+            ParcelUuid[] uuids = new ParcelUuid[serviceUuids.length];
+            for(int i = 0; i != uuids.length; ++i) {
+                uuids[i] = new ParcelUuid(serviceUuids[i]);
+            }
+            mService.startScanWithUuids(mServerIf, true, uuids);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Stops an ongoing Bluetooth LE device scan.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     */
+    public void stopScan() {
+        if (DBG) Log.d(TAG, "stopScan()");
+        if (mService == null || mServerIf == 0) return;
+
+        try {
+            mService.stopScan(mServerIf, true);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+    }
+
+    /**
+     * Initiate a connection to a Bluetooth Gatt capable device.
+     *
+     * <p>The connection may not be established right away, but will be
+     * completed when the remote device is available. A
+     * {@link BluetoothGattCallback#onConnectionStateChange} callback will be
+     * invoked when the connection state changes as a result of this function.
+     *
+     * <p>The autoConnect paramter determines whether to actively connect to
+     * the remote device, or rather passively scan and finalize the connection
+     * when the remote device is in range/available. Generally, the first ever
+     * connection to a device should be direct (autoConnect set to false) and
+     * subsequent connections to known devices should be invoked with the
+     * autoConnect parameter set to false.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote device to connect to
+     * @param autoConnect Whether to directly connect to the remote device (false)
+     *                    or to automatically connect as soon as the remote
+     *                    device becomes available (true).
+     * @return true, if the connection attempt was initiated successfully
+     */
+    public boolean connect(BluetoothDevice device, boolean autoConnect) {
+        if (DBG) Log.d(TAG, "connect: " + device.getAddress() + ", auto: " + autoConnect);
+        if (mService == null || mServerIf == 0) return false;
+
+        try {
+            mService.serverConnect(mServerIf, device.getAddress(),
+                               autoConnect ? false : true); // autoConnect is inverse of "isDirect"
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Disconnects an established connection, or cancels a connection attempt
+     * currently in progress.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote device
+     */
+    public void cancelConnection(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "cancelConnection() - device: " + device.getAddress());
+        if (mService == null || mServerIf == 0) return;
+
+        try {
+            mService.serverDisconnect(mServerIf, device.getAddress());
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+    }
+
+    /**
+     * Send a response to a read or write request to a remote device.
+     *
+     * <p>This function must be invoked in when a remote read/write request
+     * is received by one of these callback methots:
+     *
+     * <ul>
+     *      <li>{@link BluetoothGattServerCallback#onCharacteristicReadRequest}
+     *      <li>{@link BluetoothGattServerCallback#onCharacteristicWriteRequest}
+     *      <li>{@link BluetoothGattServerCallback#onDescriptorReadRequest}
+     *      <li>{@link BluetoothGattServerCallback#onDescriptorWriteRequest}
+     * </ul>
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device The remote device to send this response to
+     * @param requestId The ID of the request that was received with the callback
+     * @param status The status of the request to be sent to the remote devices
+     * @param offset Value offset for partial read/write response
+     * @param value The value of the attribute that was read/written (optional)
+     */
+    public boolean sendResponse(BluetoothDevice device, int requestId,
+                                int status, int offset, byte[] value) {
+        if (DBG) Log.d(TAG, "sendResponse() - device: " + device.getAddress());
+        if (mService == null || mServerIf == 0) return false;
+
+        try {
+            mService.sendResponse(mServerIf, device.getAddress(), requestId,
+                                  status, offset, value);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Send a notification or indication that a local characteristic has been
+     * updated.
+     *
+     * <p>A notification or indication is sent to the remote device to signal
+     * that the characteristic has been updated. This function should be invoked
+     * for every client that requests notifications/indications by writing
+     * to the "Client Configuration" descriptor for the given characteristic.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device The remote device to receive the notification/indication
+     * @param characteristic The local characteristic that has been updated
+     * @param confirm true to request confirmation from the client (indication),
+     *                false to send a notification
+     * @return true, if the notification has been triggered successfully
+     */
+    public boolean notifyCharacteristicChanged(BluetoothDevice device,
+                    BluetoothGattCharacteristic characteristic, boolean confirm) {
+        if (DBG) Log.d(TAG, "notifyCharacteristicChanged() - device: " + device.getAddress());
+        if (mService == null || mServerIf == 0) return false;
+
+        BluetoothGattService service = characteristic.getService();
+        if (service == null) return false;
+
+        try {
+            mService.sendNotification(mServerIf, device.getAddress(),
+                    service.getType(), service.getInstanceId(),
+                    new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
+                    new ParcelUuid(characteristic.getUuid()), confirm,
+                    characteristic.getValue());
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Add a service to the list of services to be advertised.
+     *
+     * <p>Once a service has been addded to the the list, the service and it's
+     * included characteristics will be advertised by the local device.
+     *
+     * <p>If the local device is already advertising services when this function
+     * is called, a service update notification will be sent to all clients.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param service Service to be added to the list of services advertised
+     *                by this device.
+     * @return true, if the service has been added successfully
+     */
+    public boolean addService(BluetoothGattService service) {
+        if (DBG) Log.d(TAG, "addService() - service: " + service.getUuid());
+        if (mService == null || mServerIf == 0) return false;
+
+        mServices.add(service);
+
+        try {
+            mService.beginServiceDeclaration(mServerIf, service.getType(),
+                service.getInstanceId(), service.getHandles(),
+                new ParcelUuid(service.getUuid()));
+
+            List<BluetoothGattService> includedServices = service.getIncludedServices();
+            for (BluetoothGattService includedService : includedServices) {
+                mService.addIncludedService(mServerIf,
+                    includedService.getType(),
+                    includedService.getInstanceId(),
+                    new ParcelUuid(includedService.getUuid()));
+            }
+
+            List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
+            for (BluetoothGattCharacteristic characteristic : characteristics) {
+                int permission = ((characteristic.getKeySize() - 7) << 12)
+                                    + characteristic.getPermissions();
+                mService.addCharacteristic(mServerIf,
+                    new ParcelUuid(characteristic.getUuid()),
+                    characteristic.getProperties(), permission);
+
+                List<BluetoothGattDescriptor> descriptors = characteristic.getDescriptors();
+                for (BluetoothGattDescriptor descriptor: descriptors) {
+                    mService.addDescriptor(mServerIf,
+                        new ParcelUuid(descriptor.getUuid()),
+                        descriptor.getPermissions());
+                }
+            }
+
+            mService.endServiceDeclaration(mServerIf);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Removes a service from the list of services to be advertised.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param service Service to beremoved.
+     * @return true, if the service has been removed
+     */
+    public boolean removeService(BluetoothGattService service) {
+        if (DBG) Log.d(TAG, "removeService() - service: " + service.getUuid());
+        if (mService == null || mServerIf == 0) return false;
+
+        BluetoothGattService intService = getService(service.getUuid(),
+                                service.getInstanceId(), service.getType());
+        if (intService == null) return false;
+
+        try {
+            mService.removeService(mServerIf, service.getType(),
+                service.getInstanceId(), new ParcelUuid(service.getUuid()));
+            mServices.remove(intService);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Remove all services from the list of advertised services.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     */
+    public void clearServices() {
+        if (DBG) Log.d(TAG, "clearServices()");
+        if (mService == null || mServerIf == 0) return;
+
+        try {
+            mService.clearServices(mServerIf);
+            mServices.clear();
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+    }
+
+    /**
+     * Returns a list of GATT services offered bu this device.
+     *
+     * <p>An application must call {@link #addService} to add a serice to the
+     * list of services offered by this device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return List of services. Returns an empty list
+     *         if no services have been added yet.
+     */
+    public List<BluetoothGattService> getServices() {
+        return mServices;
+    }
+
+    /**
+     * Returns a {@link BluetoothGattService} from the list of services offered
+     * by this device.
+     *
+     * <p>If multiple instances of the same service (as identified by UUID)
+     * exist, the first instance of the service is returned.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param uuid UUID of the requested service
+     * @return BluetoothGattService if supported, or null if the requested
+     *         service is not offered by this device.
+     */
+    public BluetoothGattService getService(UUID uuid) {
+        for (BluetoothGattService service : mServices) {
+            if (service.getUuid().equals(uuid)) {
+                return service;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Get the current connection state of the profile.
+     *
+     * <p>This is not specific to any application configuration but represents
+     * the connection state of the local Bluetooth adapter for this profile.
+     * This can be used by applications like status bar which would just like
+     * to know the state of the local adapter.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote bluetooth device.
+     * @return State of the profile connection. One of
+     *               {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
+     *               {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}
+     */
+    @Override
+    public int getConnectionState(BluetoothDevice device) {
+        if (DBG) Log.d(TAG,"getConnectionState()");
+        if (mService == null) return STATE_DISCONNECTED;
+
+        List<BluetoothDevice> connectedDevices = getConnectedDevices();
+        for(BluetoothDevice connectedDevice : connectedDevices) {
+            if (device.equals(connectedDevice)) {
+                return STATE_CONNECTED;
+            }
+        }
+
+        return STATE_DISCONNECTED;
+    }
+
+    /**
+     * Get connected devices for the Gatt profile.
+     *
+     * <p> Return the set of devices which are in state {@link #STATE_CONNECTED}
+     *
+     * <p>This is not specific to any application configuration but represents
+     * the connection state of the local Bluetooth adapter for this profile.
+     * This can be used by applications like status bar which would just like
+     * to know the state of the local adapter.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return List of devices. The list will be empty on error.
+     */
+    @Override
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (DBG) Log.d(TAG,"getConnectedDevices");
+
+        List<BluetoothDevice> connectedDevices = new ArrayList<BluetoothDevice>();
+        if (mService == null) return connectedDevices;
+
+        try {
+            connectedDevices = mService.getDevicesMatchingConnectionStates(
+                new int[] { BluetoothProfile.STATE_CONNECTED });
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+
+        return connectedDevices;
+    }
+
+    /**
+     * Get a list of devices that match any of the given connection
+     * states.
+     *
+     * <p> If none of the devices match any of the given states,
+     * an empty list will be returned.
+     *
+     * <p>This is not specific to any application configuration but represents
+     * the connection state of the local Bluetooth adapter for this profile.
+     * This can be used by applications like status bar which would just like
+     * to know the state of the local adapter.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param states Array of states. States can be one of
+     *              {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
+     *              {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING},
+     * @return List of devices. The list will be empty on error.
+     */
+    @Override
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (DBG) Log.d(TAG,"getDevicesMatchingConnectionStates");
+
+        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
+        if (mService == null) return devices;
+
+        try {
+            devices = mService.getDevicesMatchingConnectionStates(states);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+
+        return devices;
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothGattServerCallback.java b/core/java/android/bluetooth/BluetoothGattServerCallback.java
new file mode 100644
index 0000000..4f608ff
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattServerCallback.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2013 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.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+
+import android.util.Log;
+
+/**
+ * This abstract class is used to implement {@link BluetoothGattServer} callbacks.
+ * @hide
+ */
+public abstract class BluetoothGattServerCallback {
+    /**
+     * Callback to inform change in registration state of the  application.
+     *
+     * @param status Returns {@link BluetoothGatt#GATT_SUCCESS} if the application
+     *               was successfully registered.
+     */
+    public void onAppRegistered(int status) {
+    }
+
+    /**
+     * Callback reporting an LE device found during a device scan initiated
+     * by the {@link BluetoothGattServer#startScan} function.
+     *
+     * @param device Identifies the remote device
+     * @param rssi The RSSI value for the remote device as reported by the
+     *             Bluetooth hardware. 0 if no RSSI value is available.
+     * @param scanRecord The content of the advertisement record offered by
+     *                   the remote device.
+     */
+    public void onScanResult(BluetoothDevice device, int rssi, byte[] scanRecord) {
+    }
+
+    /**
+     * Callback indicating when a remote device has been connected or disconnected.
+     *
+     * @param device Remote device that has been connected or disconnected.
+     * @param status Status of the connect or disconnect operation.
+     * @param newState Returns the new connection state. Can be one of
+     *                  {@link BluetoothProfile#STATE_DISCONNECTED} or
+     *                  {@link BluetoothProfile#STATE_CONNECTED}
+     */
+    public void onConnectionStateChange(BluetoothDevice device, int status,
+                                        int newState) {
+    }
+
+    /**
+     * Indicates whether a local service has been added successfully.
+     *
+     * @param status Returns {@link BluetoothGatt#GATT_SUCCESS} if the service
+     *               was added successfully.
+     * @param service The service that has been added
+     */
+    public void onServiceAdded(int status, BluetoothGattService service) {
+    }
+
+    /**
+     * A remote client has requested to read a local characteristic.
+     *
+     * <p>An application must call {@link BluetoothGattServer#sendResponse}
+     * to complete the request.
+     *
+     * @param device The remote device that has requested the read operation
+     * @param requestId The Id of the request
+     * @param offset Offset into the value of the characteristic
+     * @param characteristic Characteristic to be read
+     */
+    public void onCharacteristicReadRequest(BluetoothDevice device, int requestId,
+                        int offset, BluetoothGattCharacteristic characteristic) {
+    }
+
+    /**
+     * A remote client has requested to write to a local characteristic.
+     *
+     * <p>An application must call {@link BluetoothGattServer#sendResponse}
+     * to complete the request.
+     *
+     * @param device The remote device that has requested the write operation
+     * @param requestId The Id of the request
+     * @param characteristic Characteristic to be written to.
+     * @param preparedWrite true, if this write operation should be queued for
+     *                      later execution.
+     * @param responseNeeded true, if the remote device requires a response
+     * @param offset The offset given for the value
+     * @param value The value the client wants to assign to the characteristic
+     */
+    public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId,
+                            BluetoothGattCharacteristic characteristic,
+                            boolean preparedWrite, boolean responseNeeded,
+                            int offset, byte[] value) {
+    }
+
+    /**
+     * A remote client has requested to read a local descriptor.
+     *
+     * <p>An application must call {@link BluetoothGattServer#sendResponse}
+     * to complete the request.
+     *
+     * @param device The remote device that has requested the read operation
+     * @param requestId The Id of the request
+     * @param offset Offset into the value of the characteristic
+     * @param descriptor Descriptor to be read
+     */
+    public void onDescriptorReadRequest(BluetoothDevice device, int requestId,
+                            int offset, BluetoothGattDescriptor descriptor) {
+    }
+
+    /**
+     * A remote client has requested to write to a local descriptor.
+     *
+     * <p>An application must call {@link BluetoothGattServer#sendResponse}
+     * to complete the request.
+     *
+     * @param device The remote device that has requested the write operation
+     * @param requestId The Id of the request
+     * @param descriptor Descriptor to be written to.
+     * @param preparedWrite true, if this write operation should be queued for
+     *                      later execution.
+     * @param responseNeeded true, if the remote device requires a response
+     * @param offset The offset given for the value
+     * @param value The value the client wants to assign to the descriptor
+     */
+    public void onDescriptorWriteRequest(BluetoothDevice device, int requestId,
+                            BluetoothGattDescriptor descriptor,
+                            boolean preparedWrite, boolean responseNeeded,
+                            int offset,  byte[] value) {
+    }
+
+    /**
+     * Execute all pending write operations for this device.
+     *
+     * <p>An application must call {@link BluetoothGattServer#sendResponse}
+     * to complete the request.
+     *
+     * @param device The remote device that has requested the write operations
+     * @param requestId The Id of the request
+     * @param execute Whether the pending writes should be executed (true) or
+     *                cancelled (false)
+     */
+    public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothGattService.java b/core/java/android/bluetooth/BluetoothGattService.java
new file mode 100644
index 0000000..6a3ce66e
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattService.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2013 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.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Represents a Bluetooth Gatt Service
+ * @hide
+ */
+public class BluetoothGattService {
+
+    /**
+     * Primary service
+     */
+    public static final int SERVICE_TYPE_PRIMARY = 0;
+
+    /**
+     * Secondary service (included by primary services)
+     */
+    public static final int SERVICE_TYPE_SECONDARY = 1;
+
+
+    /**
+     * The remote device his service is associated with.
+     * This applies to client applications only.
+     * @hide
+     */
+    protected BluetoothDevice mDevice;
+
+    /**
+     * The UUID of this service.
+     * @hide
+     */
+    protected UUID mUuid;
+
+    /**
+     * Instance ID for this service.
+     * @hide
+     */
+    protected int mInstanceId;
+
+    /**
+     * Handle counter override (for conformance testing).
+     * @hide
+     */
+    protected int mHandles = 0;
+
+    /**
+     * Service type (Primary/Secondary).
+     * @hide
+     */
+    protected int mServiceType;
+
+    /**
+     * List of characteristics included in this service.
+     */
+    protected List<BluetoothGattCharacteristic> mCharacteristics;
+
+    /**
+     * List of included services for this service.
+     */
+    protected List<BluetoothGattService> mIncludedServices;
+
+    /**
+     * Create a new BluetoothGattService.
+     * @hide
+     */
+    /*package*/ BluetoothGattService(UUID uuid, int serviceType) {
+        mDevice = null;
+        mUuid = uuid;
+        mInstanceId = 0;
+        mServiceType = serviceType;
+        mCharacteristics = new ArrayList<BluetoothGattCharacteristic>();
+        mIncludedServices = new ArrayList<BluetoothGattService>();
+    }
+
+    /**
+     * Create a new BluetoothGattService
+     * @hide
+     */
+    /*package*/ BluetoothGattService(BluetoothDevice device, UUID uuid,
+                                     int instanceId, int serviceType) {
+        mDevice = device;
+        mUuid = uuid;
+        mInstanceId = instanceId;
+        mServiceType = serviceType;
+        mCharacteristics = new ArrayList<BluetoothGattCharacteristic>();
+        mIncludedServices = new ArrayList<BluetoothGattService>();
+    }
+
+    /**
+     * Returns the device associated with this service.
+     * @hide
+     */
+    /*package*/ BluetoothDevice getDevice() {
+        return mDevice;
+    }
+
+    /**
+     * Add a characteristic to this service.
+     * @hide
+     */
+    /*package*/ void addCharacteristic(BluetoothGattCharacteristic characteristic) {
+        mCharacteristics.add(characteristic);
+    }
+
+    /**
+     * Get characteristic by UUID and instanceId.
+     * @hide
+     */
+    /*package*/ BluetoothGattCharacteristic getCharacteristic(UUID uuid, int instanceId) {
+        for(BluetoothGattCharacteristic characteristic : mCharacteristics) {
+            if (uuid.equals(characteristic.getUuid()) &&
+                    mInstanceId == instanceId)
+                return characteristic;
+        }
+        return null;
+    }
+
+    /**
+     * Get the handle count override (conformance testing.
+     * @hide
+     */
+    /*package*/ int getHandles() {
+        return mHandles;
+    }
+
+    /**
+     * Add an included service to the internal map.
+     * @hide
+     */
+    /*package*/ void addIncludedService(BluetoothGattService includedService) {
+        mIncludedServices.add(includedService);
+    }
+
+    /**
+     * Returns the UUID of this service
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return UUID of this service
+     */
+    public UUID getUuid() {
+        return mUuid;
+    }
+
+    /**
+     * Returns the instance ID for this service
+     *
+     * <p>If a remote device offers multiple services with the same UUID
+     * (ex. multiple battery services for different batteries), the instance
+     * ID is used to distuinguish services.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return Instance ID of this service
+     */
+    public int getInstanceId() {
+        return mInstanceId;
+    }
+
+    /**
+     * Get the type of this service (primary/secondary)
+     * @hide
+     */
+    public int getType() {
+        return mServiceType;
+    }
+
+    /**
+     * Get the list of included Gatt services for this service.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return List of included services or empty list if no included services
+     *         were discovered.
+     */
+    public List<BluetoothGattService> getIncludedServices() {
+        return mIncludedServices;
+    }
+
+    /**
+     * Returns a list of characteristics included in this service.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return Characteristics included in this service
+     */
+    public List<BluetoothGattCharacteristic> getCharacteristics() {
+        return mCharacteristics;
+    }
+
+    /**
+     * Returns a characteristic with a given UUID out of the list of
+     * characteristics offered by this service.
+     *
+     * <p>This is a convenience function to allow access to a given characteristic
+     * without enumerating over the list returned by {@link #getCharacteristics}
+     * manually.
+     *
+     * <p>If a remote service offers multiple characteristics with the same
+     * UUID, the first instance of a characteristic with the given UUID
+     * is returned.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return Gatt characteristic object or null if no characteristic with the
+     *         given UUID was found.
+     */
+    public BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
+        for(BluetoothGattCharacteristic characteristic : mCharacteristics) {
+            if (uuid.equals(characteristic.getUuid()))
+                return characteristic;
+        }
+        return null;
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
old mode 100755
new mode 100644
index 1920efa..9ee202a
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -88,6 +88,18 @@
     public static final int PBAP = 6;
 
     /**
+     * GATT
+     * @hide
+     */
+    static public final int GATT = 7;
+
+    /**
+     * GATT_SERVER
+     * @hide
+     */
+    static public final int GATT_SERVER = 8;
+
+    /**
      * Default priority for devices that we try to auto-connect to and
      * and allow incoming connections for the profile
      * @hide
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
new file mode 100644
index 0000000..c89d132
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2013 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.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+import android.os.ParcelUuid;
+
+import android.bluetooth.IBluetoothGattCallback;
+import android.bluetooth.IBluetoothGattServerCallback;
+
+/**
+ * API for interacting with BLE / GATT
+ * @hide
+ */
+interface IBluetoothGatt {
+    List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
+
+    void startScan(in int appIf, in boolean isServer);
+    void startScanWithUuids(in int appIf, in boolean isServer, in ParcelUuid[] ids);
+    void stopScan(in int appIf, in boolean isServer);
+
+    void registerClient(in ParcelUuid appId, in IBluetoothGattCallback callback);
+    void unregisterClient(in int clientIf);
+    void clientConnect(in int clientIf, in String address, in boolean isDirect);
+    void clientDisconnect(in int clientIf, in String address);
+    void refreshDevice(in int clientIf, in String address);
+    void discoverServices(in int clientIf, in String address);
+    void readCharacteristic(in int clientIf, in String address, in int srvcType,
+                            in int srvcInstanceId, in ParcelUuid srvcId,
+                            in int charInstanceId, in ParcelUuid charId,
+                            in int authReq);
+    void writeCharacteristic(in int clientIf, in String address, in int srvcType,
+                            in int srvcInstanceId, in ParcelUuid srvcId,
+                            in int charInstanceId, in ParcelUuid charId,
+                            in int writeType, in int authReq, in byte[] value);
+    void readDescriptor(in int clientIf, in String address, in int srvcType,
+                            in int srvcInstanceId, in ParcelUuid srvcId,
+                            in int charInstanceId, in ParcelUuid charId,
+                            in ParcelUuid descrUuid, in int authReq);
+    void writeDescriptor(in int clientIf, in String address, in int srvcType,
+                            in int srvcInstanceId, in ParcelUuid srvcId,
+                            in int charInstanceId, in ParcelUuid charId,
+                            in ParcelUuid descrId, in int writeType,
+                            in int authReq, in byte[] value);
+    void registerForNotification(in int clientIf, in String address, in int srvcType,
+                            in int srvcInstanceId, in ParcelUuid srvcId,
+                            in int charInstanceId, in ParcelUuid charId,
+                            in boolean enable);
+    void beginReliableWrite(in int clientIf, in String address);
+    void endReliableWrite(in int clientIf, in String address, in boolean execute);
+    void readRemoteRssi(in int clientIf, in String address);
+
+    void registerServer(in ParcelUuid appId, in IBluetoothGattServerCallback callback);
+    void unregisterServer(in int serverIf);
+    void serverConnect(in int servertIf, in String address, in boolean isDirect);
+    void serverDisconnect(in int serverIf, in String address);
+    void beginServiceDeclaration(in int serverIf, in int srvcType,
+                            in int srvcInstanceId, in int minHandles,
+                            in ParcelUuid srvcId);
+    void addIncludedService(in int serverIf, in int srvcType,
+                            in int srvcInstanceId, in ParcelUuid srvcId);
+    void addCharacteristic(in int serverIf, in ParcelUuid charId,
+                            in int properties, in int permissions);
+    void addDescriptor(in int serverIf, in ParcelUuid descId,
+                            in int permissions);
+    void endServiceDeclaration(in int serverIf);
+    void removeService(in int serverIf, in int srvcType,
+                            in int srvcInstanceId, in ParcelUuid srvcId);
+    void clearServices(in int serverIf);
+    void sendResponse(in int serverIf, in String address, in int requestId,
+                            in int status, in int offset, in byte[] value);
+    void sendNotification(in int serverIf, in String address, in int srvcType,
+                            in int srvcInstanceId, in ParcelUuid srvcId,
+                            in int charInstanceId, in ParcelUuid charId,
+                            in boolean confirm, in byte[] value);
+}
diff --git a/core/java/android/bluetooth/IBluetoothGattCallback.aidl b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
new file mode 100644
index 0000000..fc52172
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2013 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.bluetooth;
+
+import android.os.ParcelUuid;
+
+
+/**
+ * Callback definitions for interacting with BLE / GATT
+ * @hide
+ */
+interface IBluetoothGattCallback {
+    void onClientRegistered(in int status, in int clientIf);
+    void onClientConnectionState(in int status, in int clientIf,
+                                 in boolean connected, in String address);
+    void onScanResult(in String address, in int rssi, in byte[] advData);
+    void onGetService(in String address, in int srvcType, in int srvcInstId,
+                      in ParcelUuid srvcUuid);
+    void onGetIncludedService(in String address, in int srvcType, in int srvcInstId,
+                              in ParcelUuid srvcUuid, in int inclSrvcType,
+                              in int inclSrvcInstId, in ParcelUuid inclSrvcUuid);
+    void onGetCharacteristic(in String address, in int srvcType,
+                             in int srvcInstId, in ParcelUuid srvcUuid,
+                             in int charInstId, in ParcelUuid charUuid,
+                             in int charProps);
+    void onGetDescriptor(in String address, in int srvcType,
+                             in int srvcInstId, in ParcelUuid srvcUuid,
+                             in int charInstId, in ParcelUuid charUuid,
+                             in ParcelUuid descrUuid);
+    void onSearchComplete(in String address, in int status);
+    void onCharacteristicRead(in String address, in int status, in int srvcType,
+                             in int srvcInstId, in ParcelUuid srvcUuid,
+                             in int charInstId, in ParcelUuid charUuid,
+                             in byte[] value);
+    void onCharacteristicWrite(in String address, in int status, in int srvcType,
+                             in int srvcInstId, in ParcelUuid srvcUuid,
+                             in int charInstId, in ParcelUuid charUuid);
+    void onExecuteWrite(in String address, in int status);
+    void onDescriptorRead(in String address, in int status, in int srvcType,
+                             in int srvcInstId, in ParcelUuid srvcUuid,
+                             in int charInstId, in ParcelUuid charUuid,
+                             in ParcelUuid descrUuid, in byte[] value);
+    void onDescriptorWrite(in String address, in int status, in int srvcType,
+                             in int srvcInstId, in ParcelUuid srvcUuid,
+                             in int charInstId, in ParcelUuid charUuid,
+                             in ParcelUuid descrUuid);
+    void onNotify(in String address, in int srvcType,
+                             in int srvcInstId, in ParcelUuid srvcUuid,
+                             in int charInstId, in ParcelUuid charUuid,
+                             in byte[] value);
+    void onReadRemoteRssi(in String address, in int rssi, in int status);
+}
diff --git a/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl b/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
new file mode 100644
index 0000000..ae9bffc
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2013 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.bluetooth;
+
+import android.os.ParcelUuid;
+
+
+/**
+ * Callback definitions for interacting with BLE / GATT
+ * @hide
+ */
+interface IBluetoothGattServerCallback {
+    void onServerRegistered(in int status, in int serverIf);
+    void onScanResult(in String address, in int rssi, in byte[] advData);
+    void onServerConnectionState(in int status, in int serverIf,
+                                 in boolean connected, in String address);
+    void onServiceAdded(in int status, in int srvcType,
+                        in int srvcInstId, in ParcelUuid srvcId);
+    void onCharacteristicReadRequest(in String address, in int transId,
+                                     in int offset, in boolean isLong,
+                                     in int srvcType,
+                                     in int srvcInstId, in ParcelUuid srvcId,
+                                     in int charInstId, in ParcelUuid charId);
+    void onDescriptorReadRequest(in String address, in int transId,
+                                     in int offset, in boolean isLong,
+                                     in int srvcType,
+                                     in int srvcInstId, in ParcelUuid srvcId,
+                                     in int charInstId, in ParcelUuid charId,
+                                     in ParcelUuid descrId);
+    void onCharacteristicWriteRequest(in String address, in int transId,
+                                     in int offset, in int length,
+                                     in boolean isPrep,
+                                     in boolean needRsp,
+                                     in int srvcType,
+                                     in int srvcInstId, in ParcelUuid srvcId,
+                                     in int charInstId, in ParcelUuid charId,
+                                     in byte[] value);
+    void onDescriptorWriteRequest(in String address, in int transId,
+                                     in int offset, in int length,
+                                     in boolean isPrep,
+                                     in boolean needRsp,
+                                     in int srvcType,
+                                     in int srvcInstId, in ParcelUuid srvcId,
+                                     in int charInstId, in ParcelUuid charId,
+                                     in ParcelUuid descrId,
+                                     in byte[] value);
+    void onExecuteWrite(in String address, in int transId, in boolean execWrite);
+}
diff --git a/core/java/android/bluetooth/MutableBluetoothGattCharacteristic.java b/core/java/android/bluetooth/MutableBluetoothGattCharacteristic.java
new file mode 100644
index 0000000..c05abb2
--- /dev/null
+++ b/core/java/android/bluetooth/MutableBluetoothGattCharacteristic.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2013 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.bluetooth;
+
+import java.util.ArrayList;
+import java.util.IllegalFormatConversionException;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Mutable variant of a Bluetooth Gatt Characteristic
+ * @hide
+ */
+public class MutableBluetoothGattCharacteristic extends BluetoothGattCharacteristic {
+
+    /**
+     * Create a new MutableBluetoothGattCharacteristic.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param uuid The UUID for this characteristic
+     * @param properties Properties of this characteristic
+     * @param permissions Permissions for this characteristic
+     */
+    public MutableBluetoothGattCharacteristic(UUID uuid, int properties, int permissions) {
+        super(null, uuid, 0, properties, permissions);
+    }
+
+    /**
+     * Adds a descriptor to this characteristic.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param descriptor Descriptor to be added to this characteristic.
+     */
+    public void addDescriptor(MutableBluetoothGattDescriptor descriptor) {
+        mDescriptors.add(descriptor);
+        descriptor.setCharacteristic(this);
+    }
+
+    /**
+     * Set the desired key size.
+     * @hide
+     */
+    public void setKeySize(int keySize) {
+        mKeySize = keySize;
+    }
+
+    /**
+     * Sets the service associated with this device.
+     * @hide
+     */
+    /*package*/ void setService(BluetoothGattService service) {
+        mService = service;
+    }
+}
diff --git a/core/java/android/bluetooth/MutableBluetoothGattDescriptor.java b/core/java/android/bluetooth/MutableBluetoothGattDescriptor.java
new file mode 100644
index 0000000..e455392
--- /dev/null
+++ b/core/java/android/bluetooth/MutableBluetoothGattDescriptor.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2013 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.bluetooth;
+
+import java.util.UUID;
+
+/**
+ * Mutable variant of a Bluetooth Gatt Descriptor
+ * @hide
+ */
+public class MutableBluetoothGattDescriptor extends BluetoothGattDescriptor {
+
+    /**
+     * Create a new BluetoothGattDescriptor.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param uuid The UUID for this descriptor
+     * @param permissions Permissions for this descriptor
+     */
+    public MutableBluetoothGattDescriptor(UUID uuid, int permissions) {
+        super(null, uuid, permissions);
+    }
+
+    /**
+     * Set the back-reference to the associated characteristic
+     * @hide
+     */
+    /*package*/ void setCharacteristic(BluetoothGattCharacteristic characteristic) {
+        mCharacteristic = characteristic;
+    }
+}
diff --git a/core/java/android/bluetooth/MutableBluetoothGattService.java b/core/java/android/bluetooth/MutableBluetoothGattService.java
new file mode 100644
index 0000000..927f5ab
--- /dev/null
+++ b/core/java/android/bluetooth/MutableBluetoothGattService.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2013 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.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Represents a Bluetooth Gatt Service
+ * @hide
+ */
+public class MutableBluetoothGattService extends BluetoothGattService {
+
+    /**
+     * Create a new MutableBluetoothGattService.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param uuid The UUID for this service
+     * @param serviceType The type of this service (primary/secondary)
+     */
+    public MutableBluetoothGattService(UUID uuid, int serviceType) {
+        super(uuid, serviceType);
+    }
+
+    /**
+     * Add an included service to this service.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param service The service to be added
+     * @return true, if the included service was added to the service
+     */
+    public boolean addService(BluetoothGattService service) {
+        mIncludedServices.add(service);
+        return true;
+    }
+
+    /**
+     * Add a characteristic to this service.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param characteristic The characteristics to be added
+     * @return true, if the characteristic was added to the service
+     */
+    public boolean addCharacteristic(MutableBluetoothGattCharacteristic characteristic) {
+        mCharacteristics.add(characteristic);
+        characteristic.setService(this);
+        return true;
+    }
+
+    /**
+     * Force the instance ID.
+     * This is needed for conformance testing only.
+     * @hide
+     */
+    public void setInstanceId(int instanceId) {
+        mInstanceId = instanceId;
+    }
+
+    /**
+     * Force the number of handles to reserve for this service.
+     * This is needed for conformance testing only.
+     * @hide
+     */
+    public void setHandles(int handles) {
+        mHandles = handles;
+    }
+}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 612f1af..8aef405 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -298,7 +298,7 @@
 
         private void enforceFilePermission(String callingPkg, Uri uri, String mode)
                 throws FileNotFoundException, SecurityException {
-            if (mode != null && mode.startsWith("rw")) {
+            if (mode != null && mode.indexOf('w') != -1) {
                 if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
                     throw new FileNotFoundException("App op not allowed");
                 }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index f8ff8d1..60e9f58 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1158,16 +1158,29 @@
     public static final String ACTION_ASSIST = "android.intent.action.ASSIST";
 
     /**
-     * An optional field on {@link #ACTION_ASSIST} containing the name of the current
-     * foreground application package at the time the assist was invoked.
+     * Activity Action: Perform voice assist action.
+     * <p>
+     * Input: {@link #EXTRA_ASSIST_PACKAGE} and {@link #EXTRA_ASSIST_CONTEXT} can provide
+     * additional optional contextual information about where the user was when they requested
+     * the voice assist.
+     * Output: nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
+
+    /**
+     * An optional field on {@link #ACTION_ASSIST} and {@link #ACTION_VOICE_ASSIST}
+     * containing the name of the current foreground application package at the time
+     * the assist was invoked.
      */
     public static final String EXTRA_ASSIST_PACKAGE
             = "android.intent.extra.ASSIST_PACKAGE";
 
     /**
-     * An optional field on {@link #ACTION_ASSIST} containing additional contextual
-     * information supplied by the current foreground app at the time of the assist
-     * request.  This is a {@link Bundle} of additional data.
+     * An optional field on {@link #ACTION_ASSIST} and {@link #ACTION_VOICE_ASSIST}
+     * containing additional contextual information supplied by the current
+     * foreground app at the time of the assist request.  This is a {@link Bundle} of
+     * additional data.
      */
     public static final String EXTRA_ASSIST_CONTEXT
             = "android.intent.extra.ASSIST_CONTEXT";
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index c765457..4b83611 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -362,4 +362,19 @@
      * Clear a process (pid) from being associated with an interface.
      */
     void clearDnsInterfaceForPid(int pid);
+
+    /**
+     * Start the clatd (464xlat) service
+     */
+    void startClatd(String interfaceName);
+
+    /**
+     * Stop the clatd (464xlat) service
+     */
+    void stopClatd();
+
+    /**
+     * Determine whether the clatd (464xlat) service has been started
+     */
+    boolean isClatdStarted();
 }
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 03a9b09..9955bc1 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -74,17 +74,13 @@
     int mNativeObject; // package scope only for SurfaceControl access
 
     private int mGenerationId; // incremented each time mNativeSurface changes
+    @SuppressWarnings("UnusedDeclaration")
     private final Canvas mCanvas = new CompatibleCanvas();
 
-    // The Translator for density compatibility mode.  This is used for scaling
-    // the canvas to perform the appropriate density transformation.
-    private Translator mCompatibilityTranslator;
-
     // A matrix to scale the matrix set by application. This is set to null for
     // non compatibility mode.
     private Matrix mCompatibleMatrix;
 
-
     /**
      * Rotation constant: 0 degree rotation (natural orientation)
      */
@@ -105,8 +101,6 @@
      */
     public static final int ROTATION_270 = 3;
 
-
-
     /**
      * Create an empty surface, which will later be filled in by readFromParcel().
      * @hide
@@ -169,6 +163,7 @@
         if (mNativeObject != 0) {
             nativeRelease(mNativeObject);
             mNativeObject = 0;
+            mGenerationId++;
         }
         mCloseGuard.close();
     }
@@ -183,6 +178,7 @@
         if (mNativeObject != 0) {
             nativeDestroy(mNativeObject);
             mNativeObject = 0;
+            mGenerationId++;
         }
         mCloseGuard.close();
     }
@@ -291,6 +287,7 @@
                     "SurfaceControl native object is null. Are you using a released SurfaceControl?");
         }
         mNativeObject = nativeCopyFrom(mNativeObject, other.mNativeObject);
+        mGenerationId++;
     }
 
     /**
@@ -312,7 +309,10 @@
             }
             // transfer the reference from other to us
             mNativeObject = other.mNativeObject;
+            mGenerationId++;
+
             other.mNativeObject = 0;
+            other.mGenerationId++;
         }
     }
 
@@ -327,6 +327,7 @@
         }
         mName = source.readString();
         mNativeObject = nativeReadFromParcel(mNativeObject, source);
+        mGenerationId++;
     }
 
     @Override
@@ -405,24 +406,6 @@
         private Matrix mOrigMatrix = null;
 
         @Override
-        public int getWidth() {
-            int w = super.getWidth();
-            if (mCompatibilityTranslator != null) {
-                w = (int)(w * mCompatibilityTranslator.applicationInvertedScale + .5f);
-            }
-            return w;
-        }
-
-        @Override
-        public int getHeight() {
-            int h = super.getHeight();
-            if (mCompatibilityTranslator != null) {
-                h = (int)(h * mCompatibilityTranslator.applicationInvertedScale + .5f);
-            }
-            return h;
-        }
-
-        @Override
         public void setMatrix(Matrix matrix) {
             if (mCompatibleMatrix == null || mOrigMatrix == null || mOrigMatrix.equals(matrix)) {
                 // don't scale the matrix if it's not compatibility mode, or
@@ -435,6 +418,7 @@
             }
         }
 
+        @SuppressWarnings("deprecation")
         @Override
         public void getMatrix(Matrix m) {
             super.getMatrix(m);
diff --git a/core/java/android/webkit/WebIconDatabase.java b/core/java/android/webkit/WebIconDatabase.java
index 99f20ff..e574593 100644
--- a/core/java/android/webkit/WebIconDatabase.java
+++ b/core/java/android/webkit/WebIconDatabase.java
@@ -25,11 +25,19 @@
  * and WebView.getIconDatabase() will return a WebIconDatabase object. This
  * WebIconDatabase object is a single instance and all methods operate on that
  * single object.
+ * The main use-case for this class is calling {@link #open}
+ * to enable favicon functionality on all WebView instances in this process.
+ *
+ * @deprecated This class is only required when running on devices
+ *             up to {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}
  */
+@Deprecated
 public class WebIconDatabase {
     /**
      * Interface for receiving icons from the database.
+     * @deprecated This interface is obsolete.
      */
+    @Deprecated
     public interface IconListener {
         /**
          * Called when the icon has been retrieved from the database and the
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 92b028e..d901d0a 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -422,7 +422,9 @@
 
     /**
      * Sets whether the WebView should save passwords. The default is true.
+     * @deprecated Saving passwords in WebView will not be supported in future versions.
      */
+    @Deprecated
     public void setSavePassword(boolean save) {
         throw new MustOverrideException();
     }
@@ -432,7 +434,9 @@
      *
      * @return whether the WebView saves passwords
      * @see #setSavePassword
+     * @deprecated Saving passwords in WebView will not be supported in future versions.
      */
+    @Deprecated
     public boolean getSavePassword() {
         throw new MustOverrideException();
     }
@@ -515,18 +519,20 @@
 
     /**
      * Enables using light touches to make a selection and activate mouseovers.
-     * The default is false.
+     * @deprecated From {@link android.os.Build.VERSION_CODES#JELLY_BEAN} this
+     *             setting is obsolete and has no effect.
      */
+    @Deprecated
     public void setLightTouchEnabled(boolean enabled) {
         throw new MustOverrideException();
     }
 
     /**
      * Gets whether light touches are enabled.
-     *
-     * @return whether light touches are enabled
      * @see #setLightTouchEnabled
+     * @deprecated This setting is obsolete.
      */
+    @Deprecated
     public boolean getLightTouchEnabled() {
         throw new MustOverrideException();
     }
@@ -1012,7 +1018,9 @@
      * {@link PluginState#OFF}.
      *
      * @param state a PluginState value
+     * @deprecated Plugins will not be supported in future, and should not be used.
      */
+    @Deprecated
     public synchronized void setPluginState(PluginState state) {
         throw new MustOverrideException();
     }
@@ -1091,9 +1099,12 @@
      * this should be viewed as a guide, not a hard limit. Setting the
      * size to a value less than current database size does not cause the
      * database to be trimmed. The default size is {@link Long#MAX_VALUE}.
+     * It is recommended to leave the maximum size set to the default value.
      *
      * @param appCacheMaxSize the maximum size in bytes
+     * @deprecated In future quota will be managed automatically.
      */
+    @Deprecated
     public synchronized void setAppCacheMaxSize(long appCacheMaxSize) {
         throw new MustOverrideException();
     }
@@ -1224,7 +1235,9 @@
      *
      * @return the plugin state as a {@link PluginState} value
      * @see #setPluginState
+     * @deprecated Plugins will not be supported in future, and should not be used.
      */
+    @Deprecated
     public synchronized PluginState getPluginState() {
         throw new MustOverrideException();
     }
@@ -1329,7 +1342,10 @@
      * {@link RenderPriority#NORMAL}.
      *
      * @param priority the priority
+     * @deprecated It is not recommended to adjust thread priorities, and this will
+     *             not be supported in future versions.
      */
+    @Deprecated
     public synchronized void setRenderPriority(RenderPriority priority) {
         throw new MustOverrideException();
     }
diff --git a/core/java/android/webkit/WebStorage.java b/core/java/android/webkit/WebStorage.java
index 1e955bd..7d9373c 100644
--- a/core/java/android/webkit/WebStorage.java
+++ b/core/java/android/webkit/WebStorage.java
@@ -171,7 +171,9 @@
      * The quota is specified in bytes and the origin is specified using its string
      * representation. Note that a quota is not enforced on a per-origin basis
      * for the Application Cache API.
+     * @deprecated Controlling quota per-origin will not be supported in future.
      */
+    @Deprecated
     public void setQuotaForOrigin(String origin, long quota) {
         // Must be a no-op for backward compatibility: see the hidden constructor for reason.
     }
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index dcb664e..175cbcb 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -208,8 +208,7 @@
  * and default scaling is not applied to the web page; if the value is "1.5", then the device is
  * considered a high density device (hdpi) and the page content is scaled 1.5x; if the
  * value is "0.75", then the device is considered a low density device (ldpi) and the content is
- * scaled 0.75x. However, if you specify the {@code "target-densitydpi"} meta property
- * (discussed below), then you can stop this default scaling behavior.</li>
+ * scaled 0.75x.</li>
  * <li>The {@code -webkit-device-pixel-ratio} CSS media query. Use this to specify the screen
  * densities for which this style sheet is to be used. The corresponding value should be either
  * "0.75", "1", or "1.5", to indicate that the styles are for devices with low density, medium
@@ -219,29 +218,6 @@
  * <p>The {@code hdpi.css} stylesheet is only used for devices with a screen pixel ration of 1.5,
  * which is the high density pixel ratio.</p>
  * </li>
- * <li>The {@code target-densitydpi} property for the {@code viewport} meta tag. You can use
- * this to specify the target density for which the web page is designed, using the following
- * values:
- * <ul>
- * <li>{@code device-dpi} - Use the device's native dpi as the target dpi. Default scaling never
- * occurs.</li>
- * <li>{@code high-dpi} - Use hdpi as the target dpi. Medium and low density screens scale down
- * as appropriate.</li>
- * <li>{@code medium-dpi} - Use mdpi as the target dpi. High density screens scale up and
- * low density screens scale down. This is also the default behavior.</li>
- * <li>{@code low-dpi} - Use ldpi as the target dpi. Medium and high density screens scale up
- * as appropriate.</li>
- * <li><em>{@code <value>}</em> - Specify a dpi value to use as the target dpi (accepted
- * values are 70-400).</li>
- * </ul>
- * <p>Here's an example meta tag to specify the target density:</p>
- * <pre>&lt;meta name="viewport" content="target-densitydpi=device-dpi" /&gt;</pre></li>
- * </ul>
- * <p>If you want to modify your web page for different densities, by using the {@code
- * -webkit-device-pixel-ratio} CSS media query and/or the {@code
- * window.devicePixelRatio} DOM property, then you should set the {@code target-densitydpi} meta
- * property to {@code device-dpi}. This stops Android from performing scaling in your web page and
- * allows you to make the necessary adjustments for each density via CSS and JavaScript.</p>
  *
  * <h3>HTML5 Video support</h3>
  *
@@ -602,7 +578,9 @@
      * @param password the password for the given host
      * @see WebViewDatabase#clearUsernamePassword
      * @see WebViewDatabase#hasUsernamePassword
+     * @deprecated Saving passwords in WebView will not be supported in future versions.
      */
+    @Deprecated
     public void savePassword(String host, String username, String password) {
         checkThread();
         mProvider.savePassword(host, username, password);
@@ -999,7 +977,10 @@
     /**
      * Clears this WebView so that onDraw() will draw nothing but white background,
      * and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY.
+     * @deprecated Use WebView.loadUrl("about:blank") to reliably reset the view state
+     *             and release page resources (including any running JavaScript).
      */
+    @Deprecated
     public void clearView() {
         checkThread();
         mProvider.clearView();
@@ -1389,7 +1370,11 @@
      * @param showIme if true, show the IME, assuming the user will begin typing.
      *                If false and text is non-null, perform a find all.
      * @return true if the find dialog is shown, false otherwise
+     * @deprecated This method does not work reliably on all Android versions;
+     *             implementing a custom find dialog using WebView.findAllAsync()
+     *             provides a more robust solution.
      */
+    @Deprecated
     public boolean showFindDialog(String text, boolean showIme) {
         checkThread();
         return mProvider.showFindDialog(text, showIme);
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
index f041f07..db20549 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -110,6 +110,7 @@
 
     private int mCurWindowVisibility = View.VISIBLE;
 
+    private boolean mContentAnimations = true;
     private boolean mHiddenByApp;
     private boolean mHiddenBySystem;
     private boolean mShowingForMode;
@@ -122,7 +123,7 @@
     final AnimatorListener mHideListener = new AnimatorListenerAdapter() {
         @Override
         public void onAnimationEnd(Animator animation) {
-            if (mContentView != null) {
+            if (mContentAnimations && mContentView != null) {
                 mContentView.setTranslationY(0);
                 mTopVisibilityView.setTranslationY(0);
             }
@@ -151,23 +152,24 @@
         mActivity = activity;
         Window window = activity.getWindow();
         View decor = window.getDecorView();
-        init(decor);
-        if (!mActivity.getWindow().hasFeature(Window.FEATURE_ACTION_BAR_OVERLAY)) {
+        boolean overlayMode = mActivity.getWindow().hasFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
+        init(decor, overlayMode);
+        if (!overlayMode) {
             mContentView = decor.findViewById(android.R.id.content);
         }
     }
 
     public ActionBarImpl(Dialog dialog) {
         mDialog = dialog;
-        init(dialog.getWindow().getDecorView());
+        init(dialog.getWindow().getDecorView(), false);
     }
 
-    private void init(View decor) {
+    private void init(View decor, boolean overlayMode) {
         mContext = decor.getContext();
         mOverlayLayout = (ActionBarOverlayLayout) decor.findViewById(
                 com.android.internal.R.id.action_bar_overlay_layout);
         if (mOverlayLayout != null) {
-            mOverlayLayout.setActionBar(this);
+            mOverlayLayout.setActionBar(this, overlayMode);
         }
         mActionView = (ActionBarView) decor.findViewById(com.android.internal.R.id.action_bar);
         mContextView = (ActionBarContextView) decor.findViewById(
@@ -586,6 +588,10 @@
         return mContainerView.getHeight();
     }
 
+    public void enableContentAnimations(boolean enabled) {
+        mContentAnimations = enabled;
+    }
+
     @Override
     public void show() {
         if (mHiddenByApp) {
@@ -684,7 +690,7 @@
             AnimatorSet anim = new AnimatorSet();
             AnimatorSet.Builder b = anim.play(ObjectAnimator.ofFloat(mTopVisibilityView,
                     "translationY", 0));
-            if (mContentView != null) {
+            if (mContentAnimations && mContentView != null) {
                 b.with(ObjectAnimator.ofFloat(mContentView, "translationY",
                         startingY, 0));
             }
@@ -709,7 +715,7 @@
         } else {
             mTopVisibilityView.setAlpha(1);
             mTopVisibilityView.setTranslationY(0);
-            if (mContentView != null) {
+            if (mContentAnimations && mContentView != null) {
                 mContentView.setTranslationY(0);
             }
             if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) {
@@ -742,7 +748,7 @@
             }
             AnimatorSet.Builder b = anim.play(ObjectAnimator.ofFloat(mTopVisibilityView,
                     "translationY", endingY));
-            if (mContentView != null) {
+            if (mContentAnimations && mContentView != null) {
                 b.with(ObjectAnimator.ofFloat(mContentView, "translationY",
                         0, endingY));
             }
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index 18a696e..482eba7 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -40,6 +40,7 @@
     private ActionBarContainer mContainerView;
     private ActionBarView mActionView;
     private View mActionBarBottom;
+    private boolean mOverlayMode;
     private int mLastSystemUiVisibility;
     private final Rect mLocalInsets = new Rect();
 
@@ -63,8 +64,13 @@
         ta.recycle();
     }
 
-    public void setActionBar(ActionBarImpl impl) {
+    public void setOverlayMode(boolean mode) {
+        mOverlayMode = mode;
+    }
+
+    public void setActionBar(ActionBarImpl impl, boolean overlayMode) {
         mActionBar = impl;
+        mOverlayMode = overlayMode;
         if (getWindowToken() != null) {
             // This is being initialized after being added to a window;
             // make sure to update all state now.
@@ -105,8 +111,13 @@
         mLastSystemUiVisibility = visible;
         final boolean barVisible = (visible&SYSTEM_UI_FLAG_FULLSCREEN) == 0;
         final boolean wasVisible = mActionBar != null ? mActionBar.isSystemShowing() : true;
+        final boolean stable = (visible&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
         if (mActionBar != null) {
-            if (barVisible) mActionBar.showForSystem();
+            // We want the bar to be visible if it is not being hidden,
+            // or the app has not turned on a stable UI mode (meaning they
+            // are performing explicit layout around the action bar).
+            mActionBar.enableContentAnimations(!stable);
+            if (barVisible || !stable) mActionBar.showForSystem();
             else mActionBar.hideForSystem();
         }
         if ((diff&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
@@ -161,35 +172,42 @@
             changed |= applyInsets(mActionBarBottom, insets, true, false, true, true);
         }
 
+        int topSpace = 0;
+        if (stable || mActionBarTop.getVisibility() == VISIBLE) {
+            // This is the space needed on top of the window for the action bar.
+            topSpace = mActionBarHeight;
+        }
+        if (mActionBar != null && mActionBar.hasNonEmbeddedTabs()) {
+            View tabs = mContainerView.getTabContainer();
+            if (tabs != null && (stable || tabs.getVisibility() == VISIBLE)) {
+                // If tabs are not embedded, increase space on top to account for them.
+                topSpace += mActionBarHeight;
+            }
+        }
+
+        int bottomSpace = 0;
+        if (mActionView.isSplitActionBar()) {
+            if ((mActionBarBottom != null
+                    && (stable || mActionBarBottom.getVisibility() == VISIBLE))) {
+                // If action bar is split, adjust bottom insets for it.
+                bottomSpace = mActionBarHeight;
+            }
+        }
+
         // If the window has not requested system UI layout flags, we need to
         // make sure its content is not being covered by system UI...  though it
         // will still be covered by the action bar since they have requested it to
         // overlay.
         boolean res = computeFitSystemWindows(insets, mLocalInsets);
+        if (!mOverlayMode && !stable) {
+            mLocalInsets.top += topSpace;
+            mLocalInsets.bottom += bottomSpace;
+        } else {
+            insets.top += topSpace;
+            insets.bottom += bottomSpace;
+        }
         changed |= applyInsets(mContent, mLocalInsets, true, true, true, true);
 
-
-        if (stable || mActionBarTop.getVisibility() == VISIBLE) {
-            // The action bar creates additional insets for its content to use.
-            insets.top += mActionBarHeight;
-        }
-
-        if (mActionBar != null && mActionBar.hasNonEmbeddedTabs()) {
-            View tabs = mContainerView.getTabContainer();
-            if (stable || (tabs != null && tabs.getVisibility() == VISIBLE)) {
-                // If tabs are not embedded, adjust insets to account for them.
-                insets.top += mActionBarHeight;
-            }
-        }
-
-        if (mActionView.isSplitActionBar()) {
-            if (stable || (mActionBarBottom != null
-                    && mActionBarBottom.getVisibility() == VISIBLE)) {
-                // If action bar is split, adjust bottom insets for it.
-                insets.bottom += mActionBarHeight;
-            }
-        }
-
         if (changed) {
             requestLayout();
         }
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index 6410bc3..6640555 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -54,6 +54,21 @@
 
 namespace android {
 
+class ClipCopier : public SkCanvas::ClipVisitor {
+public:
+    ClipCopier(SkCanvas* dstCanvas) : m_dstCanvas(dstCanvas) {}
+
+    virtual void clipRect(const SkRect& rect, SkRegion::Op op, bool antialias) {
+        m_dstCanvas->clipRect(rect, op, antialias);
+    }
+    virtual void clipPath(const SkPath& path, SkRegion::Op op, bool antialias) {
+        m_dstCanvas->clipPath(path, op, antialias);
+    }
+
+private:
+    SkCanvas* m_dstCanvas;
+};
+
 class SkCanvasGlue {
 public:
 
@@ -68,13 +83,15 @@
     static void copyCanvasState(JNIEnv* env, jobject clazz,
                                 SkCanvas* srcCanvas, SkCanvas* dstCanvas) {
         if (srcCanvas && dstCanvas) {
-            if (NULL != srcCanvas->getDevice() && NULL != dstCanvas->getDevice()) {
-                dstCanvas->clipRegion(srcCanvas->getTotalClip());
-            }
             dstCanvas->setMatrix(srcCanvas->getTotalMatrix());
+            if (NULL != srcCanvas->getDevice() && NULL != dstCanvas->getDevice()) {
+                ClipCopier copier(dstCanvas);
+                srcCanvas->replayClips(&copier);
+            }
         }
     }
 
+
     static void freeCaches(JNIEnv* env, jobject) {
         // these are called in no particular order
         SkImageRef_GlobalPool::SetRAMUsed(0);
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 02e76e5..1ffb1b8 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -53,7 +53,6 @@
 static struct {
     jclass clazz;
     jfieldID mNativeObject;
-    jfieldID mGenerationId;
     jfieldID mCanvas;
     jmethodID ctor;
 } gSurfaceClassInfo;
@@ -384,8 +383,6 @@
     gSurfaceClassInfo.clazz = jclass(env->NewGlobalRef(clazz));
     gSurfaceClassInfo.mNativeObject =
             env->GetFieldID(gSurfaceClassInfo.clazz, "mNativeObject", "I");
-    gSurfaceClassInfo.mGenerationId =
-            env->GetFieldID(gSurfaceClassInfo.clazz, "mGenerationId", "I");
     gSurfaceClassInfo.mCanvas =
             env->GetFieldID(gSurfaceClassInfo.clazz, "mCanvas", "Landroid/graphics/Canvas;");
     gSurfaceClassInfo.ctor = env->GetMethodID(gSurfaceClassInfo.clazz, "<init>", "(I)V");
diff --git a/core/jni/com_android_internal_os_ZygoteInit.cpp b/core/jni/com_android_internal_os_ZygoteInit.cpp
index bc8c4a7..44452f0 100644
--- a/core/jni/com_android_internal_os_ZygoteInit.cpp
+++ b/core/jni/com_android_internal_os_ZygoteInit.cpp
@@ -27,12 +27,8 @@
 #include <JNIHelp.h>
 #include "android_runtime/AndroidRuntime.h"
 
-#include <linux/capability.h>
-#include <linux/prctl.h>
+#include <sys/capability.h>
 #include <sys/prctl.h>
-extern "C" int capget(cap_user_header_t hdrp, cap_user_data_t datap);
-extern "C" int capset(cap_user_header_t hdrp, const cap_user_data_t datap);
-
 
 namespace android {
 
diff --git a/core/res/res/layout/screen_action_bar.xml b/core/res/res/layout/screen_action_bar.xml
index f0b2313..95519c6 100644
--- a/core/res/res/layout/screen_action_bar.xml
+++ b/core/res/res/layout/screen_action_bar.xml
@@ -18,38 +18,47 @@
 This is an optimized layout for a screen with the Action Bar enabled.
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.internal.widget.ActionBarOverlayLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/action_bar_overlay_layout"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:fitsSystemWindows="true"
     android:splitMotionEvents="false">
-    <com.android.internal.widget.ActionBarContainer android:id="@+id/action_bar_container"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        style="?android:attr/actionBarStyle">
-        <com.android.internal.widget.ActionBarView
-            android:id="@+id/action_bar"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            style="?android:attr/actionBarStyle" />
-        <com.android.internal.widget.ActionBarContextView
-            android:id="@+id/action_context_bar"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:visibility="gone"
-            style="?android:attr/actionModeStyle" />
-    </com.android.internal.widget.ActionBarContainer>
     <FrameLayout android:id="@android:id/content"
-        android:layout_width="match_parent" 
-        android:layout_height="0dip"
-        android:layout_weight="1"
-        android:foregroundGravity="fill_horizontal|top"
-        android:foreground="?android:attr/windowContentOverlay" />
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+    <LinearLayout android:id="@+id/top_action_bar"
+                  android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  android:layout_gravity="top">
+        <com.android.internal.widget.ActionBarContainer android:id="@+id/action_bar_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true"
+            style="?android:attr/actionBarStyle"
+            android:gravity="top">
+            <com.android.internal.widget.ActionBarView
+                android:id="@+id/action_bar"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                style="?android:attr/actionBarStyle" />
+            <com.android.internal.widget.ActionBarContextView
+                android:id="@+id/action_context_bar"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:visibility="gone"
+                style="?android:attr/actionModeStyle" />
+        </com.android.internal.widget.ActionBarContainer>
+        <ImageView android:src="?android:attr/windowContentOverlay"
+                   android:scaleType="fitXY"
+                   android:layout_width="match_parent"
+                   android:layout_height="wrap_content" />
+    </LinearLayout>
     <com.android.internal.widget.ActionBarContainer android:id="@+id/split_action_bar"
                   android:layout_width="match_parent"
                   android:layout_height="wrap_content"
+                  android:layout_gravity="bottom"
                   style="?android:attr/actionBarSplitStyle"
                   android:visibility="gone"
                   android:gravity="center"/>
-</LinearLayout>
+</com.android.internal.widget.ActionBarOverlayLayout>
diff --git a/core/res/res/layout/screen_action_bar_overlay.xml b/core/res/res/layout/screen_action_bar_overlay.xml
deleted file mode 100644
index c8181d1..0000000
--- a/core/res/res/layout/screen_action_bar_overlay.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<!--
-This is an optimized layout for a screen with
-the Action Bar enabled overlaying application content.
--->
-
-<com.android.internal.widget.ActionBarOverlayLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/action_bar_overlay_layout"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:splitMotionEvents="false">
-    <FrameLayout android:id="@android:id/content"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent" />
-    <LinearLayout android:id="@+id/top_action_bar"
-                  android:layout_width="match_parent"
-                  android:layout_height="wrap_content"
-                  android:layout_gravity="top">
-        <com.android.internal.widget.ActionBarContainer android:id="@+id/action_bar_container"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentTop="true"
-            style="?android:attr/actionBarStyle"
-            android:gravity="top">
-            <com.android.internal.widget.ActionBarView
-                android:id="@+id/action_bar"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                style="?android:attr/actionBarStyle" />
-            <com.android.internal.widget.ActionBarContextView
-                android:id="@+id/action_context_bar"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:visibility="gone"
-                style="?android:attr/actionModeStyle" />
-        </com.android.internal.widget.ActionBarContainer>
-        <ImageView android:src="?android:attr/windowContentOverlay"
-                   android:scaleType="fitXY"
-                   android:layout_width="match_parent"
-                   android:layout_height="wrap_content" />
-    </LinearLayout>
-    <com.android.internal.widget.ActionBarContainer android:id="@+id/split_action_bar"
-                  android:layout_width="match_parent"
-                  android:layout_height="wrap_content"
-                  android:layout_gravity="bottom"
-                  style="?android:attr/actionBarSplitStyle"
-                  android:visibility="gone"
-                  android:gravity="center"/>
-</com.android.internal.widget.ActionBarOverlayLayout>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index a64758c..8cfad68 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Altydaan-VPN koppel tans..."</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Altydaan-VPN gekoppel"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Altydaan-VPN-fout"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Raak om op te stel"</string>
     <string name="upload_file" msgid="2897957172366730416">"Kies lêer"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Geen lêer gekies nie"</string>
     <string name="reset" msgid="2448168080964209908">"Stel terug"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 2a23541..2cef636 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"ሁልጊዜ የበራ VPN በመገናኘት ላይ…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"ሁልጊዜ የበራ VPN ተገናኝቷል"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"ሁልጊዜ የበራ VPN ስህተት"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"ለማዋቀር ይንኩ"</string>
     <string name="upload_file" msgid="2897957172366730416">"ፋይል ምረጥ"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"ምንም ፋይል አልተመረጠም"</string>
     <string name="reset" msgid="2448168080964209908">"ዳግም አስጀምር"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index cba7680..9f6345f 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"جارٍ الاتصال بشبكة ظاهرية خاصة (VPN) دائمة التشغيل..."</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"تم الاتصال بشبكة ظاهرية خاصة (VPN) دائمة التشغيل"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"خطأ بشبكة ظاهرية خاصة (VPN) دائمة التشغيل"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"المس للتهيئة"</string>
     <string name="upload_file" msgid="2897957172366730416">"اختيار ملف"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"لم يتم اختيار أي ملف"</string>
     <string name="reset" msgid="2448168080964209908">"إعادة تعيين"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index a2284ce..2c6aff3 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Падключэнне заўсёды ўключанага VPN..."</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Заўсёды ўключаны i падключаны VPN"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Памылка заўсёды ўключанага VPN"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Націсніце, каб змяніць налады"</string>
     <string name="upload_file" msgid="2897957172366730416">"Выберыце файл"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Файл не выбраны"</string>
     <string name="reset" msgid="2448168080964209908">"Скінуць"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index cc8ec59..f5f89a6 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Установява се връзка с винаги включената виртуална частна мрежа (VPN)…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Установена е връзка с винаги включената виртуална частна мрежа (VPN)"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Грешка във винаги включената виртуална частна мрежа (VPN)"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Докоснете, за да конфигурирате"</string>
     <string name="upload_file" msgid="2897957172366730416">"Избор на файл"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Няма избран файл"</string>
     <string name="reset" msgid="2448168080964209908">"Повторно задаване"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 4e8aa9c..b077308 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"T\'estàs connectant a la VPN sempre activada…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Estàs connectat a la VPN sempre activada"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Error de la VPN sempre activada"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Toca per configurar"</string>
     <string name="upload_file" msgid="2897957172366730416">"Trieu un fitxer"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"No s\'ha escollit cap fitxer"</string>
     <string name="reset" msgid="2448168080964209908">"Reinicia"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 76c17de..3482ed4f 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Připojování k trvalé síti VPN…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Je připojena trvalá síť VPN"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Chyba trvalé sítě VPN"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Dotykem zahájíte konfiguraci"</string>
     <string name="upload_file" msgid="2897957172366730416">"Zvolit soubor"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Není vybrán žádný soubor"</string>
     <string name="reset" msgid="2448168080964209908">"Resetovat"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 6b01a68..3fb84ec 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Opretter forbindelse til Always-on VPN…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Always-on VPN er forbundet"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Fejl i Always-on VPN"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Tryk for at konfigurere"</string>
     <string name="upload_file" msgid="2897957172366730416">"Vælg fil"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil er valgt"</string>
     <string name="reset" msgid="2448168080964209908">"Nulstil"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 7fb0014..95e0b5b 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Verbindung zu durchgehend aktivem VPN wird hergestellt…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Mit durchgehend aktivem VPN verbunden"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Durchgehend aktives VPN – Verbindungsfehler"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Zum Konfigurieren berühren"</string>
     <string name="upload_file" msgid="2897957172366730416">"Datei auswählen"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Keine ausgewählt"</string>
     <string name="reset" msgid="2448168080964209908">"Zurücksetzen"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 3307abf..478b064 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Σύνδεση πάντα ενεργοποιημένου VPN…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Έχει συνδεθεί πάντα ενεργοποιημένο VPN"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Σφάλμα πάντα ενεργοποιημένου VPN"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Αγγίξτε για διαμόρφωση"</string>
     <string name="upload_file" msgid="2897957172366730416">"Επιλογή αρχείου"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Δεν έχει επιλεγεί αρχείο"</string>
     <string name="reset" msgid="2448168080964209908">"Επαναφορά"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index a695432..8794a5d 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Always-on VPN connecting…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Always-on VPN connected"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Always-on VPN error"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Touch to configure"</string>
     <string name="upload_file" msgid="2897957172366730416">"Choose file"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"No file chosen"</string>
     <string name="reset" msgid="2448168080964209908">"Reset"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 3aa900f..985b088 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Estableciendo conexión con la VPN siempre activada..."</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Se estableció conexión con la VPN siempre activada."</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Se produjo un error al establecer conexión con la VPN siempre activada."</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Toca para configurar."</string>
     <string name="upload_file" msgid="2897957172366730416">"Elegir archivo"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"No se seleccionó un archivo."</string>
     <string name="reset" msgid="2448168080964209908">"Restablecer"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 788c265c..febefe3 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Conectando VPN siempre activada…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN siempre activada conectada"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Error de VPN siempre activada"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Toca para configurar"</string>
     <string name="upload_file" msgid="2897957172366730416">"Seleccionar archivo"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Archivo no seleccionado"</string>
     <string name="reset" msgid="2448168080964209908">"Restablecer"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 3312381e..2815c43 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Ühendamine alati sees VPN-iga …"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Ühendatud alati sees VPN-iga"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Alati sees VPN-i viga"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Puudutage seadistamiseks"</string>
     <string name="upload_file" msgid="2897957172366730416">"Valige fail"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Ühtegi faili pole valitud"</string>
     <string name="reset" msgid="2448168080964209908">"Lähtesta"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index b6e3abe..660029a 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"در حال اتصال VPN همیشه فعال…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN همیشه فعال متصل شد"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"خطای VPN همیشه فعال"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"برای پیکربندی لمس کنید"</string>
     <string name="upload_file" msgid="2897957172366730416">"انتخاب فایل"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"هیچ فایلی انتخاب نشد"</string>
     <string name="reset" msgid="2448168080964209908">"بازنشانی"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 92712bc..78e1412 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Yhdistetään aina käytössä olevaan VPN-verkkoon..."</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Yhdistetty aina käytössä olevaan VPN-verkkoon"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Aina käytössä oleva VPN: virhe"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Kosketa ja tee määritykset"</string>
     <string name="upload_file" msgid="2897957172366730416">"Valitse tiedosto"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Ei valittua tiedostoa"</string>
     <string name="reset" msgid="2448168080964209908">"Palauta"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 4c8ccc5..4c9c0a5 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"VPN permanent en cours de connexion…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN permanent connecté"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Erreur du VPN permanent"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Appuyer pour configurer"</string>
     <string name="upload_file" msgid="2897957172366730416">"Sélectionner un fichier"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Aucun fichier sélectionné"</string>
     <string name="reset" msgid="2448168080964209908">"Réinitialiser"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 057f620..3aaace5 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"हमेशा-चालू VPN कनेक्ट हो रहा है…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"हमेशा-चालू VPN कनेक्ट है"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"हमेशा-चालू VPN त्रुटि"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"कॉन्‍फ़िगर करने के लिए स्‍पर्श करें"</string>
     <string name="upload_file" msgid="2897957172366730416">"फ़ाइल चुनें"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"कोई फ़ाइल चुनी नहीं गई"</string>
     <string name="reset" msgid="2448168080964209908">"रीसेट करें"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index cf566d7..dbf80ce 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Povezivanje s uvijek uključenom VPN mrežom…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Povezan s uvijek uključenom VPN mrežom"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Pogreška uvijek uključene VPN mreže"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Dodirnite za konfiguraciju"</string>
     <string name="upload_file" msgid="2897957172366730416">"Odaberite datoteku"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Nema odabranih datoteka"</string>
     <string name="reset" msgid="2448168080964209908">"Ponovo postavi"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 02c80a6..3cb1df7 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Csatlakozás a mindig bekapcsolt VPN-hez..."</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Csatlakozva a mindig bekapcsolt VPN-hez"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Hiba a mindig bekapcsolt VPN-nel"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"A beállításhoz érintse meg"</string>
     <string name="upload_file" msgid="2897957172366730416">"Fájl kiválasztása"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Nincs fájl kiválasztva"</string>
     <string name="reset" msgid="2448168080964209908">"Alaphelyzet"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index f0fd1ef..33e3b2d2 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Menyambungkan VPN selalu aktif..."</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN selalu aktif tersambung"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Kesalahan VPN selalu aktif"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Sentuh untuk mengonfigurasi"</string>
     <string name="upload_file" msgid="2897957172366730416">"Pilih file"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Tidak ada file yang dipilih"</string>
     <string name="reset" msgid="2448168080964209908">"Setel ulang"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index ad9a94d..ec9a00e 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Connessione a VPN sempre attiva…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN sempre attiva connessa"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Errore VPN sempre attiva"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Tocca per configurare"</string>
     <string name="upload_file" msgid="2897957172366730416">"Scegli file"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Nessun file è stato scelto"</string>
     <string name="reset" msgid="2448168080964209908">"Reimposta"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 9a2275b..6a0d9e2 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"ה-VPN שמופעל תמיד, מתחבר..."</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"ה-VPN שפועל תמיד, מחובר"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"שגיאת VPN שמופעל תמיד"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"גע כדי להגדיר"</string>
     <string name="upload_file" msgid="2897957172366730416">"בחר קובץ"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"לא נבחר קובץ"</string>
     <string name="reset" msgid="2448168080964209908">"איפוס"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index df3d6f7..5009af4 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"VPNに常時接続しています…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPNに常時接続しました"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"常時接続VPNのエラー"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"タップして設定してください"</string>
     <string name="upload_file" msgid="2897957172366730416">"ファイルを選択"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"ファイルが選択されていません"</string>
     <string name="reset" msgid="2448168080964209908">"リセット"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 67192dd..46e6339 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"연결 유지 VPN에 연결하는 중…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"연결 유지 VPN에 연결됨"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"연결 유지 VPN 오류"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"설정하려면 터치하세요."</string>
     <string name="upload_file" msgid="2897957172366730416">"파일 선택"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"파일을 선택하지 않았습니다."</string>
     <string name="reset" msgid="2448168080964209908">"초기화"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 22656ff..fb476b5 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Prisijungiama prie visada įjungto VPN…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Prisijungta prie visada įjungto VPN"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Visada įjungto VPN klaida"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Jei norite konfigūruoti, palieskite."</string>
     <string name="upload_file" msgid="2897957172366730416">"Pasirinkti failą"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Nepasirinktas joks failas"</string>
     <string name="reset" msgid="2448168080964209908">"Atstatyti"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index f9b3381..667e29c 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Notiek savienojuma izveide ar vienmēr ieslēgtu VPN…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Izveidots savienojums ar vienmēr ieslēgtu VPN."</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Kļūda saistībā ar vienmēr ieslēgtu VPN"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Pieskarieties, lai konfigurētu."</string>
     <string name="upload_file" msgid="2897957172366730416">"Izvēlēties failu"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Neviens fails nav izvēlēts"</string>
     <string name="reset" msgid="2448168080964209908">"Atiestatīt"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index d9b39aa..a04aac1 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"VPN sentiasa hidup sedang disambungkan..."</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN sentiasa hidup telah disambungkan"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Ralat VPN sentiasa hidup"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Sentuh untuk mengkonfigurasikan"</string>
     <string name="upload_file" msgid="2897957172366730416">"Pilih fail"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Tiada fail dipilih"</string>
     <string name="reset" msgid="2448168080964209908">"Tetapkan semula"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 4e7eba8..62da0ae 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Alltid-på VPN kobler til ..."</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Alltid-på VPN er tilkoblet"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Alltid-på VPN-feil"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Trykk for å konfigurere"</string>
     <string name="upload_file" msgid="2897957172366730416">"Velg fil"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil er valgt"</string>
     <string name="reset" msgid="2448168080964209908">"Tilbakestill"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 8af7e4c..47264b03 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Always-on VPN-verbinding maken…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Always-on VPN-verbinding"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Fout met Always-on VPN"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Aanraken om te configureren"</string>
     <string name="upload_file" msgid="2897957172366730416">"Bestand kiezen"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Geen bestand geselecteerd"</string>
     <string name="reset" msgid="2448168080964209908">"Opnieuw instellen"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index e7bee92..98d7477 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Łączę z zawsze włączoną siecią VPN…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Połączono z zawsze włączoną siecią VPN"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Błąd zawsze włączonej sieci VPN"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Kliknij, by skonfigurować"</string>
     <string name="upload_file" msgid="2897957172366730416">"Wybierz plik"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Nie wybrano pliku"</string>
     <string name="reset" msgid="2448168080964209908">"Resetuj"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 552d24e..5a701a0 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"A ligar VPN sempre ativa..."</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN sempre ativa ligada"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Erro da VPN sempre ativa"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Tocar para configurar"</string>
     <string name="upload_file" msgid="2897957172366730416">"Escolher ficheiro"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Não foi selecionado nenhum ficheiro"</string>
     <string name="reset" msgid="2448168080964209908">"Repor"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index db894a0..9eb11b2 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"VPN sempre ativa conectando..."</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN sempre ativa conectada"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Erro na VPN sempre ativa"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Toque para configurar"</string>
     <string name="upload_file" msgid="2897957172366730416">"Escolher arquivo"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Nenhum arquivo escolhido"</string>
     <string name="reset" msgid="2448168080964209908">"Redefinir"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index fe7b437..eb7185f 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Se efectuează conectarea la reţeaua VPN activată permanent…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Conectat(ă) la reţeaua VPN activată permanent"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Eroare de reţea VPN activată permanent"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Atingeți pentru a configura"</string>
     <string name="upload_file" msgid="2897957172366730416">"Alegeţi un fişier"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Nu au fost găsite fişiere"</string>
     <string name="reset" msgid="2448168080964209908">"Resetaţi"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 249e46b..b8b1c29 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Подключение…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Подключено"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Ошибка"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Нажмите, чтобы изменить настройки"</string>
     <string name="upload_file" msgid="2897957172366730416">"Выбрать файл"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Не выбран файл"</string>
     <string name="reset" msgid="2448168080964209908">"Сбросить"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index e11f93a..c1d2c46 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Prebieha pripájanie k vždy zapnutej sieti VPN..."</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Pripojenie k vždy zapnutej sieti VPN"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Chyba vždy zapnutej siete VPN"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Dotykom spustíte konfiguráciu"</string>
     <string name="upload_file" msgid="2897957172366730416">"Zvoliť súbor"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Nie je vybratý žiadny súbor"</string>
     <string name="reset" msgid="2448168080964209908">"Obnoviť"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 5a3f897..7eb8583 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Povezovanje v stalno vklopljeno navidezno zasebno omrežje ..."</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Vzpostavljena povezava v stalno vklopljeno navidezno zasebno omrežje"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Napaka stalno vklopljenega navideznega zasebnega omrežja"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Dotaknite se, če želite konfigurirati"</string>
     <string name="upload_file" msgid="2897957172366730416">"Izberi datoteko"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Nobena datoteka ni izbrana"</string>
     <string name="reset" msgid="2448168080964209908">"Ponastavi"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 2072c7e..de942ea 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Повезивање стално укљученог VPN-а..."</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Стално укључени VPN је повезан"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Грешка стално укљученог VPN-а"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Додирните да бисте конфигурисали"</string>
     <string name="upload_file" msgid="2897957172366730416">"Одабери датотеку"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Није изабрана ниједна датотека"</string>
     <string name="reset" msgid="2448168080964209908">"Поново постави"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 1b4cba5..adfc22a 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Ansluter till Always-on VPN ..."</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Ansluten till Always-on VPN"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Fel på Always-on VPN"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Tryck om du vill konfigurera"</string>
     <string name="upload_file" msgid="2897957172366730416">"Välj fil"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil har valts"</string>
     <string name="reset" msgid="2448168080964209908">"Återställ"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index a678e1f..f30d11f 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Kila mara VPN iliyowashwa inaunganishwa…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Kila mara VPN iliyowashwa imeunganishwa"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Kila mara kuna hitilafu ya VPN iliyowashwa"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Gusa ili kusanidi"</string>
     <string name="upload_file" msgid="2897957172366730416">"Chagua faili"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Hakuna faili iliyochaguliwa"</string>
     <string name="reset" msgid="2448168080964209908">"Weka upya"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 01a9fec..1a6d610 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"กำลังเชื่อมต่อ VPN แบบเปิดตลอดเวลา…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"เชื่อมต่อ VPN แบบเปิดตลอดเวลาแล้ว"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"ข้อผิดพลาดของ VPN แบบเปิดตลอดเวลา"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"แตะเพื่อกำหนดค่า"</string>
     <string name="upload_file" msgid="2897957172366730416">"เลือกไฟล์"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"ไม่ได้เลือกไฟล์ไว้"</string>
     <string name="reset" msgid="2448168080964209908">"รีเซ็ต"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 4de813a..1354129 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Kumukonekta ang Always-on VPN…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Nakakonekta ang Always-on VPN"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Error sa Always-on VPN"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Pindutin upang i-configure"</string>
     <string name="upload_file" msgid="2897957172366730416">"Pumili ng file"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Walang napiling file"</string>
     <string name="reset" msgid="2448168080964209908">"I-reset"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index cfc3c7b..aa3244c 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Her zaman açık VPN\'ye bağlanılıyor…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Her zaman açık VPN\'ye bağlanıldı"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Her zaman açık VPN hatası"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Yapılandırmak için dokunun"</string>
     <string name="upload_file" msgid="2897957172366730416">"Dosya seç"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Seçili dosya yok"</string>
     <string name="reset" msgid="2448168080964209908">"Sıfırla"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 095c364..2f6d65c 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Під’єднання до постійної мережі VPN…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Під’єднано до постійної мережі VPN"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Помилка постійної мережі VPN"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Натисніть, щоб налаштувати"</string>
     <string name="upload_file" msgid="2897957172366730416">"Виберіть файл"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Не вибрано файл"</string>
     <string name="reset" msgid="2448168080964209908">"Віднов."</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 2918928..df86597 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Đang kết nối VPN luôn bật…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Đã kết nối VPN luôn bật"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Lỗi VPN luôn bật"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Chạm để định cấu hình"</string>
     <string name="upload_file" msgid="2897957172366730416">"Chọn tệp"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Không có tệp nào được chọn"</string>
     <string name="reset" msgid="2448168080964209908">"Đặt lại"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index dcfc35f..554c1aa 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"正在连接到始终开启的 VPN…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"已连接到始终开启的 VPN"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"始终开启的 VPN 出现错误"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"触摸即可进行配置"</string>
     <string name="upload_file" msgid="2897957172366730416">"选择文件"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"未选定任何文件"</string>
     <string name="reset" msgid="2448168080964209908">"重置"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 2ceb440..c13cdf7 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"正在連線至永久連線的 VPN…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"已連線至永久連線的 VPN"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"永久連線的 VPN 發生錯誤"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"輕觸即可設定"</string>
     <string name="upload_file" msgid="2897957172366730416">"選擇檔案"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"未選擇任何檔案"</string>
     <string name="reset" msgid="2448168080964209908">"重設"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 01cadb7..6dc9e68 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -1269,8 +1269,7 @@
     <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"I-VPN ehlala ikhanya iyaxhuma…"</string>
     <string name="vpn_lockdown_connected" msgid="8202679674819213931">"I-VPN ehlala ikhanya ixhunyiwe"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"Iphutha le-VPN ehlala ikhanya"</string>
-    <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
-    <skip />
+    <string name="vpn_lockdown_config" msgid="6415899150671537970">"Thinta ukuze ulungiselele"</string>
     <string name="upload_file" msgid="2897957172366730416">"Khetha ifayela"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"Ayikho ifayela ekhethiwe"</string>
     <string name="reset" msgid="2448168080964209908">"Setha kabusha"</string>
diff --git a/docs/html/training/articles/security-ssl.jd b/docs/html/training/articles/security-ssl.jd
new file mode 100644
index 0000000..9a6320b
--- /dev/null
+++ b/docs/html/training/articles/security-ssl.jd
@@ -0,0 +1,539 @@
+page.title=Security with HTTPS and SSL
+page.article=true
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+<h2>In this document</h2>
+<ol class="nolist">
+  <li><a href="#Concepts">Concepts</a></li>
+  <li><a href="#HttpsExample">An HTTP Example</a></li>
+  <li><a href="#CommonProblems">Common Problems Verifying Server Certificates</a>
+    <ol class="nolist">
+      <li><a href="#UnknownCa">Unknown certificate authority</a></li>
+      <li><a href="#SelfSigned">Self-signed server certificate</a></li>
+      <li><a href="#MissingCa">Missing intermediate certificate authority</a></li>
+    </ol>
+  </li>
+  <li><a href="#CommonHostnameProbs">Common Problems with Hostname Verification</a></li>
+  <li><a href="#WarningsSslSocket">Warnings About Using SSLSocket Directly</a></li>
+  <li><a href="#Blacklisting">Blacklisting</a></li>
+  <li><a href="#Pinning">Pinning</a></li>
+  <li><a href="#ClientCert">Client Certificates</a></li>
+</ol>
+
+
+<h2>See also</h2>
+<ul>
+<li><a href="http://source.android.com/tech/security/index.html">Android
+Security Overview</a></li>
+<li><a href="{@docRoot}guide/topics/security/permissions.html">Permissions</a></li>
+</ul>
+</div></div>
+
+
+
+<p>The Secure Sockets Layer (SSL)&mdash;now technically known as <a
+href="http://en.wikipedia.org/wiki/Transport_Layer_Security">Transport Layer Security
+(TLS)</a>&mdash;is a
+common building block for encrypted communications between clients and servers. It's possible that
+an application might use SSL incorrectly such that malicious entities may
+be able to intercept an app's data over the network. To help you ensure that this does not happen
+to your app, this article highlights the common pitfalls when using secure network protocols and addresses some larger concerns about using <a
+href="http://en.wikipedia.org/wiki/Public-key_infrastructure">Public-Key Infrastructure (PKI)</a>.
+
+
+<h2 id="Concepts">Concepts</h2>
+
+<p>In a typical SSL usage scenario, a server is configured with a certificate containing a
+public key as well as a matching private key. As part of the handshake between an SSL client
+and server, the server proves it has the private key by signing its certificate with <a
+href="http://en.wikipedia.org/wiki/Public-key_cryptography">public-key cryptography</a>.</p>
+
+<p>However, anyone can generate their own certificate and private key, so a simple handshake
+doesn't prove anything about the server other than that the server knows the private key that
+matches the public key of the certificate. One way to solve this problem is to have the client
+have a set of one or more certificates it trusts. If the certificate is not in the set, the
+server is not to be trusted.</p>
+
+<p>There are several downsides to this simple approach. Servers should be able to
+upgrade to stronger keys over time ("key rotation"), which replaces the public key in the
+certificate with a new one. Unfortunately, now the client app has to be updated due to what
+is essentially a server configuration change. This is especially problematic if the server
+is not under the app developer's control, for example if it is a third party web service. This
+approach also has issues if the app has to talk to arbitrary servers such as a web browser or
+email app.</p>
+
+<p>In order to address these downsides, servers are typically configured with certificates
+from well known issuers called <a
+href="http://en.wikipedia.org/wiki/Certificate_authority">Certificate Authorities (CAs)</a>.
+The host platform generally contains a list of well known CAs that it trusts.
+As of Android 4.2 (Jelly Bean), Android currently contains over 100 CAs that are updated
+in each release. Similar to a server, a CA has a certificate and a private key. When issuing
+a certificate for a server, the CA <a
+href="http://en.wikipedia.org/wiki/Digital_signature">signs</a>
+the server certificate using its private key. The
+client can then verify that the server has a certificate issued by a CA known to the platform.</p>
+
+<p>However, while solving some problems, using CAs introduces another. Because the CA issues
+certificates for many servers, you still need some way to make sure you are talking to the
+server you want. To address this, the certificate issued by the CA identifies the server
+either with a specific name such as <em>gmail.com</em> or a wildcarded set of
+hosts such as <em>*.google.com</em>. </p>
+
+<p>The following example will make these concepts a little more concrete. In the snippet below
+from a command line, the <a href="http://www.openssl.org/docs/apps/openssl.html">{@code openssl}</a>
+tool's {@code s_client} command looks at Wikipedia's server certificate information. It
+specifies port 443 because that is the default for <acronym title="Hypertext Transfer
+Protocol Secure">HTTPS</acronym>. The command sends
+the output of {@code openssl s_client} to {@code openssl x509}, which formats information
+about certificates according to the <a
+href="http://en.wikipedia.org/wiki/X.509">X.509 standard</a>. Specifically,
+the command asks for the subject, which contains the server name information,
+and the issuer, which identifies the CA.</p>
+
+<pre class="no-pretty-print">
+$ openssl s_client -connect wikipedia.org:443 | openssl x509 -noout -subject -issuer
+<b>subject=</b> /serialNumber=sOrr2rKpMVP70Z6E9BT5reY008SJEdYv/C=US/O=*.wikipedia.org/OU=GT03314600/OU=See www.rapidssl.com/resources/cps (c)11/OU=Domain Control Validated - RapidSSL(R)/<b>CN=*.wikipedia.org</b>
+<b>issuer=</b> /C=US/O=GeoTrust, Inc./CN=<b>RapidSSL CA</b>
+</pre>
+
+<p>You can see that the certificate was issued for servers matching <em>*.wikipedia.org</em> by
+the RapidSSL CA.</p>
+
+
+
+<h2 id="HttpsExample">An HTTPS Example</h2>
+
+<p>Assuming you have a web server with a
+certificate issued by a well known CA, you can make a secure request with code as
+simple this:</p>
+
+<pre>
+URL url = new URL("https://wikipedia.org");
+URLConnection urlConnection = url.openConnection();
+InputStream in = urlConnection.getInputStream();
+copyInputStreamToOutputStream(in, System.out);
+</pre>
+
+<p>Yes, it really can be that simple. If you want to tailor the HTTP request, you can cast to
+an {@link java.net.HttpURLConnection}. The Android documentation for
+{@link java.net.HttpURLConnection} has further examples about how to deal with request
+and response headers, posting content, managing cookies, using proxies, caching responses,
+and so on. But in terms of the details for verifying certificates and hostnames, the Android
+framework takes care of it for you through these APIs.
+This is where you want to be if at all possible. That said, below are some other considerations.</p>
+
+
+
+<h2 id="CommonProblems">Common Problems Verifying Server Certificates</h2>
+
+<p>Suppose instead of receiving the content from {@link java.net.URLConnection#getInputStream
+getInputStream()}, it throws an exception:</p>
+
+<pre class="no-pretty-print">
+javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
+        at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:374)
+        at libcore.net.http.HttpConnection.setupSecureSocket(HttpConnection.java:209)
+        at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:478)
+        at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:433)
+        at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290)
+        at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240)
+        at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282)
+        at libcore.net.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:177)
+        at libcore.net.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:271)
+</pre>
+
+<p>This can happen for several reasons, including:
+<ol>
+  <li><a href="#UnknownCa">The CA that issued the server certificate was unknown</a></li>
+  <li><a href="#SelfSigned">The server certificate wasn't signed by a CA, but was self signed</a></li>
+  <li><a href="#MissingCa">The server configuration is missing an intermediate CA</a></li>
+</ol>
+
+<p>The following sections discuss how to address these problems while keeping your
+connection to the server secure.
+
+
+
+<h3 id="UnknownCa">Unknown certificate authority</h3>
+
+<p>In this case, the {@link javax.net.ssl.SSLHandshakeException} occurs
+because you have a CA that isn't trusted by the system. It could be because
+you have a certificate from a new CA that isn't yet trusted by Android or your app is
+running on an older version without the CA. More often a CA is unknown because it isn't a
+public CA, but a private one issued by an organization such as a government, corporation,
+or education institution for their own use.</p>
+
+<p>Fortunately, you can teach {@link javax.net.ssl.HttpsURLConnection}
+to trust a specific set of CAs. The procedure
+can be a little convoluted, so below is an example that takes a specific CA from
+an {@link java.io.InputStream}, uses it to create a {@link java.security.KeyStore},
+which is then used to create and initialize a
+{@link javax.net.ssl.TrustManager}. A {@link javax.net.ssl.TrustManager} is what the system
+uses to validate certificates from the server
+and&mdash;by creating one from a {@link java.security.KeyStore} with one or more CAs&mdash;those
+will be the only CAs trusted by that {@link javax.net.ssl.TrustManager}.</p>
+
+<p>Given the new {@link javax.net.ssl.TrustManager},
+the example initializes a new {@link javax.net.ssl.SSLContext} which provides
+an {@link javax.net.ssl.SSLSocketFactory} you can use to override the default
+{@link javax.net.ssl.SSLSocketFactory} from
+{@link javax.net.ssl.HttpsURLConnection}. This way the
+connection will use your CAs for certificate validation.</p>
+
+<p>Here is the example in
+full using an organizational CA from the University of Washington:</p>
+
+<pre>
+// Load CAs from an InputStream
+// (could be from a resource or ByteArrayInputStream or ...)
+CertificateFactory cf = CertificateFactory.getInstance("X.509");
+// From https://www.washington.edu/itconnect/security/ca/load-der.crt
+InputStream caInput = new BufferedInputStream(new FileInputStream("load-der.crt"));
+Certificate ca;
+try {
+    ca = cf.generateCertificate(caInput);
+    System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
+} finally {
+    caInput.close();
+}
+
+// Create a KeyStore containing our trusted CAs
+String keyStoreType = KeyStore.getDefaultType();
+KeyStore keyStore = KeyStore.getInstance(keyStoreType);
+keyStore.load(null, null);
+keyStore.setCertificateEntry("ca", ca);
+
+// Create a TrustManager that trusts the CAs in our KeyStore
+String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
+TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
+tmf.init(keyStore);
+
+// Create an SSLContext that uses our TrustManager
+SSLContext context = SSLContext.getInstance("TLS");
+context.init(null, tmf.getTrustManagers(), null);
+
+// Tell the URLConnection to use a SocketFactory from our SSLContext
+URL url = new URL("https://certs.cac.washington.edu/CAtest/");
+HttpsURLConnection urlConnection =
+    (HttpsURLConnection)url.openConnection();
+urlConnection.setSSLSocketFactory(context.getSocketFactory());
+InputStream in = urlConnection.getInputStream();
+copyInputStreamToOutputStream(in, System.out);
+</pre>
+
+<p>With a custom {@link javax.net.ssl.TrustManager} that knows about your CAs,
+the system is able to validate
+that your server certificate come from a trusted issuer.</p>
+
+<p class="caution"><strong>Caution:</strong>
+Many web sites describe a poor alternative solution which is to install a
+{@link javax.net.ssl.TrustManager} that does nothing. If you do this you might as well not
+be encrypting your communication, because anyone can attack your users at a public Wi-Fi hotspot
+by using <acronym title="Domain Name System">DNS</acronym> tricks to send your users'
+traffic through a proxy of their own that pretends to be your server. The attacker can then
+record passwords and other personal data. This works because the attacker can generate a
+certificate and&mdash;without a {@link javax.net.ssl.TrustManager} that actually
+validates that the certificate comes from a trusted
+source&mdash;your app could be talking to anyone. So don't do this, not even temporarily. You can
+always make your app trust the issuer of the server's certificate, so just do it.</p>
+
+
+
+<h3 id="SelfSigned">Self-signed server certificate</h3>
+
+<p>The second case of {@link javax.net.ssl.SSLHandshakeException} is
+due to a self-signed certificate, which means the server is behaving as its own CA.
+This is similar to an unknown certificate authority, so you can use the
+same approach from the previous section.</p>
+
+<p>You can create yout own {@link javax.net.ssl.TrustManager},
+this time trusting the server certificate directly. This has all of the
+downsides discussed earlier of tying your app directly to a certificate, but can be done
+securely. However, you should be careful to make sure your self-signed certificate has a
+reasonably strong key. As of 2012, a 2048-bit RSA signature with an exponent of 65537 expiring
+yearly is acceptable. When rotating keys, you should check for <a
+href="http://csrc.nist.gov/groups/ST/key_mgmt/index.html">recommendations</a> from an
+authority (such as <a href="http://www.nist.gov/">NIST</a>) about what is acceptable.</p>
+
+
+
+<h3 id="MissingCa">Missing intermediate certificate authority</h3>
+
+<p>The third case of {@link javax.net.ssl.SSLHandshakeException}
+occurs due to a missing intermediate CA. Most public
+CAs don't sign server certificates directly. Instead, they use their main CA certificate,
+referred to as the root CA, to sign intermediate CAs. They do this so the root CA can be stored
+offline to reduce risk of compromise. However, operating systems like Android typically
+trust only root CAs directly, which leaves a short gap of trust between the server
+certificate&mdash;signed by the intermediate CA&mdash;and the certificate verifier,
+which knows the root CA. To solve
+this, the server doesn't send the client only it's certificate during the SSL handshake, but
+a chain of certificates from the server CA through any intermediates necessary to reach a
+trusted root CA.</p>
+
+<p>To see what this looks like in practice, here's the <em>mail.google.com</em> certificate
+chain as viewed by the <a href="http://www.openssl.org/docs/apps/openssl.html">{@code openssl}</a>
+{@code s_client} command:</p>
+
+<pre class="no-pretty-print">
+$ openssl s_client -connect mail.google.com:443
+---
+Certificate chain
+ 0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/CN=mail.google.com
+   i:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
+ 1 s:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
+   i:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority
+---
+</pre>
+
+
+<p>This shows that the server sends a certificate for <em>mail.google.com</em>
+issued by the <em>Thawte SGC</em> CA, which is an intermediate CA, and a second certificate
+for the <em>Thawte SGC</em> CA issued by a <em>Verisign</em> CA, which is the primary CA that's
+trusted by Android.</p>
+
+<p>However, it is not uncommon to configure a server to not include the necessary
+intermediate CA. For example, here is a server that can cause an error in Android browsers and
+exceptions in Android apps:</p>
+
+<pre class="no-pretty-print">
+$ openssl s_client -connect egov.uscis.gov:443
+---
+Certificate chain
+ 0 s:/C=US/ST=District Of Columbia/L=Washington/O=U.S. Department of Homeland Security/OU=United States Citizenship and Immigration Services/OU=Terms of use at www.verisign.com/rpa (c)05/CN=egov.uscis.gov
+   i:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)10/CN=VeriSign Class 3 International Server CA - G3
+---
+</pre>
+
+<p>What is interesting to note here is that visiting this server in most desktop browsers
+does not cause an error like a completely unknown CA or self-signed server certificate would
+cause. This is because most desktop browsers cache trusted intermediate CAs over time. Once
+a browser has visited and learned about an intermediate CA from one site, it won't
+need to have the intermediate CA included in the certificate chain the next time.</p>
+
+<p>Some sites do this intentionally for secondary web servers used to serve resources. For
+example, they might have their main HTML page served by a server with a full certificate
+chain, but have servers for resources such as images, CSS, or JavaScript not include the
+CA, presumably to save bandwidth. Unfortunately, sometimes these servers might be providing
+a web service you are trying to call from your Android app, which is not as forgiving.</p>
+
+<p>There are two approaches to solve this issue:</p>
+<ul>
+  <li>Configure the server to
+  include the intermediate CA in the server chain. Most CAs provide documentation on how to do
+  this for all common web servers. This is the only approach if you need the site to work with
+  default Android browsers at least through Android 4.2.</li>
+  <li>Or, treat the
+  intermediate CA like any other unknown CA, and create a {@link javax.net.ssl.TrustManager}
+  to trust it directly, as done in the previous two sections.</li>
+</ul>
+
+
+<h2 id="CommonHostnameProbs">Common Problems with Hostname Verification</h2>
+
+<p>As mentioned at the beginning of this article,
+there are two key parts to verifying an SSL connection. The first
+is to verify the certificate is from a trusted source, which was the focus of the previous
+section. The focus of this section is the second part: making sure the server you are
+talking to presents the right certificate. When it doesn't, you'll typically see an error
+like this:</p>
+
+<pre class="no-pretty-print">
+java.io.IOException: Hostname 'example.com' was not verified
+        at libcore.net.http.HttpConnection.verifySecureSocketHostname(HttpConnection.java:223)
+        at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:446)
+        at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290)
+        at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240)
+        at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282)
+        at libcore.net.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:177)
+        at libcore.net.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:271)
+</pre>
+
+
+<p>One reason this can happen is due to a server configuration error. The server is
+configured with a certificate that does not have a subject or subject alternative name fields
+that match the server you are trying to reach. It is possible to have one certificate be used
+with many different servers. For example, looking at the <em>google.com</em> certificate with
+<a href="http://www.openssl.org/docs/apps/openssl.html">{@code openssl}</a> {@code
+s_client -connect google.com:443 | openssl x509 -text} you can see that a subject
+that supports <em>*.google.com</em> but also subject alternative names for <em>*.youtube.com</em>,
+<em>*.android.com</em>, and others. The error occurs only when the server name you
+are connecting to isn't listed by the certificate as acceptable.</p>
+
+<p>Unfortunately this can happen for another reason as well: <a
+href="http://en.wikipedia.org/wiki/Virtual_hosting">virtual hosting</a>. When sharing a
+server for more than one hostname with HTTP, the web server can tell from the HTTP/1.1 request
+which target hostname the client is looking for. Unfortunately this is complicated with
+HTTPS, because the server has to know which certificate to return before it sees the HTTP
+request. To address this problem, newer versions of SSL, specifically TLSv.1.0 and later,
+support <a href="http://en.wikipedia.org/wiki/Server_Name_Indication">Server Name Indication
+(SNI)</a>, which allows the SSL client to specify the intended
+hostname to the server so the proper certificate can be returned.</p>
+
+<p>Fortunately, {@link javax.net.ssl.HttpsURLConnection} supports
+SNI since Android 2.3. Unfortunately, Apache
+HTTP Client does not, which is one of the many reasons we discourage its use. One workaround
+if you need to support Android 2.2 (and older) or Apache HTTP Client is to set up an alternative
+virtual host on a unique port so that it's unambiguous which server certificate to return.</p>
+
+<p>The more drastic alternative is to replace {@link javax.net.ssl.HostnameVerifier}
+with one that uses not the
+hostname of your virtual host, but the one returned by the server by default.</p>
+
+<p class="caution"><strong>Caution:</strong> Replacing {@link javax.net.ssl.HostnameVerifier}
+can be <strong>very dangerous</strong> if the other virtual host is
+not under your control, because a man-in-the-middle attack could direct traffic to another
+server without your knowledge.</p>
+
+<p>If you are still sure you want to override hostname verification, here is an example
+that replaces the verifier for a single {@link java.net.URLConnection}
+with one that still verifies that the hostname is at least on expected by the app:</p>
+
+<pre>
+// Create an HostnameVerifier that hardwires the expected hostname.
+// Note that is different than the URL's hostname:
+// example.com versus example.org
+HostnameVerifier hostnameVerifier = new HostnameVerifier() {
+    &#64;Override
+    public boolean verify(String hostname, SSLSession session) {
+        HostnameVerifier hv =
+            HttpsURLConnection.getDefaultHostnameVerifier();
+        return hv.verify("example.com", session);
+    }
+};
+
+// Tell the URLConnection to use our HostnameVerifier
+URL url = new URL("https://example.org/");
+HttpsURLConnection urlConnection =
+    (HttpsURLConnection)url.openConnection();
+urlConnection.setHostnameVerifier(hostnameVerifier);
+InputStream in = urlConnection.getInputStream();
+copyInputStreamToOutputStream(in, System.out);
+</pre>
+
+<p>But remember, if you find yourself replacing hostname verification, especially
+due to virtual hosting, it's still <strong>very dangerous</strong> if the other virtual host is
+not under your control and you should find an alternative hosting arrangement
+that avoids this issue.</p>
+
+
+
+
+<h2 id="WarningsSslSocket">Warnings About Using SSLSocket Directly</h2>
+
+<p>So far, the examples have focused on HTTPS using {@link javax.net.ssl.HttpsURLConnection}.
+Sometimes apps need to use SSL separate from HTTP. For example, an email app might use SSL variants
+of SMTP, POP3, or IMAP. In those cases, the app would want to use {@link javax.net.ssl.SSLSocket}
+directly, much the same way that {@link javax.net.ssl.HttpsURLConnection} does internally.</p>
+
+<p>The techniques described so
+far to deal with certificate verification issues also apply to {@link javax.net.ssl.SSLSocket}.
+In fact, when using a custom {@link javax.net.ssl.TrustManager}, what is passed to
+{@link javax.net.ssl.HttpsURLConnection} is an {@link javax.net.ssl.SSLSocketFactory}.
+So if you need to use a custom {@link javax.net.ssl.TrustManager} with an
+{@link javax.net.ssl.SSLSocket}, follow
+the same steps and use that {@link javax.net.ssl.SSLSocketFactory} to create your
+{@link javax.net.ssl.SSLSocket}.</p>
+
+<p class="caution"><strong>Caution:</strong>
+{@link javax.net.ssl.SSLSocket} <strong>does not</strong> perform hostname verification. It is
+up the your app to do its own hostname verification, preferably by calling {@link
+javax.net.ssl.HttpsURLConnection#getDefaultHostnameVerifier()} with the expected hostname. Further
+beware that {@link javax.net.ssl.HostnameVerifier#verify HostnameVerifier.verify()}
+doesn't throw an exception on error but instead returns a boolean result that you must
+explicitly check.</p>
+
+<p>Here is an example showing how you can do this. It shows that when connecting to
+<em>gmail.com</em> port 443 without SNI support, you'll receive a certificate for
+<em>mail.google.com</em>. This is expected in this case, so check to make sure that
+the certificate is indeed for <em>mail.google.com</em>:</p>
+
+<pre>
+// Open SSLSocket directly to gmail.com
+SocketFactory sf = SSLSocketFactory.getDefault();
+SSLSocket socket = (SSLSocket) sf.createSocket("gmail.com", 443);
+HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
+SSLSession s = socket.getSession();
+
+// Verify that the certicate hostname is for mail.google.com
+// This is due to lack of SNI support in the current SSLSocket.
+if (!hv.verify("mail.google.com", s)) {
+    throw new SSLHandshakeException("Expected mail.google.com, "
+                                    "found " + s.getPeerPrincipal());
+}
+
+// At this point SSLSocket performed certificate verificaiton and
+// we have performed hostname verification, so it is safe to proceed.
+
+// ... use socket ...
+socket.close();
+</pre>
+
+
+
+<h2 id="Blacklisting">Blacklisting</h2>
+
+<p>SSL relies heavily on CAs to issue certificates to only the properly verified owners
+of servers and domains. In rare cases, CAs are either tricked or, in the case of <a
+href="http://en.wikipedia.org/wiki/Comodo_Group#Breach_of_security">Comodo</a> or <a
+href="http://en.wikipedia.org/wiki/DigiNotar">DigiNotar</a>, breached,
+resulting in the certificates for a hostname to be issued to
+someone other than the owner of the server or domain.</p>
+
+<p>In order to mitigate this risk, Android has the ability to blacklist certain certificates or even
+whole CAs. While this list was historically built into the operating system, starting in
+Android 4.2 this list can be remotely updated to deal with future compromises.</p>
+
+
+
+<h2 id="Pinning">Pinning</h2>
+
+<p>An app can further protect itself from fraudulently issued certificates by a
+technique known as pinning. This is basically using the example provided in the unknown CA case
+above to restrict an app's trusted CAs to a small set known to be used by the app's servers. This
+prevents the compromise of one of the other 100+ CAs in the system from resulting in a breach of
+the apps secure channel.</p>
+
+
+
+<h2 id="ClientCert">Client Certificates</h2>
+
+<p>This article has focused on the user of SSL to secure communications with servers. SSL also
+supports the notion of client certificates that allow the server to validate the identity of a
+client. While beyond the scope of this article, the techniques involved are similar to specifying
+a custom {@link javax.net.ssl.TrustManager}.
+See the discussion about creating a custom {@link javax.net.ssl.KeyManager} in the documentation for
+{@link javax.net.ssl.HttpsURLConnection}.</p>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs
index 9518046..79980be 100644
--- a/docs/html/training/training_toc.cs
+++ b/docs/html/training/training_toc.cs
@@ -1069,6 +1069,13 @@
            "How to perform various tasks and keep your app's data and your user's data secure."
           >Security Tips</a>
       </li>
+
+      <li>
+        <a href="<?cs var:toroot ?>training/articles/security-ssl.html"
+           description=
+           "How to ensure that your app is secure when performing network transactions."
+          >Security with HTTPS and SSL</a>
+      </li>
       
       <li class="nav-section">
         <div class="nav-section-header">
diff --git a/graphics/java/android/graphics/drawable/ShapeDrawable.java b/graphics/java/android/graphics/drawable/ShapeDrawable.java
index 2ec1293..1dbcddb 100644
--- a/graphics/java/android/graphics/drawable/ShapeDrawable.java
+++ b/graphics/java/android/graphics/drawable/ShapeDrawable.java
@@ -217,16 +217,19 @@
         int prevAlpha = paint.getAlpha();
         paint.setAlpha(modulateAlpha(prevAlpha, mShapeState.mAlpha));
 
-        if (mShapeState.mShape != null) {
-            // need the save both for the translate, and for the (unknown) Shape
-            int count = canvas.save();
-            canvas.translate(r.left, r.top);
-            onDraw(mShapeState.mShape, canvas, paint);
-            canvas.restoreToCount(count);
-        } else {
-            canvas.drawRect(r, paint);
+        // only draw shape if it may affect output
+        if (paint.getAlpha() != 0 || paint.getXfermode() != null || paint.hasShadow) {
+            if (mShapeState.mShape != null) {
+                // need the save both for the translate, and for the (unknown) Shape
+                int count = canvas.save();
+                canvas.translate(r.left, r.top);
+                onDraw(mShapeState.mShape, canvas, paint);
+                canvas.restoreToCount(count);
+            } else {
+                canvas.drawRect(r, paint);
+            }
         }
-        
+
         // restore
         paint.setAlpha(prevAlpha);
     }
diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java
index a99cdad..b8564b6 100644
--- a/graphics/java/android/renderscript/Allocation.java
+++ b/graphics/java/android/renderscript/Allocation.java
@@ -26,6 +26,7 @@
 import android.graphics.SurfaceTexture;
 import android.util.Log;
 import android.util.TypedValue;
+import android.graphics.Canvas;
 
 /**
  * <p>
@@ -429,6 +430,9 @@
 
     private void validateBitmapFormat(Bitmap b) {
         Bitmap.Config bc = b.getConfig();
+        if (bc == null) {
+            throw new RSIllegalArgumentException("Bitmap has an unsupported format for this operation");
+        }
         switch (bc) {
         case ALPHA_8:
             if (mType.getElement().mKind != Element.DataKind.PIXEL_A) {
@@ -612,6 +616,13 @@
      */
     public void copyFrom(Bitmap b) {
         mRS.validate();
+        if (b.getConfig() == null) {
+            Bitmap newBitmap = Bitmap.createBitmap(b.getWidth(), b.getHeight(), Bitmap.Config.ARGB_8888);
+            Canvas c = new Canvas(newBitmap);
+            c.drawBitmap(b, 0, 0, null);
+            copyFrom(newBitmap);
+            return;
+        }
         validateBitmapSize(b);
         validateBitmapFormat(b);
         mRS.nAllocationCopyFromBitmap(getID(mRS), b);
@@ -951,6 +962,12 @@
      */
     public void copy2DRangeFrom(int xoff, int yoff, Bitmap data) {
         mRS.validate();
+        if (data.getConfig() == null) {
+            Bitmap newBitmap = Bitmap.createBitmap(data.getWidth(), data.getHeight(), Bitmap.Config.ARGB_8888);
+            Canvas c = new Canvas(newBitmap);
+            c.drawBitmap(data, 0, 0, null);
+            copy2DRangeFrom(xoff, yoff, newBitmap);
+        }
         validateBitmapFormat(data);
         validate2DRange(xoff, yoff, data.getWidth(), data.getHeight());
         mRS.nAllocationData2D(getIDSafe(), xoff, yoff, mSelectedLOD, mSelectedFace.mID, data);
@@ -1220,6 +1237,18 @@
                                               MipmapControl mips,
                                               int usage) {
         rs.validate();
+
+        // WAR undocumented color formats
+        if (b.getConfig() == null) {
+            if ((usage & USAGE_SHARED) != 0) {
+                throw new RSIllegalArgumentException("USAGE_SHARED cannot be used with a Bitmap that has a null config.");
+            }
+            Bitmap newBitmap = Bitmap.createBitmap(b.getWidth(), b.getHeight(), Bitmap.Config.ARGB_8888);
+            Canvas c = new Canvas(newBitmap);
+            c.drawBitmap(b, 0, 0, null);
+            return createFromBitmap(rs, newBitmap, mips, usage);
+        }
+
         Type t = typeFromBitmap(rs, b, mips);
 
         // enable optimized bitmap path only with no mipmap and script-only usage
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 9db8fe8..9ecfb5a 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -1140,9 +1140,20 @@
             const float* positions, SkPaint* paint, float length)
             : DrawBoundedOp(paint), mText(text), mBytesCount(bytesCount), mCount(count),
             mX(x), mY(y), mPositions(positions), mLength(length) {
+        // duplicates bounds calculation from OpenGLRenderer::drawText, but doesn't alter mX
         SkPaint::FontMetrics metrics;
         paint->getFontMetrics(&metrics, 0.0f);
-        mLocalBounds.set(mX, mY + metrics.fTop, mX + length, mY + metrics.fBottom);
+        switch (paint->getTextAlign()) {
+        case SkPaint::kCenter_Align:
+            x -= length / 2.0f;
+            break;
+        case SkPaint::kRight_Align:
+            x -= length;
+            break;
+        default:
+            break;
+        }
+        mLocalBounds.set(x, mY + metrics.fTop, x + length, mY + metrics.fBottom);
     }
 
     virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 710f12f..16218fa 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -406,7 +406,7 @@
     if (addDrawOp(op)) {
         // precache if draw operation is visible
         FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
-        fontRenderer.precache(paint, text, count, *mSnapshot->transform);
+        fontRenderer.precache(paint, text, count, mat4::identity());
     }
     return DrawGlInfo::kStatusDone;
 }
@@ -423,7 +423,7 @@
     if (addDrawOp(op)) {
         // precache if draw operation is visible
         FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
-        fontRenderer.precache(paint, text, count, *mSnapshot->transform);
+        fontRenderer.precache(paint, text, count, mat4::identity());
     }
     return DrawGlInfo::kStatusDone;
 }
@@ -442,7 +442,9 @@
     if (addDrawOp(op)) {
         // precache if draw operation is visible
         FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
-        fontRenderer.precache(paint, text, count, *mSnapshot->transform);
+        const bool pureTranslate = mSnapshot->transform->isPureTranslate();
+        fontRenderer.precache(paint, text, count,
+                pureTranslate ? mat4::identity() : *mSnapshot->transform);
     }
     return DrawGlInfo::kStatusDone;
 }
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index db65b88..d5ea0f9 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -180,7 +180,17 @@
 void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
         uint32_t* retOriginX, uint32_t* retOriginY, bool precaching) {
     checkInit();
+
+    // If the glyph bitmap is empty let's assum the glyph is valid
+    // so we can avoid doing extra work later on
+    if (glyph.fWidth == 0 || glyph.fHeight == 0) {
+        cachedGlyph->mIsValid = true;
+        cachedGlyph->mCacheTexture = NULL;
+        return;
+    }
+
     cachedGlyph->mIsValid = false;
+
     // If the glyph is too tall, don't cache it
     if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 >
                 mCacheTextures[mCacheTextures.size() - 1]->getHeight()) {
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index fb77ef6..7e9734f 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -2151,17 +2151,17 @@
 
     alpha *= mSnapshot->alpha;
 
-    mCaches.activeTexture(0);
-    Texture* texture = mCaches.textureCache.get(bitmap);
-    if (!texture) return DrawGlInfo::kStatusDone;
-    const AutoTexture autoCleanup(texture);
-    texture->setWrap(GL_CLAMP_TO_EDGE, true);
-    texture->setFilter(GL_LINEAR, true);
-
     const Patch* mesh = mCaches.patchCache.get(bitmap->width(), bitmap->height(),
             right - left, bottom - top, xDivs, yDivs, colors, width, height, numColors);
 
     if (CC_LIKELY(mesh && mesh->verticesCount > 0)) {
+        mCaches.activeTexture(0);
+        Texture* texture = mCaches.textureCache.get(bitmap);
+        if (!texture) return DrawGlInfo::kStatusDone;
+        const AutoTexture autoCleanup(texture);
+        texture->setWrap(GL_CLAMP_TO_EDGE, true);
+        texture->setFilter(GL_LINEAR, true);
+
         const bool pureTranslate = mSnapshot->transform->isPureTranslate();
         // Mark the current layer dirty where we are going to draw the patch
         if (hasLayer() && mesh->hasEmptyQuads) {
@@ -2666,6 +2666,7 @@
     const float oldX = x;
     const float oldY = y;
     const bool pureTranslate = mSnapshot->transform->isPureTranslate();
+    const bool isPerspective = mSnapshot->transform->isPerspective();
 
     if (CC_LIKELY(pureTranslate)) {
         x = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f);
@@ -2687,8 +2688,7 @@
     fontRenderer.setFont(paint, pureTranslate ? mat4::identity() : *mSnapshot->transform);
 
     // Pick the appropriate texture filtering
-    bool linearFilter = !mSnapshot->transform->isPureTranslate() ||
-            fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
+    bool linearFilter = !pureTranslate || fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
 
     // The font renderer will always use texture unit 0
     mCaches.activeTexture(0);
@@ -2701,13 +2701,13 @@
     setupDrawShader();
     setupDrawBlending(true, mode);
     setupDrawProgram();
-    setupDrawModelView(x, y, x, y, true, true);
+    setupDrawModelView(x, y, x, y, !isPerspective, true);
     // See comment above; the font renderer must use texture unit 0
     // assert(mTextureUnit == 0)
     setupDrawTexture(fontRenderer.getTexture(linearFilter));
     setupDrawPureColorUniforms();
     setupDrawColorFilterUniforms();
-    setupDrawShaderUniforms(true);
+    setupDrawShaderUniforms(!isPerspective);
     setupDrawTextGammaUniforms();
 
     const Rect* clip = mSnapshot->hasPerspectiveTransform() ? NULL : mSnapshot->clipRect;
@@ -2727,6 +2727,9 @@
     }
 
     if (status && hasActiveLayer) {
+        if (isPerspective) {
+            mSnapshot->transform->mapRect(bounds);
+        }
         dirtyLayerUnchecked(bounds, getRegion());
     }
 
diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h
index 13ee336..e8b6d47 100644
--- a/libs/hwui/Program.h
+++ b/libs/hwui/Program.h
@@ -24,6 +24,7 @@
 
 #include <SkXfermode.h>
 
+#include "Debug.h"
 #include "Matrix.h"
 #include "Properties.h"
 
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index 0f014cb..f78fb2d 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -433,6 +433,12 @@
 
 Program* ProgramCache::get(const ProgramDescription& description) {
     programid key = description.key();
+    if (key == (PROGRAM_KEY_TEXTURE | PROGRAM_KEY_A8_TEXTURE)) {
+        // program for A8, unmodulated, texture w/o shader (black text/path textures) is equivalent
+        // to standard texture program (bitmaps, patches). Consider them equivalent.
+        key = PROGRAM_KEY_TEXTURE;
+    }
+
     ssize_t index = mCache.indexOfKey(key);
     Program* program = NULL;
     if (index < 0) {
diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h
index 6cfe0c7..1ca148d 100644
--- a/libs/hwui/ProgramCache.h
+++ b/libs/hwui/ProgramCache.h
@@ -31,17 +31,6 @@
 namespace uirenderer {
 
 ///////////////////////////////////////////////////////////////////////////////
-// Defines
-///////////////////////////////////////////////////////////////////////////////
-
-// Debug
-#if DEBUG_PROGRAMS
-    #define PROGRAM_LOGD(...) ALOGD(__VA_ARGS__)
-#else
-    #define PROGRAM_LOGD(...)
-#endif
-
-///////////////////////////////////////////////////////////////////////////////
 // Cache
 ///////////////////////////////////////////////////////////////////////////////
 
diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp
index ea9fd03..d48b612 100644
--- a/libs/hwui/font/Font.cpp
+++ b/libs/hwui/font/Font.cpp
@@ -52,6 +52,7 @@
     mStyle = paint->getStyle();
     mStrokeWidth = paint->getStrokeWidth();
     mAntiAliasing = paint->isAntiAlias();
+    mLookupTransform.reset();
     mLookupTransform[SkMatrix::kMScaleX] = matrix.data[mat4::kScaleX];
     mLookupTransform[SkMatrix::kMScaleY] = matrix.data[mat4::kScaleY];
     mLookupTransform[SkMatrix::kMSkewX] = matrix.data[mat4::kSkewX];
@@ -165,7 +166,7 @@
 void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y,
         uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
     float nPenX = x + glyph->mBitmapLeft;
-    float nPenY = y + (glyph->mBitmapTop + glyph->mBitmapHeight);
+    float nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight;
 
     float width = (float) glyph->mBitmapWidth;
     float height = (float) glyph->mBitmapHeight;
@@ -181,6 +182,38 @@
             nPenX, nPenY - height, u1, v1, glyph->mCacheTexture);
 }
 
+void Font::drawCachedGlyphPerspective(CachedGlyphInfo* glyph, int x, int y,
+        uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
+    SkMatrix i;
+    if (!mDescription.mLookupTransform.invert(&i)) {
+        return;
+    }
+
+    SkPoint p[4];
+    p[0].set(glyph->mBitmapLeft, glyph->mBitmapTop + glyph->mBitmapHeight);
+    p[1].set(glyph->mBitmapLeft + glyph->mBitmapWidth, glyph->mBitmapTop + glyph->mBitmapHeight);
+    p[2].set(glyph->mBitmapLeft + glyph->mBitmapWidth, glyph->mBitmapTop);
+    p[3].set(glyph->mBitmapLeft, glyph->mBitmapTop);
+
+    i.mapPoints(p, 4);
+
+    p[0].offset(x, y);
+    p[1].offset(x, y);
+    p[2].offset(x, y);
+    p[3].offset(x, y);
+
+    float u1 = glyph->mBitmapMinU;
+    float u2 = glyph->mBitmapMaxU;
+    float v1 = glyph->mBitmapMinV;
+    float v2 = glyph->mBitmapMaxV;
+
+    mState->appendRotatedMeshQuad(
+            p[0].fX, p[0].fY, u1, v2,
+            p[1].fX, p[1].fY, u2, v2,
+            p[2].fX, p[2].fY, u2, v1,
+            p[3].fX, p[3].fY, u1, v1, glyph->mCacheTexture);
+}
+
 void Font::drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y,
         uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
     int nPenX = x + glyph->mBitmapLeft;
@@ -307,7 +340,7 @@
         penX += SkFixedToFloat(AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta));
         prevRsbDelta = cachedGlyph->mRsbDelta;
 
-        if (cachedGlyph->mIsValid) {
+        if (cachedGlyph->mIsValid && cachedGlyph->mCacheTexture) {
             drawCachedGlyph(cachedGlyph, penX, hOffset, vOffset, measure, &position, &tangent);
         }
 
@@ -328,7 +361,6 @@
 }
 
 void Font::precache(SkPaint* paint, const char* text, int numGlyphs) {
-
     if (numGlyphs == 0 || text == NULL) {
         return;
     }
@@ -357,14 +389,18 @@
 
     static RenderGlyph gRenderGlyph[] = {
             &android::uirenderer::Font::drawCachedGlyph,
+            &android::uirenderer::Font::drawCachedGlyphPerspective,
             &android::uirenderer::Font::drawCachedGlyphBitmap,
+            &android::uirenderer::Font::drawCachedGlyphBitmap,
+            &android::uirenderer::Font::measureCachedGlyph,
             &android::uirenderer::Font::measureCachedGlyph
     };
-    RenderGlyph render = gRenderGlyph[mode];
+    RenderGlyph render = gRenderGlyph[(mode << 1) + mTransform.isPerspective()];
 
     text += start;
     int glyphsCount = 0;
 
+    const bool applyTransform = !mTransform.isIdentity() && !mTransform.isPerspective();
     const SkPaint::Align align = paint->getTextAlign();
 
     while (glyphsCount < numGlyphs) {
@@ -377,12 +413,13 @@
 
         CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph);
 
-        // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
-        if (cachedGlyph->mIsValid) {
+        // If it's still not valid, we couldn't cache it, so we shouldn't
+        // draw garbage; also skip empty glyphs (spaces)
+        if (cachedGlyph->mIsValid && cachedGlyph->mCacheTexture) {
             float penX = x + positions[(glyphsCount << 1)];
             float penY = y + positions[(glyphsCount << 1) + 1];
 
-            if (!mTransform.isIdentity()) {
+            if (applyTransform) {
                 mTransform.mapPoint(penX, penY);
             }
 
@@ -424,15 +461,18 @@
     glyph->mBitmapWidth = skiaGlyph.fWidth;
     glyph->mBitmapHeight = skiaGlyph.fHeight;
 
-    uint32_t cacheWidth = glyph->mCacheTexture->getWidth();
-    uint32_t cacheHeight = glyph->mCacheTexture->getHeight();
+    bool empty = skiaGlyph.fWidth == 0 || skiaGlyph.fHeight == 0;
+    if (!empty) {
+        uint32_t cacheWidth = glyph->mCacheTexture->getWidth();
+        uint32_t cacheHeight = glyph->mCacheTexture->getHeight();
 
-    glyph->mBitmapMinU = startX / (float) cacheWidth;
-    glyph->mBitmapMinV = startY / (float) cacheHeight;
-    glyph->mBitmapMaxU = endX / (float) cacheWidth;
-    glyph->mBitmapMaxV = endY / (float) cacheHeight;
+        glyph->mBitmapMinU = startX / (float) cacheWidth;
+        glyph->mBitmapMinV = startY / (float) cacheHeight;
+        glyph->mBitmapMaxU = endX / (float) cacheWidth;
+        glyph->mBitmapMaxV = endY / (float) cacheHeight;
 
-    mState->setTextureDirty();
+        mState->setTextureDirty();
+    }
 }
 
 CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, glyph_t glyph, bool precaching) {
@@ -440,8 +480,8 @@
     mCachedGlyphs.add(glyph, newGlyph);
 
     const SkGlyph& skiaGlyph = GET_METRICS(paint, glyph, &mDescription.mLookupTransform);
-    newGlyph->mGlyphIndex = skiaGlyph.fID;
     newGlyph->mIsValid = false;
+    newGlyph->mGlyphIndex = skiaGlyph.fID;
 
     updateGlyphCache(paint, skiaGlyph, newGlyph, precaching);
 
@@ -452,14 +492,13 @@
     FontDescription description(paint, matrix);
     Font* font = state->mActiveFonts.get(description);
 
-    if (font) {
-        font->mTransform.load(matrix);
-        return font;
+    if (!font) {
+        font = new Font(state, description);
+        state->mActiveFonts.put(description, font);
     }
+    font->mTransform.load(matrix);
 
-    Font* newFont = new Font(state, description);
-    state->mActiveFonts.put(description, newFont);
-    return newFont;
+    return font;
 }
 
 }; // namespace uirenderer
diff --git a/libs/hwui/font/Font.h b/libs/hwui/font/Font.h
index 26b10afd..542b552 100644
--- a/libs/hwui/font/Font.h
+++ b/libs/hwui/font/Font.h
@@ -124,6 +124,9 @@
     void drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y,
             uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
             Rect* bounds, const float* pos);
+    void drawCachedGlyphPerspective(CachedGlyphInfo* glyph, int x, int y,
+            uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
+            Rect* bounds, const float* pos);
     void drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y,
             uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
             Rect* bounds, const float* pos);
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index e1d9b73..5ad305c 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -33,6 +33,7 @@
 import com.android.internal.view.menu.MenuView;
 import com.android.internal.widget.ActionBarContainer;
 import com.android.internal.widget.ActionBarContextView;
+import com.android.internal.widget.ActionBarOverlayLayout;
 import com.android.internal.widget.ActionBarView;
 
 import android.app.KeyguardManager;
@@ -2788,11 +2789,7 @@
                         com.android.internal.R.attr.dialogTitleDecorLayout, res, true);
                 layoutResource = res.resourceId;
             } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
-                if ((features & (1 << FEATURE_ACTION_BAR_OVERLAY)) != 0) {
-                    layoutResource = com.android.internal.R.layout.screen_action_bar_overlay;
-                } else {
-                    layoutResource = com.android.internal.R.layout.screen_action_bar;
-                }
+                layoutResource = com.android.internal.R.layout.screen_action_bar;
             } else {
                 layoutResource = com.android.internal.R.layout.screen_title;
             }
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 5e4855b..3257d2c 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -3382,6 +3382,7 @@
         try {
             if (tracker != null) {
                 mNetd.setFirewallEnabled(true);
+                mNetd.setFirewallInterfaceRule("lo", true);
                 mLockdownTracker = tracker;
                 mLockdownTracker.init();
             } else {
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index 5630b08..7686705 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -23,6 +23,7 @@
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
 import static android.net.TrafficStats.UID_TETHERING;
+import static com.android.server.NetworkManagementService.NetdResponseCode.ClatdStatusResult;
 import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceGetCfgResult;
 import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceListResult;
 import static com.android.server.NetworkManagementService.NetdResponseCode.IpFwdStatusResult;
@@ -122,6 +123,7 @@
         public static final int QuotaCounterResult        = 220;
         public static final int TetheringStatsResult      = 221;
         public static final int DnsProxyQueryResult       = 222;
+        public static final int ClatdStatusResult         = 223;
 
         public static final int InterfaceChange           = 600;
         public static final int BandwidthControl          = 601;
@@ -1498,6 +1500,43 @@
         }
     }
 
+    @Override
+    public void startClatd(String interfaceName) throws IllegalStateException {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        try {
+            mConnector.execute("clatd", "start", interfaceName);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void stopClatd() throws IllegalStateException {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        try {
+            mConnector.execute("clatd", "stop");
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public boolean isClatdStarted() {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        final NativeDaemonEvent event;
+        try {
+            event = mConnector.execute("clatd", "status");
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+
+        event.checkCode(ClatdStatusResult);
+        return event.getMessage().endsWith("started");
+    }
+
     /** {@inheritDoc} */
     @Override
     public void monitor() {
diff --git a/tests/FrameworkPerf/src/com/android/frameworkperf/FrameworkPerfActivity.java b/tests/FrameworkPerf/src/com/android/frameworkperf/FrameworkPerfActivity.java
index 30a968f..6633787 100644
--- a/tests/FrameworkPerf/src/com/android/frameworkperf/FrameworkPerfActivity.java
+++ b/tests/FrameworkPerf/src/com/android/frameworkperf/FrameworkPerfActivity.java
@@ -272,7 +272,7 @@
                 args.bgOp = mCurOpIndex;
             } else {
                 args.fgOp = mCurOpIndex;
-                args.bgOp = mFgTestIndex;
+                args.bgOp = mBgTestIndex;
             }
         }
         Bundle bundle = new Bundle();
@@ -424,6 +424,8 @@
             updateWakeLock();
             stopService(new Intent(this, SchedulerService.class));
             synchronized (mResults) {
+                Log.i("PerfRes", "\tTEST\tFgOps\tFgMsPerOp\tFgTime\tFgName\tBgOps\tBgMsPerOp\t"
+                        + "BgTime\tBgName");
                 for (int i=0; i<mResults.size(); i++) {
                     RunResult result = mResults.get(i);
                     float fgMsPerOp = result.getFgMsPerOp();
diff --git a/tests/FrameworkPerf/src/com/android/frameworkperf/TestService.java b/tests/FrameworkPerf/src/com/android/frameworkperf/TestService.java
index a8c43e9..5f4f006 100644
--- a/tests/FrameworkPerf/src/com/android/frameworkperf/TestService.java
+++ b/tests/FrameworkPerf/src/com/android/frameworkperf/TestService.java
@@ -300,7 +300,7 @@
                     threadFinished(false);
                 }
             }, Process.THREAD_PRIORITY_BACKGROUND);
-            mForegroundThread = new RunnerThread("background", new Runnable() {
+            mForegroundThread = new RunnerThread("foreground", new Runnable() {
                 @Override public void run() {
                     boolean running;
                     int ops = 0;
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 7c7d10e..46a539e 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -30,448 +30,466 @@
         android:label="HwUi"
         android:hardwareAccelerated="true">
 
-        <meta-data android:name="android.graphics.renderThread" android:value="true" />
+        <activity
+                android:name="HwTests"
+                android:label="OpenGL Renderer Tests">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
 
         <activity
                 android:name="ScaledTextActivity"
-                android:label="_ScaledText"
+                android:label="Text/Scaled"
                 android:theme="@android:style/Theme.Holo.Light">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
+            </intent-filter>
+        </activity>
+
+        <activity
+                android:name="Rotate3dTextActivity"
+                android:label="Text/3D Rotation"
+                android:theme="@android:style/Theme.Holo.Light">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="NoAATextActivity"
-                android:label="_NoAAText">
+                android:label="Text/Aliased">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="ScaledPathsActivity"
-                android:label="_ScaledPaths">
+                android:label="Path/Scaled">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="Alpha8BitmapActivity"
-                android:label="_Alpha8Bitmap">
+                android:label="Bitmaps/Alpha8">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="MipMapActivity"
-                android:label="_MipMap">
+                android:label="Bitmaps/MipMap">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="PathOffsetActivity"
-                android:label="_PathOffset">
+                android:label="Path/Offset">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="MultiLayersActivity"
-                android:label="_MultiLayers">
+                android:label="Layers/Multiple">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="TJunctionActivity"
-                android:label="_T-Junction">
+                android:label="Layers/T-Junction">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="TextPathActivity"
-                android:label="_TextPath">
+                android:label="Text/As Path">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="GradientStopsActivity"
-                android:label="_GradientStops">
+                android:label="Gradients/Multi-stops">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="PaintDrawFilterActivity"
-                android:label="_DrawFilter">
+                android:label="Paint/Draw Filter">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="BigGradientActivity"
-                android:label="_BigGradient">
+                android:label="Gradients/Large">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="DatePickerActivity"
-                android:label="_DatePicker">
+                android:label="View/DatePicker">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="ClipRegionActivity"
-                android:label="_ClipRegion">
+                android:label="Clip/Region 1">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="ClipRegion2Activity"
-                android:label="_ClipRegion2">
+                android:label="Clip/Region 2">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="ClipRegion3Activity"
-                android:label="_ClipRegion3">
+                android:label="Clip/Region 3">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="DisplayListLayersActivity"
-                android:label="__DisplayListLayers">
+                android:label="Layers/Display Lists">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="MatrixActivity"
-                android:label="_Matrix">
+                android:label="Misc/Matrix">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="TextFadeActivity"
-                android:label="_TextFade">
+                android:label="Text/Fade">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="MaxBitmapSizeActivity"
-                android:label="_MaxBitmapSize">
+                android:label="Bitmaps/Max Size">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="TimeDialogActivity"
-                android:label="_TimeDialog">
+                android:label="View/TimeDialog">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="OpaqueActivity"
-                android:label="_Opaque">
+                android:label="Window/Opaque">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="GetBitmapActivity"
-                android:label="_GetBitmap">
+                android:label="TextureView/Get Bitmap">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="SmallCircleActivity"
-                android:label="_SmallCircle">
+                android:label="Draw/Small Circle">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="ClearActivity"
-                android:label="_Clear">
+                android:label="Window/Clear">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="TextureViewActivity"
-                android:label="_TextureView">
+                android:label="TextureView/Camera">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="GlyphCacheActivity"
-                android:label="_GlyphCache">
+                android:label="Text/Glyph Cache">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="CanvasTextureViewActivity"
-                android:label="_CanvasTextureView">
+                android:label="TextureView/Canvas">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="GLTextureViewActivity"
-                android:label="_TextureViewGL">
+                android:label="TextureView/OpenGL">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="BitmapMeshActivity"
-                android:label="_BitmapMesh">
+                android:label="Bitmaps/Mesh">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="BitmapMutateActivity"
-                android:label="_BitmapMutate">
+                android:label="Bitmaps/Mutate">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="BitmapMeshLayerActivity"
-                android:label="_BitmapMeshLayer">
+                android:label="Bitmaps/Mesh in Layer">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
                 
         <activity
                 android:name="MarqueeActivity"
-                android:label="_Marquee">
+                android:label="Text/Marquee">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="ShapesActivity"
-                android:label="_Shapes">
+                android:label="Path/Shapes">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="ColoredRectsActivity"
-                android:label="_Rects">
+                android:label="Draw/Rects">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="SimplePatchActivity"
-                android:label="_SimplePatch"
+                android:label="Draw/9-Patch"
                 android:theme="@android:style/Theme.Translucent.NoTitleBar">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="ViewLayersActivity"
-                android:label="_ViewLayers">
+                android:label="Layers/Views 1">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="ViewLayersActivity2"
-                android:label="_ViewLayers2">
+                android:label="Layers/Views 2">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="ViewLayersActivity3"
-                android:label="_ViewLayers3">
+                android:label="Layers/Views 3">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="ViewLayersActivity4"
-                android:label="_ViewLayers4">
+                android:label="Layers/Views 4">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="ViewLayersActivity5"
-                android:label="_ViewLayers5">
+                android:label="Layers/Views 5">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="AlphaLayersActivity"
-                android:label="_αLayers">
+                android:label="Layers/Alpha">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="AdvancedGradientsActivity"
-                android:label="_Advanced Gradients">
+                android:label="Gradients/Advanced">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="Bitmaps3dActivity"
-                android:label="_Bitmaps3d">
+                android:label="Bitmaps/3D Rotation">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="LabelsActivity"
-                android:label="_Labels">
+                android:label="View/TextView">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
             <activity
                 android:name="ViewFlipperActivity"
-                android:label="_ViewFlipper"
+                android:label="View/ViewFlipper"
                 android:theme="@android:style/Theme.Translucent.NoTitleBar">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="ResizeActivity"
-                android:label="_Resize"
+                android:label="Window/Resize"
                 android:windowSoftInputMode="adjustResize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="TextGammaActivity"
-                android:label="_Gamma"
+                android:label="Text/Gamma"
                 android:theme="@android:style/Theme.Translucent.NoTitleBar">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
             <activity
                 android:name="TextGammaActivity$SubGammaActivity"
-                android:label="_Sub Gamma"
+                android:label="Text/Sub Gamma"
                 android:theme="@android:style/Theme.Translucent.NoTitleBar"
                 android:hardwareAccelerated="false">
             <intent-filter>
@@ -481,333 +499,333 @@
         
         <activity
                 android:name="LayersActivity"
-                android:label="_Layers"
+                android:label="Layers/Canvas Layers"
                 android:theme="@android:style/Theme.Translucent.NoTitleBar">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="NewLayersActivity"
-                android:label="_NewLayers">
+                android:label="Layers/Overlapping Layers">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="XfermodeActivity"
-                android:label="_Xfermodes"
+                android:label="Draw/Xfermodes"
                 android:theme="@android:style/Theme.Translucent.NoTitleBar">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="BitmapsActivity"
-                android:label="_Bitmaps"
+                android:label="Bitmaps/Draw Bitmaps"
                 android:theme="@android:style/Theme.Translucent.NoTitleBar">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="BitmapsSkewActivity"
-                android:label="_BitmapsSkew">
+                android:label="Bitmaps/Skew">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="BitmapsAlphaActivity"
-                android:label="_BitmapsAlpha"
+                android:label="Bitmaps/Alpha"
                 android:theme="@android:style/Theme.Translucent.NoTitleBar">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="BitmapsRectActivity"
-                android:label="_BitmapsRect"
+                android:label="Bitmaps/Rect"
                 android:theme="@android:style/Theme.Translucent.NoTitleBar">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="ThinPatchesActivity"
-                android:label="_9patchThin"
+                android:label="Draw/9-Patch Thin Drawable"
                 android:theme="@android:style/Theme.Translucent.NoTitleBar">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="NinePatchesActivity"
-                android:label="_9patch">
+                android:label="Draw/9-Patch Drawable">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="MoreNinePatchesActivity"
-                android:label="_9patch2">
+                android:label="Draw/9-Patch Vertical Drawable">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="QuickRejectActivity"
-                android:label="_QuickReject">
+                android:label="Clip/QuickReject">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="RotationActivity"
-                android:label="_Rotation">
+                android:label="View/Rotation">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="GradientsActivity"
-                android:label="_Gradients">
+                android:label="Gradients/Gradients">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="ShadersActivity"
-                android:label="_Shaders">
+                android:label="Shaders/Shaders">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="TextActivity"
-                android:label="_Text"
+                android:label="Text/Simple Text"
                 android:theme="@android:style/Theme.NoTitleBar">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="PosTextActivity"
-                android:label="_PosText"
+                android:label="Text/Pos Text"
                 android:theme="@android:style/Theme.NoTitleBar">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="ListActivity"
-                android:label="__List">
+                android:label="View/List">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="TransparentListActivity"
-                android:label="_TransparentList">
+                android:label="View/Transparent List">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
         
         <activity
                 android:name="MoreShadersActivity"
-                android:label="_Shaders2">
+                android:label="Shaders/Compose Shaders">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="ColorFiltersActivity"
-                android:label="_ColorFilters">
+                android:label="ColorFilters/Filters">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="LinesActivity"
-                android:label="_Lines">
+                android:label="Draw/Lines">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="Lines2Activity"
-                android:label="_Lines2">
+                android:label="Draw/Lines 2">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="PathsActivity"
-                android:label="_Paths">
+                android:label="Path/Paths">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="TextOnPathActivity"
-                android:label="_TextOnPath">
+                android:label="Text/Text on Path">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="PathsCacheActivity"
-                android:label="_PathsCache">
+                android:label="Path/Cache">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="PointsActivity"
-                android:label="_Points">
+                android:label="Draw/Points">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="Transform3dActivity"
-                android:label="_3d">
+                android:label="Draw/3D Transform">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="Animated3dActivity"
-                android:label="_Animated 3d">
+                android:label="Draw/Animated 3D Transform">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="SimplePathsActivity"
-                android:label="_SimplePaths">
+                android:label="Path/Simple Paths">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="AdvancedBlendActivity"
-                android:label="_Advanced Blend">
+                android:label="Draw/Advanced Blend">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="FramebufferBlendActivity"
-                android:label="_FramebufferBlend">
+                android:label="Draw/Framebuffer Blend">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="StackActivity"
-                android:label="_Stacks">
+                android:label="View/Stacks">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="PathDestructionActivity"
-                android:label="_PathDestruction">
+                android:label="Path/Path Destruction">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="TransformsAndAnimationsActivity"
-                android:label="_TransformAnim">
+                android:label="Draw/Transforms and Animations">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="ViewPropertyAlphaActivity"
-                android:label="_ViewPropAlpha">
+                android:label="View/Alpha Property">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
         <activity
                 android:name="ViewLayerInvalidationActivity"
-                android:label="_ViewLayerInvalidation">
+                android:label="Layers/Invalidation">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="com.android.test.hwui.TEST" />
             </intent-filter>
         </activity>
 
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/HwTests.java b/tests/HwAccelerationTest/src/com/android/test/hwui/HwTests.java
new file mode 100644
index 0000000..b1c32a8
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/HwTests.java
@@ -0,0 +1,153 @@
+/*
+ * 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 com.android.test.hwui;
+
+import android.app.*;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ListView;
+import android.widget.SimpleAdapter;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@SuppressWarnings("UnusedDeclaration")
+public class HwTests extends android.app.ListActivity {
+    private static final String EXTRA_PATH = "com.android.test.hwui.Path";
+    private static final String CATEGORY_HWUI_TEST = "com.android.test.hwui.TEST";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = getIntent();
+        String path = intent.getStringExtra("com.android.test.hwui.Path");
+
+        if (path == null) {
+            path = "";
+        }
+
+        setListAdapter(new SimpleAdapter(this, getData(path),
+                android.R.layout.simple_list_item_1, new String[] { "title" },
+                new int[] { android.R.id.text1 }));
+        getListView().setTextFilterEnabled(true);
+    }
+
+    protected List<Map<String, Object>> getData(String prefix) {
+        List<Map<String, Object>> myData = new ArrayList<Map<String, Object>>();
+
+        Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+        mainIntent.addCategory(CATEGORY_HWUI_TEST);
+
+        PackageManager pm = getPackageManager();
+        List<ResolveInfo> list = pm.queryIntentActivities(mainIntent, 0);
+
+        if (null == list)
+            return myData;
+
+        String[] prefixPath;
+        String prefixWithSlash = prefix;
+
+        if (prefix.equals("")) {
+            prefixPath = null;
+        } else {
+            prefixPath = prefix.split("/");
+            prefixWithSlash = prefix + "/";
+        }
+
+        int len = list.size();
+
+        Map<String, Boolean> entries = new HashMap<String, Boolean>();
+
+        for (int i = 0; i < len; i++) {
+            ResolveInfo info = list.get(i);
+            CharSequence labelSeq = info.loadLabel(pm);
+            String label = labelSeq != null
+                    ? labelSeq.toString()
+                    : info.activityInfo.name;
+
+            if (prefixWithSlash.length() == 0 || label.startsWith(prefixWithSlash)) {
+
+                String[] labelPath = label.split("/");
+
+                String nextLabel = prefixPath == null ? labelPath[0] : labelPath[prefixPath.length];
+
+                if ((prefixPath != null ? prefixPath.length : 0) == labelPath.length - 1) {
+                    addItem(myData, nextLabel, activityIntent(
+                            info.activityInfo.applicationInfo.packageName,
+                            info.activityInfo.name));
+                } else {
+                    if (entries.get(nextLabel) == null) {
+                        addItem(myData, nextLabel, browseIntent(prefix.equals("") ?
+                                nextLabel : prefix + "/" + nextLabel));
+                        entries.put(nextLabel, true);
+                    }
+                }
+            }
+        }
+
+        Collections.sort(myData, sDisplayNameComparator);
+
+        return myData;
+    }
+
+    private final static Comparator<Map<String, Object>> sDisplayNameComparator =
+            new Comparator<Map<String, Object>>() {
+                private final Collator collator = Collator.getInstance();
+
+                public int compare(Map<String, Object> map1, Map<String, Object> map2) {
+                    return collator.compare(map1.get("title"), map2.get("title"));
+                }
+            };
+
+    protected Intent activityIntent(String pkg, String componentName) {
+        Intent result = new Intent();
+        result.setClassName(pkg, componentName);
+        return result;
+    }
+
+    protected Intent browseIntent(String path) {
+        Intent result = new Intent();
+        result.setClass(this, HwTests.class);
+        result.putExtra(EXTRA_PATH, path);
+        return result;
+    }
+
+    protected void addItem(List<Map<String, Object>> data, String name, Intent intent) {
+        Map<String, Object> temp = new HashMap<String, Object>();
+        temp.put("title", name);
+        temp.put("intent", intent);
+        data.add(temp);
+    }
+
+    @Override
+    @SuppressWarnings({ "unchecked", "UnusedParameters" })
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        Map<String, Object> map = (Map<String, Object>)l.getItemAtPosition(position);
+
+        Intent intent = (Intent) map.get("intent");
+        startActivity(intent);
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/Rotate3dTextActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/Rotate3dTextActivity.java
new file mode 100644
index 0000000..93b8705
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/Rotate3dTextActivity.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class Rotate3dTextActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final Rotate3dTextView view = new Rotate3dTextView(this);
+        setContentView(view);
+    }
+
+    public static class Rotate3dTextView extends View {
+        private static final String TEXT = "Hello libhwui! ";
+
+        private final Paint mPaint;
+
+        public Rotate3dTextView(Context c) {
+            super(c);
+
+            mPaint = new Paint();
+            mPaint.setAntiAlias(true);
+            mPaint.setTextSize(50.0f);
+            mPaint.setTextAlign(Paint.Align.CENTER);
+
+            setRotationY(45.0f);
+            setScaleX(2.0f);
+            setScaleY(2.0f);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+
+            canvas.drawText(TEXT, getWidth() / 2.0f, getHeight() / 2.0f, mPaint);
+
+            invalidate();
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java
index 9e77b8c..95d3041 100644
--- a/wifi/java/android/net/wifi/WifiNative.java
+++ b/wifi/java/android/net/wifi/WifiNative.java
@@ -210,13 +210,14 @@
      * age=2623
      * flags=[WPA2-PSK-CCMP][WPS][ESS]
      * ssid=zubyb
+     * ====
      *
      * RANGE=ALL gets all scan results
      * RANGE=ID- gets results from ID
      * MASK=<N> see wpa_supplicant/src/common/wpa_ctrl.h for details
      */
     public String scanResults(int sid) {
-        return doStringCommand("BSS RANGE=" + sid + "- MASK=0x1987");
+        return doStringCommand("BSS RANGE=" + sid + "- MASK=0x21987");
     }
 
     public boolean startDriver() {
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 28f6bb2..900a38a 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -1378,7 +1378,7 @@
     private static final String FLAGS_STR = "flags=";
     private static final String SSID_STR = "ssid=";
     private static final String DELIMITER_STR = "====";
-    private static final int SCAN_BUF_RANGE = 3900;
+    private static final String END_STR = "####";
 
     /**
      * Format:
@@ -1417,11 +1417,12 @@
             if (TextUtils.isEmpty(tmpResults)) break;
             scanResultsBuf.append(tmpResults);
             scanResultsBuf.append("\n");
-            if (tmpResults.length() < SCAN_BUF_RANGE) break;
             String[] lines = tmpResults.split("\n");
             sid = -1;
             for (int i=lines.length - 1; i >= 0; i--) {
-                if (lines[i].startsWith(ID_STR)) {
+                if (lines[i].startsWith(END_STR)) {
+                    break;
+                } else if (lines[i].startsWith(ID_STR)) {
                     try {
                         sid = Integer.parseInt(lines[i].substring(ID_STR.length())) + 1;
                     } catch (NumberFormatException e) {
@@ -1472,7 +1473,7 @@
                 } else if (line.startsWith(SSID_STR)) {
                     wifiSsid = WifiSsid.createFromAsciiEncoded(
                             line.substring(SSID_STR.length()));
-                } else if (line.startsWith(DELIMITER_STR)) {
+                } else if (line.startsWith(DELIMITER_STR) || line.startsWith(END_STR)) {
                     if (bssid != null) {
                         String ssid = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;
                         String key = bssid + ssid;