Merge "Fix security issues with LocationManager where apps with coarse permissions can get location updates too frequently by repeatedly calling getLastKnownLocation or by registering/unregistering location updates frequently." into jb-mr2-dev
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 3586573..acb3725 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1817,11 +1817,12 @@
                     false)) {
                 owner.mRequiredForAllUsers = true;
             }
-            String restrictedAccountType = sa.getString(com.android.internal.R.styleable
-                    .AndroidManifestApplication_restrictedAccountType);
-            if (restrictedAccountType != null && restrictedAccountType.length() > 0) {
-                owner.mRestrictedAccountType = restrictedAccountType;
-            }
+        }
+
+        String restrictedAccountType = sa.getString(com.android.internal.R.styleable
+                .AndroidManifestApplication_restrictedAccountType);
+        if (restrictedAccountType != null && restrictedAccountType.length() > 0) {
+            owner.mRestrictedAccountType = restrictedAccountType;
         }
 
         String requiredAccountType = sa.getString(com.android.internal.R.styleable
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 5d0f523..14fa9cb 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -480,6 +480,7 @@
                 if (!getContext().getResources().getCompatibilityInfo().supportsScreen()) {
                     mLayout.flags |= WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW;
                 }
+                mLayout.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 
                 if (mWindow == null) {
                     Display display = getDisplay();
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 96ef0b4..48630a4 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1012,6 +1012,12 @@
         public static final int PRIVATE_FLAG_FORCE_SHOW_NAV_BAR = 0x00000020;
 
         /**
+         * Never animate position changes of the window.
+         *
+         * {@hide} */
+        public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 0x00000040;
+
+        /**
          * Control flags that are private to the platform.
          * @hide
          */
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index d2b11e4..0c7cf72 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -622,7 +622,7 @@
     <string name="permlab_accessNotifications" msgid="7673416487873432268">"adgang til underretninger"</string>
     <string name="permdesc_accessNotifications" msgid="458457742683431387">"Tillader, at appen kan hente, undersøge og rydde underretninger, herunder dem, der er sendt af andre apps."</string>
     <string name="permlab_bindNotificationListenerService" msgid="7057764742211656654">"forpligte sig til en underretningslyttertjeneste"</string>
-    <string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"Tillader, at brugeren kan forpligte sig til en grænseflade for en underretningslyttertjeneste på øverste niveau. Bør aldrig være nødvendigt til almindelige apps."</string>
+    <string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"Tillader brugeren at forpligte sig til en underretningslyttertjenestes grænseflade på øverste niveau. Bør aldrig være nødvendigt til almindelige apps."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Indstil regler for adgangskode"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Kontroller længden samt tilladte tegn i adgangskoder til oplåsning af skærmen."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Overvåg forsøg på oplåsning af skærm"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 09acbe1..485e1bf 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -559,8 +559,8 @@
     <string name="permdesc_changeNetworkState" msgid="6789123912476416214">"Permite que la aplicación modifique el estado de la conectividad de red."</string>
     <string name="permlab_changeTetherState" msgid="5952584964373017960">"cambiar conectividad de anclaje a red"</string>
     <string name="permdesc_changeTetherState" msgid="1524441344412319780">"Permite que la aplicación cambie el estado de la conectividad de red de anclaje."</string>
-    <string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"cambiar configuración de uso de datos de referencia"</string>
-    <string name="permdesc_changeBackgroundDataSetting" msgid="5347729578468744379">"Permite que la aplicación cambie los ajustes de uso de datos de referencia."</string>
+    <string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"cambiar configuración de uso de conexiones automáticas"</string>
+    <string name="permdesc_changeBackgroundDataSetting" msgid="5347729578468744379">"Permite que la aplicación cambie los ajustes de uso de conexiones automáticas."</string>
     <string name="permlab_accessWifiState" msgid="5202012949247040011">"ver conexiones Wi-Fi"</string>
     <string name="permdesc_accessWifiState" msgid="5002798077387803726">"Permite que la aplicación vea información sobre conexión a redes Wi-Fi (por ejemplo, si está habilitada la conexión Wi-Fi y el nombre de los dispositivos Wi-Fi conectados)."</string>
     <string name="permlab_changeWifiState" msgid="6550641188749128035">"conectarse a redes Wi-Fi y desconectarse"</string>
@@ -569,14 +569,14 @@
     <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="7969774021256336548">"Permite que la aplicación reciba paquetes enviados a todos los dispositivos de una red Wi-Fi que utilicen direcciones de multidifusión, no solo al tablet. Utiliza más batería que el modo de no multidifusión."</string>
     <string name="permdesc_changeWifiMulticastState" product="default" msgid="6851949706025349926">"Permite que la aplicación reciba paquetes enviados a todos los dispositivos de una red Wi-Fi que utilicen direcciones de multidifusión, no solo al teléfono. Utiliza más batería que el modo de no multidifusión."</string>
     <string name="permlab_bluetoothAdmin" msgid="6006967373935926659">"acceder a los ajustes de Bluetooth"</string>
-    <string name="permdesc_bluetoothAdmin" product="tablet" msgid="6921177471748882137">"Permite que la aplicación configure el tablet Bluetooth local y que detecte dispositivos remotos y se sincronice con ellos."</string>
-    <string name="permdesc_bluetoothAdmin" product="default" msgid="8931682159331542137">"Permite que la aplicación configure el teléfono Bluetooth local y que detecte dispositivos remotos y se sincronice con ellos."</string>
+    <string name="permdesc_bluetoothAdmin" product="tablet" msgid="6921177471748882137">"Permite que la aplicación configure el tablet Bluetooth local y que detecte dispositivos remotos y se vincule con ellos."</string>
+    <string name="permdesc_bluetoothAdmin" product="default" msgid="8931682159331542137">"Permite que la aplicación configure el teléfono Bluetooth local y que detecte dispositivos remotos y se vincule con ellos."</string>
     <string name="permlab_accessWimaxState" msgid="4195907010610205703">"conectarse a WiMAX y desconectarse de esta red"</string>
     <string name="permdesc_accessWimaxState" msgid="6360102877261978887">"Permite que la aplicación determine si está habilitada la conexión WiMAX y obtenga información sobre las redes WiMAX que están conectadas."</string>
     <string name="permlab_changeWimaxState" msgid="2405042267131496579">"Cambiar estado de WiMAX"</string>
     <string name="permdesc_changeWimaxState" product="tablet" msgid="3156456504084201805">"Permite que la aplicación conecte el tablet a redes WiMAX y lo desconecte de ellas."</string>
     <string name="permdesc_changeWimaxState" product="default" msgid="697025043004923798">"Permite que la aplicación conecte el teléfono a redes WiMAX y lo desconecte de ellas."</string>
-    <string name="permlab_bluetooth" msgid="6127769336339276828">"sincronizarse con dispositivos Bluetooth"</string>
+    <string name="permlab_bluetooth" msgid="6127769336339276828">"vincular con dispositivos Bluetooth"</string>
     <string name="permdesc_bluetooth" product="tablet" msgid="3480722181852438628">"Permite que la aplicación acceda a la configuración de Bluetooth del tablet y que establezca y acepte conexiones con los dispositivos sincronizados."</string>
     <string name="permdesc_bluetooth" product="default" msgid="3207106324452312739">"Permite que la aplicación acceda a la configuración de Bluetooth del teléfono y que establezca y acepte conexiones con los dispositivos sincronizados."</string>
     <string name="permlab_nfc" msgid="4423351274757876953">"controlar Comunicación de campo cercano (NFC)"</string>
@@ -702,7 +702,7 @@
     <string name="phoneTypeIsdn" msgid="8022453193171370337">"RDSI"</string>
     <string name="phoneTypeMain" msgid="6766137010628326916">"Principal"</string>
     <string name="phoneTypeOtherFax" msgid="8587657145072446565">"Otro fax"</string>
-    <string name="phoneTypeRadio" msgid="4093738079908667513">"Radio"</string>
+    <string name="phoneTypeRadio" msgid="4093738079908667513">"Señal móvil"</string>
     <string name="phoneTypeTelex" msgid="3367879952476250512">"Télex"</string>
     <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"TTY TDD"</string>
     <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Móvil del trabajo"</string>
@@ -1391,7 +1391,7 @@
     <string name="data_usage_mobile_limit_snoozed_title" msgid="279240572165412168">"Límite de datos móviles superado"</string>
     <string name="data_usage_wifi_limit_snoozed_title" msgid="8743856006384825974">"Límite de datos Wi-Fi superado"</string>
     <string name="data_usage_limit_snoozed_body" msgid="7035490278298441767">"Límite superado en <xliff:g id="SIZE">%s</xliff:g>"</string>
-    <string name="data_usage_restricted_title" msgid="5965157361036321914">"Datos de referencia restringidos"</string>
+    <string name="data_usage_restricted_title" msgid="5965157361036321914">"Conexiones automáticas restringidas"</string>
     <string name="data_usage_restricted_body" msgid="6741521330997452990">"Toca para eliminar la restricción."</string>
     <string name="ssl_certificate" msgid="6510040486049237639">"Certificado de seguridad"</string>
     <string name="ssl_certificate_is_valid" msgid="6825263250774569373">"Este certificado es válido."</string>
@@ -1420,7 +1420,7 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Teléfono"</string>
     <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Auriculares"</string>
-    <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Altavoces del conector"</string>
+    <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Altavoces de la base"</string>
     <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistema"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Audio Bluetooth"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 0c11321..bdc0155 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -622,7 +622,7 @@
     <string name="permlab_accessNotifications" msgid="7673416487873432268">"fikia arifa"</string>
     <string name="permdesc_accessNotifications" msgid="458457742683431387">"Huruhusu programu kurejesha, kuchunguza, na kuondoa arifa, ikiwa ni pamoja na zile zilizochapishwa na programu nyingine."</string>
     <string name="permlab_bindNotificationListenerService" msgid="7057764742211656654">"unganisha kwenye huduma ya kisikilizi cha arifa"</string>
-    <string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"Inaruhusu kishikilizi kuunganishwa kwenye kiolesura cha kiwango cha juu cha huduma ya kisikilizi cha arifa. Haipaswi kuhitajika tena kwa programu za kawaida."</string>
+    <string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"Inaruhusu kishikilizi kuunganishwa kwenye kusano cha kiwango cha juu cha huduma ya kisikilizi cha arifa. Haipaswi kuhitajika tena kwa programu za kawaida."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Weka kanuni za nenosiri"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Dhibiti urefu na vibambo vinavyoruhusiwa katika manenosiri ya kufungua skrini."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Chunguza majaribio ya kutofun gua skrini"</string>
@@ -1284,7 +1284,7 @@
     <string name="submit" msgid="1602335572089911941">"Wasilisha"</string>
     <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Mtindo wa gari umewezeshwa"</string>
     <string name="car_mode_disable_notification_message" msgid="8035230537563503262">"Gusa ili kutoka katika modi ya gari."</string>
-    <string name="tethered_notification_title" msgid="3146694234398202601">"Amilisha uzuiaji au mahali maalum"</string>
+    <string name="tethered_notification_title" msgid="3146694234398202601">"Kushiriki au kusambaza intaneti kumewashwa"</string>
     <string name="tethered_notification_message" msgid="6857031760103062982">"Gusa ili kusanidi."</string>
     <string name="back_button_label" msgid="2300470004503343439">"Nyuma"</string>
     <string name="next_button_label" msgid="1080555104677992408">"Ifuatayo"</string>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 8821a01..f2c0aa0 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -909,7 +909,7 @@
         <!-- Declare that this application requires access to restricted accounts of a certain
              type. The default value is null and restricted accounts won\'t be visible to this
              application. The type should correspond to the account authenticator type, such as
-             "com.google". Only usable by system apps. -->
+             "com.google". -->
         <attr name="restrictedAccountType" format="string"/>
         <!-- Declare that this application requires an account of a certain
              type. The default value is null and indicates that the application can work without
diff --git a/docs/html/distribute/distribute_toc.cs b/docs/html/distribute/distribute_toc.cs
index ad3121c..3ea11bf 100644
--- a/docs/html/distribute/distribute_toc.cs
+++ b/docs/html/distribute/distribute_toc.cs
@@ -1,106 +1,71 @@
 <ul id="nav">
 
   <li class="nav-section">
-    <div class="nav-section-header"><a href="<?cs var:toroot ?>distribute/index.html">
-      <span class="en">Google Play</span></a>
-    </div>
+    <div class="nav-section-header"><a href="<?cs var:toroot ?>distribute/index.html">Google Play</a></div>
     <ul>
-      <li><a href="<?cs var:toroot ?>distribute/googleplay/about/visibility.html">
-           <span class="en">Visibility</a></li>
-      <li><a href="<?cs var:toroot ?>distribute/googleplay/about/monetizing.html">
-           <span class="en">Monetizing</a></li>
-       <li><a href="<?cs var:toroot ?>distribute/googleplay/about/distribution.html">
-           <span class="en">Distribution</a></li>
+      <li><a href="<?cs var:toroot ?>distribute/googleplay/about/visibility.html">Visibility</a></li>
+      <li><a href="<?cs var:toroot ?>distribute/googleplay/about/monetizing.html">Monetizing</a></li>
+      <li><a href="<?cs var:toroot ?>distribute/googleplay/about/distribution.html">Distribution</a></li>
     </ul>  
   </li>
 
   <li class="nav-section">
-    <div class="nav-section-header"><a href="<?cs var:toroot ?>distribute/googleplay/publish/index.html">
-      <span class="en">Publishing</span></a>
-    </div>
+    <div class="nav-section-header"><a href="<?cs var:toroot ?>distribute/googleplay/publish/index.html">Publishing</a></div>
     <ul>
-       <li><a href="<?cs var:toroot ?>distribute/googleplay/publish/register.html">
-           <span class="en">Get Started</span>
-          </a></li>
-       <li><a href="<?cs var:toroot ?>distribute/googleplay/publish/console.html">
-           <span class="en">Developer Console</span>
-          </a></li>
-       <li><a href="<?cs var:toroot ?>distribute/googleplay/publish/preparing.html">
-           <span class="en">Publishing Checklist</span>
-          </a></li>
-
-     </ul>
+      <li><a href="<?cs var:toroot ?>distribute/googleplay/publish/register.html">Get Started</a></li>
+      <li><a href="<?cs var:toroot ?>distribute/googleplay/publish/console.html">Developer Console</a></li>
+      <li><a href="<?cs var:toroot ?>distribute/googleplay/publish/preparing.html">Publishing Checklist</a></li>
+    </ul>
   </li>
   
 <!--  <li class="nav-section">
-    <div class="nav-section-header"><a href="<?cs var:toroot ?>distribute/googleplay/developer-console.html">
-        <span class="en">The Developer Console</span>
-      </a>
+    <div class="nav-section-header">
+      <a href="<?cs var:toroot ?>distribute/googleplay/developer-console.html">The Developer Console</a>
     </div>
     <ul>
-      <li class="nav-section"><a href="<?cs var:toroot ?>distribute/googleplay/register.html">
-        <span class="en">Get Started</span></a></li>
-      <li><a href="<?cs var:toroot ?>distribute/googleplay/distribution-controls.html">
-        <span class="en">Managing Distribution</span>
-        </a></li>
-      <li><a href="<?cs var:toroot ?>distribute/googleplay/pricing-billing.html">
-        <span class="en">Pricing and Billing</span>
-        </a></li>
-      <li><a href="<?cs var:toroot ?>distribute/googleplay/app-data.html">
-        <span class="en">Reviewing App Data</span>
-        </a></li>
-      <li><a href="<?cs var:toroot ?>distribute/googleplay/advanced-options.html">
-        <span class="en">Advanced Options</span>
-        </a></li>
-      <li><a href="<?cs var:toroot ?>distribute/googleplay/publishing.html">
-        <span class="en">Publishing and Updating</span>
-        </a></li>
+      <li class="nav-section"><a href="<?cs var:toroot ?>distribute/googleplay/register.html">Get Started</a></li>
+      <li><a href="<?cs var:toroot ?>distribute/googleplay/distribution-controls.html">Managing Distribution</a></li>
+      <li><a href="<?cs var:toroot ?>distribute/googleplay/pricing-billing.html">Pricing and Billing</a></li>
+      <li><a href="<?cs var:toroot ?>distribute/googleplay/app-data.html">Reviewing App Data</a></li>
+      <li><a href="<?cs var:toroot ?>distribute/googleplay/advanced-options.html">Advanced Options</a></li>
+      <li><a href="<?cs var:toroot ?>distribute/googleplay/publishing.html">Publishing and Updating</a></li>
     </ul>
   </li> end of Developer Console -->
-  
+
    <li class="nav-section">
-     <div class="nav-section-header"><a href="<?cs var:toroot ?>distribute/googleplay/promote/index.html">
-       <span class="en">Promoting</span></a>
+     <div class="nav-section-header"><a href="<?cs var:toroot ?>distribute/googleplay/promote/index.html">Promoting</a>
      </div>
      <ul>
-<!--   <li><a href="<?cs var:toroot ?>distribute/googleplay/promote/product-pages.html">
-        <span class="en">Your Product Pages</a></li> 
--->
-       <li><a href="<?cs var:toroot ?>distribute/googleplay/promote/linking.html">
-        <span class="en">Linking to Your Products</a></li>
-       <li><a href="<?cs var:toroot ?>distribute/googleplay/promote/badges.html">
-        <span class="en">Google Play Badges</a></li>
-       <li><a href="<?cs var:toroot ?>distribute/promote/device-art.html">
-        <span class="en">Device Art Generator</a></li>
-       <li><a href="<?cs var:toroot ?>distribute/googleplay/promote/brand.html">
-        <span class="en">Brand Guidelines</a></li>
+<!--   <li><a href="<?cs var:toroot ?>distribute/googleplay/promote/product-pages.html">Your Product Pages</a></li> -->
+       <li><a href="<?cs var:toroot ?>distribute/googleplay/promote/linking.html">Linking to Your Products</a></li>
+       <li><a href="<?cs var:toroot ?>distribute/googleplay/promote/badges.html">Google Play Badges</a></li>
+       <li><a href="<?cs var:toroot ?>distribute/promote/device-art.html">Device Art Generator</a></li>
+       <li><a href="<?cs var:toroot ?>distribute/googleplay/promote/brand.html">Brand Guidelines</a></li>
      </ul>
    </li>
 
-
   <li class="nav-section">
-    <div class="nav-section-header"><a href="<?cs var:toroot ?>distribute/googleplay/quality/index.html">
-      <span class="en">App Quality</span></a>
-    </div>
+    <div class="nav-section-header"><a href="<?cs var:toroot ?>distribute/googleplay/quality/index.html">App Quality</a></div>
     <ul>
-       <li><a href="<?cs var:toroot ?>distribute/googleplay/quality/core.html">
-          <span class="en">Core App Quality</span>
-         </a></li>
-       <li><a href="<?cs var:toroot ?>distribute/googleplay/quality/tablet.html">
-          <span class="en">Tablet App Quality</span>
-         </a></li>
-       <li><a href="<?cs var:toroot ?>distribute/googleplay/strategies/app-quality.html">
-          <span class="en">Improving App Quality</span>
-         </a></li>
-
+       <li><a href="<?cs var:toroot ?>distribute/googleplay/quality/core.html">Core App Quality</a></li>
+       <li><a href="<?cs var:toroot ?>distribute/googleplay/quality/tablet.html">Tablet App Quality</a></li>
+       <li><a href="<?cs var:toroot ?>distribute/googleplay/strategies/app-quality.html">Improving App Quality</a></li>
     </ul>
   </li> 
 
+   <li class="nav-section">
+     <div class="nav-section-header"><a href="<?cs var:toroot ?>distribute/googleplay/policies/index.html">Policies</a></div>
+     <ul>
+       <li><a href="<?cs var:toroot ?>distribute/googleplay/policies/spam.html">Spam</a></li>
+       <li><a href="<?cs var:toroot ?>distribute/googleplay/policies/ip.html">Intellectual<br />Property</a></li> 
+       <li><a href="<?cs var:toroot ?>distribute/googleplay/policies/ads.html">Ads</a></li>
+     </ul>
+   </li>
 
 <!--    
    <li class="nav-section">
     <div class="nav-section-header"><a href="<?cs var:toroot ?>distribute/googleplay/after.html">
-      <span class="en">After Launch</span></a>
+      After Launch</a>
     </div>
     <ul>
        <li><a href="<?cs var:toroot ?>distribute/googleplay/errors.html.html">Reviewing Errors</a></li>
@@ -111,22 +76,14 @@
 -->
 
   <li class="nav-section">
-    <div class="nav-section-header"><a href="<?cs var:toroot ?>distribute/googleplay/spotlight/index.html">
-      <span class="en">Spotlight</span></a>
-    </div>
+    <div class="nav-section-header"><a href="<?cs var:toroot ?>distribute/googleplay/spotlight/index.html">Spotlight</a></div>
     <ul>
-       <li><a href="<?cs var:toroot ?>distribute/googleplay/spotlight/tablets.html">
-          <span class="en">Tablet Stories</span>
-         </a></li>
+       <li><a href="<?cs var:toroot ?>distribute/googleplay/spotlight/tablets.html">Tablet Stories</a></li>
     </ul>
   </li> 
 
   <li class="nav-section">
-    <div class="nav-section-header empty">
-      <a href="<?cs var:toroot ?>distribute/open.html">
-        <span class="en">Open Distribution</span>
-      </a>
-    </div>
+    <div class="nav-section-header empty"><a href="<?cs var:toroot ?>distribute/open.html">Open Distribution</a></div>
   </li>
 </ul>
   
\ No newline at end of file
diff --git a/docs/html/distribute/googleplay/policies/ads.jd b/docs/html/distribute/googleplay/policies/ads.jd
new file mode 100644
index 0000000..8920499
--- /dev/null
+++ b/docs/html/distribute/googleplay/policies/ads.jd
@@ -0,0 +1,352 @@
+page.title=Ads
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+  <h2>In This Document</h2>
+  <ol>
+    <li><a href="#content-maturity">Content and Maturity</a></li>
+    <li><a href="#context">Context and Behavior</a></li>
+    <li><a href="#disclosure" style="clear:right">Disclosure</a></li>
+    <li><a href="#impersonation">Impersonation of System UI</a></li>
+    <li><a href="#adwalls">Adwalls</a></li>
+    <li><a href="#interfering" style="clear:right;">Interference with Ads and Websites</a></li>
+  </ol>
+
+  <h2>More Resources</h2>
+  <ol>
+    <li><a href="http://play.google.com/about/developer-content-policy.html" target="_policies">Developer Program Policies</a></li>
+    <li><a href="http://www.android.com/us/developer-distribution-agreement.html#showlanguages" target="_policies">Developer Distribution Agreement</a></li>
+    <li><a href="http://support.google.com/googleplay/android-developer/answer/188189" target="_policies">Maturity Ratings</a></p>
+  </ol>
+</div>
+</div>
+
+<p>
+  Google Play policies guide how you can use ads in your apps, to help ensure
+  the best experience for users visiting and downloading apps from the store.
+</p>
+
+<p>
+  In general, for the purposes of policy, the content of ads displayed by your
+  app is considered part of your app. As an app developer, it is your
+  responsibility to ensure that the content, context, and behavior of ads in
+  your apps conforms to Google Play policies.
+</p>
+
+<p>
+  Before you publish, make sure you understand Google Play ad policies and how
+  to display ads in conformance with those policies. The sections below
+  highlight best practices and common examples to help you avoid the most
+  common types of policy violations.
+</p>
+
+<p>
+  For more information about Google Play policies that apply to your apps and
+  content, please see the <a href=
+  "http://play.google.com/about/developer-content-policy.html" target=
+  "_policies">Developer Program Policies</a> and <a href=
+  "http://play.google.com/about/developer-distribution-agreement.html" target=
+  "_policies">Developer Distribution Agreement</a>.
+</p>
+
+
+<h2 id="content-maturity">Content and Maturity</h2>
+
+<div class="example-block bad">
+  <div class="heading">Ad maturity exceeds app</div>
+  <img src="{@docRoot}images/gp-policy-ads-maturity-violation.png">
+</div>
+
+<p>
+  From a policy perspective, ads shown in your app are part of your content
+  and your app is responsible for any violations. If an ad shown in your app
+  violates Google Play policies, your app may be suspended or your developer
+  account terminated.
+</p>
+
+<p>
+  For this reason, it's important for you to be be aware of what ads will be
+  displayed in your app and to manage the ads content according to Google Play
+  policies. Here are some guidelines:
+</p>
+
+<ul>
+    <li>
+        <strong>Ads must not violate Content Policy</strong>&mdash;Ads in
+        your app must not violate the terms of Google Play’s Content Policy,
+        including those concerning illegal activities, violence, sexually
+        explicit content, or privacy violations.
+    </li>
+    <li>
+        <strong>Ads maturity must be consistent with your app's
+        maturity</strong>&mdash;Content shown in your ads must be consistent
+        with the app’s maturity rating in Google Play. Especially, ads content
+        should never exceed your app's maturity rating, even if the ads content
+        by itself complies with general policies.
+    </li>
+</ul>
+
+<p>
+  In the example at right, the app's maturity rating is set to
+  "Everyone", which is the lowest maturity level on Google Play. By choosing
+  the "Everyone" maturity level, the developer is declaring that all of the
+  content in the app, <em>including ads</em>, is suitable for all users
+  regardless of age.
+</p>
+
+<p>
+  The example app violates Google Play policies by displaying ad content with a
+  higher maturity level&mdash;ad content showing gambling, profanity, user
+  location, suggestive content, or content from another app with higher
+  maturity exceeds the "Everyone" maturity rating. Because the ad's
+  maturity is higher than the app's maturity level, the app itself is in
+  violation of policy. To correct the problem, the developer must either
+  restrict ads content to "Everyone" level or raise the app's maturity rating.
+</p>
+
+<p>
+  For detailed information about how to choose the appropriate maturity level
+  for your app, or to assess the maturity requirement of ads in your app, see
+  <a href=
+  "http://support.google.com/googleplay/android-developer/answer/188189"
+  target="_policies">Rating your application content for Google Play</a>.
+</p>
+
+
+<h2 id="context">Context and Behavior</h2>
+
+<p>
+  If your app displays ads, it should do so in ways that do not interrupt users,
+  mislead them into clicking on ads, or make changes outside the app without
+  the user's knowledge or consent. Here are some guidelines:
+</p>
+
+<ul>
+  <li>
+    <strong>Display your ads within your UI</strong>&mdash;If possible,
+    display ads only within your app's UI. This leads to a better user
+    experience and helps avoid policy violations
+  </li>
+
+  <li>
+    <strong>Make sure app origin is clear</strong>&mdash;When you display an
+    ad, it must be clear to the user that the ad has originated from your app.
+    If you show the ad in your app's UI while your app has focus, the user
+    understands the ad origin without explicit attribution. However, if you
+    display the ad outside of your app, such as in a notification, you must
+    explicitly indicate the origin.
+  </li>
+
+  <li>
+    <strong>Don't make changes outside of the app without consent</strong>
+   &mdash;Ads must not make changes outside of the app without the user's
+    full knowledge and consent. For example, ads should not install shortcuts,
+    bookmarks, or icons, or change default settings without user consent.
+  </li>
+
+  <li>
+    <strong>Changes outside the app must be reversible</strong>&mdash;If an
+    ad makes changes outside the app as described above, the changes (and
+    origin app) must be evident and easily reversible. For example, the user
+    must be able to locate and reverse the changes by adjusting settings,
+    changing ad preferences in the app, or uninstalling the app altogether.
+  </li>
+
+  <li>
+    <strong>Notification ads require user opt-in</strong>&mdash;Your app
+    should not create <a href=
+    "{@docRoot}design/patterns/notifications.html">notifications</a>
+    containing ads unless the user has specifically opted-in to this behavior
+    and is able to easily opt-out.
+  </li>
+
+  <li>
+    <strong>Use low priority for notification ads</strong>&mdash;Always
+    assign your notification ads <a href="
+    {@docRoot}reference/android/app/Notification.html#PRIORITY_LOW">low
+    priority</a> (for API level 16 and above).
+  </li>
+</ul>
+
+<div class="example-block bad" style="width:400px;margin:.5em 0 0 2em;">
+    <div class="heading">Does not fully indicate origin app</div>
+    <img src="{@docRoot}images/gp-policy-ads-notif-attr-violation.png">
+</div>
+<div class="example-block good" style="width:400px;margin:.5em 0 0 2em;">
+    <div class="heading">Indicates origin app by name and icon</div>
+    <img src="{@docRoot}images/gp-policy-ads-notif-attr.png">
+</div>
+
+<p>
+  In particular, note that notification ads must clearly identify your app as
+  the ad origin. If your app sends notification ads that do not sufficiently
+  identify your app as the origin, the app will be in violation of policy.
+</p>
+
+<p>
+  To identify your app as the origin, you should display the <strong>app's full
+  name and and icon</strong> in the notification to provide the clearest
+  identification and best policy compliance. Displaying a partial app name can
+  also be sufficient, provided the name unambiguously identifies your app.
+</p>
+
+<p>
+  Above right is an example notification ad that violates ad policy by not
+  providing attribution of the origin app. Below right, the notification ads
+  comply with policy by providing both the app icon and full app name (in this
+  case, "Turtle Test").
+</p>
+
+
+<h2 id="disclosure" style="clear:right">Disclosure of Ads to Users</h2>
+
+<p>
+  It's important to sufficiently disclose to users how your app will use ads.
+  You must make it easy for users to understand what ads will be shown in your
+  app, where they will be shown, and what the associated behaviors are, if any.
+  Further, you should ask for user consent and provide options for managing ads
+  or opt-out. Here are some guidelines:
+</p>
+
+<ul>
+  <li>
+    <strong>Tell users about your ads</strong>&mdash;Create a simple,
+    complete disclosure that tells users how your app uses ads, where the ads
+    are shown, and how they can manage ad options. Take common-sense steps to
+    make the disclosure as clear as possible.
+  </li>
+
+  <li>
+    <strong>Make sure users know</strong>&mdash;Present your ads disclosure
+    is an easy-to-see location, rather than hiding it where users are not
+    likely to find it.
+  </li>
+
+  <li>
+    <strong>Ask for consent (opt-in) at launch</strong>&mdash;Where possible,
+    include your ads disclosure in the app description as well as in an Ads
+    Terms, End User License Agreement (EULA), or similar document. Display the
+    terms at first launch and ask for the user's consent before continuing to
+    the app.
+  </li>
+</ul>
+
+<p>
+  A recommended approach is to provide an ads disclosure in an End-User License
+  Agreement (EULA). The disclosure should be clear and succinct and displayed
+  in a modal dialog that asks the user to agree to the terms before using the
+  app.
+</p>
+
+<p>
+  If your app adds homescreen icons and/or browser bookmarks, an acceptable
+  practice for revealing that behavior is to provide a disclosure in both the
+  app description and an opt-in EULA on app launch. This ensures that the
+  behaviors are clearly explained to the user up-front and requires the user’s
+  consent in a pop-up EULA to continue using the app.
+</p>
+
+<div class="example-block good" style="width:213px;margin-right:2em;">
+  <div class="heading">Disclosure in Terms</div>
+  <img src="{@docRoot}images/gp-policy-ads-terms.png">
+</div>
+
+<div class="example-block good" style="width:213px;">
+  <div class="heading">Disclosure in EULA</div>
+  <img src="{@docRoot}images/gp-policy-ads-eula.png">
+</div>
+
+<div class="example-block bad" style="width:213px;margin-left:0em;">
+  <div class="heading">Disclosure is hidden</div>
+  <img src="{@docRoot}images/gp-policy-ads-eula-violation.png">
+</div>
+
+<p style="clear:right">
+  Above left is an example of ads disclosure that is hidden in a long EULA. The
+  disclosure information itself is not clearly indicated in the document text
+  and it's not visible unless the user happens to scroll down far enough in the
+  EULA. Above middle and right show two alternative approaches that
+  present the disclosure in an obvious and clear manner at the top of a
+  EULA and in a dedicated Terms agreement. 
+</p>
+
+
+<h2 id="impersonation">Impersonation of System UI</h2>
+
+<div class="example-block bad">
+  <div class="heading">Ad impersonates system dialog</div>
+  <img src="{@docRoot}images/gp-policy-ads-impersonate-violation.png">
+</div>
+
+<p>
+  Your app must not display any ad that attempts to impersonate or represent a
+  system function or UI component. If such an ad is displayed in your app, your
+  app will be in violation of policy and subject to suspension. Here are some
+  guidelines:
+</p>
+
+<ul>
+  <li>
+    <strong>No fake system dialogs or warnings</strong>&mdash;Any ad that
+    presents itself as a system dialog or warning and asks for user input is in
+    violation of Google Play policies.
+  </li>
+
+  <li>
+    <strong>No fake app updates</strong>&mdash;Ads should not impersonate
+    system UI for app updates.
+  </li>
+</ul>
+
+<p>
+  At right is an example of a pop-up ad impersonating a system dialog, warning
+  the user about viruses. This is a violation of policy.
+</p>
+
+
+<h2 id="adwalls">Adwalls</h2>
+
+<div class="example-block good" style="width:213px;">
+  <div class="heading">Adwall lets user cancel</div>
+  <img src="{@docRoot}images/gp-policy-ads-paywall.png">
+</div>
+
+<div class="example-block bad" style="width:213px;">
+  <div class="heading">Adwall forces user action</div>
+  <img src="{@docRoot}images/gp-policy-ads-paywall-violation.png">
+</div>
+
+<p>
+  If your app uses adwalls to drive affiliate traffic, those adwalls must not
+  force the user to click on ads or submit personal information for advertising
+  purposes before using the app.
+</p>
+
+<p>
+  Forcing a user action in an adwall is not only a poor user experience, it is
+  a violation of Google Play policies.
+</p>
+
+<p>
+  For this reason, <strong>all adwalls must give the user the option to
+  cancel</strong> or otherwise dismiss the ad without penalty.
+</p>
+
+<p>
+  At right is an example of an app that requires the user to click through the
+  ad to fully use the app. This is a violation of policy.
+</p>
+
+<p>
+  The adjacent example demonstrates an adequate option to let the user dismiss
+  the ad wall easily by cancelling.
+</p>
+
+
+<h2 id="interfering" style="clear:right;">Interference with Third-party Ads and Websites</h2>
+
+<p>
+  Ads associated with your app <strong>must not interfere</strong> with any
+  other ads originating in other applications.
+</p>
\ No newline at end of file
diff --git a/docs/html/distribute/googleplay/policies/index.jd b/docs/html/distribute/googleplay/policies/index.jd
new file mode 100644
index 0000000..fb46055
--- /dev/null
+++ b/docs/html/distribute/googleplay/policies/index.jd
@@ -0,0 +1,59 @@
+page.title=Google Play Policies and Guidelines
+page.metaDescription=Guidelines and tips for creating apps that comply with Google Play content and distribution policies.
+@jd:body
+
+<p>
+  Before publishing your apps on Google Play, take a few minutes to read and
+  understand the content and distribution policies that apply to all apps
+  in the store. These policies help to keep Android and Google Play an enjoyable
+  and trusted platform for content consumers and developers alike.
+</p>
+
+<p>
+  The documents below highlight important policy areas and provide tips to help
+  you create policy-compliant apps. You'll also find examples and guidance on common
+  policy questions that can help your app stay clear of practices that can result in
+  low ratings or even suspensions from the store.
+</p>
+
+<p>
+  For complete information about Google Play policies, please see the full
+  <a href="http://play.google.com/about/developer-content-policy.html" target=
+  "_policies">Developer Program Policies</a> and <a href=
+  "http://play.google.com/about/developer-distribution-agreement.html" target=
+  "_policies">Developer Distribution Agreement</a> documents.
+</p>
+
+<div class="vspace size-1">
+  &nbsp;
+</div>
+<div class="layout-content-row">
+  <div class="layout-content-col span-4">
+    <h4>
+      Spam
+    </h4>
+    <p>
+      Make sure that your app does not present content that is unwanted,
+      deceptive, repetitive, or unrelated to the core function of the app.
+    </p><a href="{@docRoot}distribute/googleplay/policies/spam.html">Learn more &raquo;</a>
+  </div>
+  <div class="layout-content-col span-4">
+    <h4>
+      Intellectual Property
+    </h4>
+    <p>
+      Tips and examples of how to use intelletual property (IP) properly,
+      including when to ask permission to use someone else's copyright or
+      trademark.
+    </p><a href="{@docRoot}distribute/googleplay/policies/ip.html">Learn more &raquo;</a>
+  </div>
+  <div class="layout-content-col span-4">
+    <h4>
+      Ads
+    </h4>
+    <p>
+      Make sure that the ads displayed in your app follow the Google Play Content
+      Policy and meet the maturity rating that you have selected for your app.
+    </p><a href="{@docRoot}distribute/googleplay/policies/ads.html">Learn more &raquo;</a>
+  </div>
+</div>
\ No newline at end of file
diff --git a/docs/html/distribute/googleplay/policies/ip.jd b/docs/html/distribute/googleplay/policies/ip.jd
new file mode 100644
index 0000000..0d1f68d
--- /dev/null
+++ b/docs/html/distribute/googleplay/policies/ip.jd
@@ -0,0 +1,345 @@
+page.title=Intellectual Property
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+  <h2>In This Document</h2>
+  <ol>
+    <li><a href="#copyright">Copyright Infringement</a></li>
+    <li><a href="#impersonation">Impersonation</a></li>
+    <li><a href="#trademarks">Trademark Infringement</a></li>
+    <li><a href="#other">DDA 4.4 Prohibited Actions</a></li>
+  </ol>
+
+  <h2>More Resources</h2>
+  <ol>
+    <li><a href="http://play.google.com/about/developer-content-policy.html"
+    target="_policies">Developer Program Policies</a></li>
+    <li><a href="http://www.android.com/us/developer-distribution-agreement.html#showlanguages"
+    target="_policies">Developer Distribution Agreement</a></li>
+  </ol>
+</div>
+</div>
+
+<p>
+  Google Play policies protect your intellectual property (IP) as well as that
+  of other app developers and content creators in the store. The policies and
+  their enforcements help ensure proper use of copyright, trademarks, and
+  developer identity in Google Play.
+</p>
+
+<p>
+  As an app developer, these IP policies benefit you. At the same time, it's
+  your responsibility to ensure that your app does not violate the IP of other
+  developers or content creators. Violations of IP-related policy may result in
+  suspension of your apps from the store and termination of your developer
+  account.
+</p>
+
+<p>
+  This document introduces several key areas of IP-related policy that you
+  should understand before publishing on Google Play. In each area you'll find
+  best practices and examples to help you avoid common types of mistakes and
+  violations.
+</p>
+
+<p>
+  For more information about Google Play policies that apply to your apps and
+  content, please see the <a href=
+  "http://play.google.com/about/developer-content-policy.html" target=
+  "_policies">Developer Program Policies</a> and <a href=
+  "http://play.google.com/about/developer-distribution-agreement.html" target=
+  "_policies">Developer Distribution Agreement</a>.
+</p>
+
+
+
+<h2 id="copyright">Copyright Infringement</h2>
+
+<p>
+  Copyright is the legal right granted to an author or creator for a literary,
+  dramatic or artistic piece of work. As soon as you create an original piece
+  of work and fix it in a tangible medium, the work is automatically protected
+  by copyright law and you are the owner of the copyright. Likewise, when other
+  people create content, they may own the copyrights for those works.
+</p>
+
+
+<div class="sidebox-wrapper">
+<div class="sidebox">
+<h2>How to report infringements</h2>
+<p>If you feel your copyright is being infringed, you may file a Digital Millenium
+   Copyright Act (DMCA) request. Please see <a 
+   href="http://support.google.com/bin/request.py?&product=androidmarket&contact_type=lr_dmca"
+   target="_policies">copyright procedures</a> for more information.</p>
+</div>
+</div>
+
+<p>
+  Copyright infringement is an improper or unauthorized use of a copyrighted
+  work. If you publish an app in Google Play that uses another party's copyrighted
+  works improperly or without permission, your apps can be suspended and your
+  developer account terminated.
+</p>
+
+<p>
+  As you design your app and prepare for publishing, make sure to review Google
+  Play policies and analyze all of your content. If your app uses or links to
+  another party's original work, make sure that your app is not infringing on
+  copyright. Not all uses of another party’s work are infringements on
+  copyright, and the rules vary by country and can be complex.
+</p>
+
+<p>
+  If you are unsure whether your use of another party's work infringes on a
+  copyright, consider getting legal advice before publishing, or simply request
+  permission to use the work from the copyright owner.
+</p>
+
+<p>
+  Here are some guidelines to help you avoid copyright infringement policy
+  violations:
+</p>
+
+<ul>
+  <li>
+    <strong>Respect copyright laws</strong>&mdash;Do not let your app infringe
+    on the copyrights of others. That includes linking to other apps or web
+    sites that contain obviously infringing material (please refer to the <a href="
+    {@docRoot}distribute/googleplay/policies/spam.html#webview-spam">Spam in WebViews</a> guidelines), and using icons or images that are obvious infringements.
+  </li>
+
+  <li>
+    <strong>Know your app's content</strong>&mdash;Before you publish, look
+    for content that may be protected by trademark or copyright in your app
+    and get legal advice if necessary. Protected work could typically include
+    product names, brands, images, music, and similar works.
+  </li>
+
+  <li>
+    <strong>Create original work</strong>&mdash;If you’re not sure whether
+    something will violate another party's copyright, the safest approach is to
+    create something that's completely original, such as images or audio
+    that you’ve created yourself. When you create your own original content,
+    you rarely have to worry about infringing on existing copyright.
+  </li>
+
+  <li>
+    <strong>Ask permission to use copyrighted work</strong>&mdash;If you want
+    to use another party's copyrighted work in your app, you should ask for
+    permission from the work's creator or copyright owner and include
+    appropriate copyright attribution.
+  </li>
+</ul>
+
+<p>
+  A common misunderstanding is believing that your app may use copyrighted
+  content without permission, provided that you clearly indicate that your app
+  is not the "official" app that readers may be familiar with. That is not the
+  case. Even if you let users know that your app is "unofficial", it still
+  violates Google Play policies if it uses or links to copyrighted content
+  without permission. Also, this type of "unofficial" app may violate <a
+  href="#impersonation">impersonation policies</a>.
+</p>
+
+<p>
+  The example app below shows an app that uses screenshots/images of known
+  artists without their authorization and lists popular songs. The combination
+  of these may induce users to download music ringtones that infringe on
+  copyright. This is a violation of Google Play policy.
+</p>
+
+<div class="example-block bad" style="width:100%;float:none;margin:.5em auto 2em 0;">
+  <div class="heading">Images and downloads that violate copyright</div>
+  <img src="{@docRoot}images/gp-policy-ip-copyright-violation.png">
+</div>
+
+
+<h2 id="impersonation">Impersonation</h2>
+
+<p>
+  Impersonation is when an app attempts to imply a relationship to another app
+  or developer, where no relationship actually exists.
+</p>
+
+<p>
+  For example, if your app displays the brand, icon, or title from another app
+  in order to get to users to download your app, you are leading users to
+  believe that your app is developed by the same entity as the other app and
+  offers similar content or experience. This is an impersonation of the other
+  app and developer, and it is a violation of Google Play policy. If you
+  publish apps that violate impersonation policies, your apps can be suspended
+  and your developer account terminated.
+</p>
+
+<p>
+  No matter what type of app you offer or what your motivation, don’t try to
+  imply an endorsement or relationship to another company or product where none
+  exists. Don’t try to establish your app as the "official" version of another
+  party's work by prominently featuring their brand names or trademarks in your
+  app title or description.
+</p>
+
+<p>
+  Even if your app description states that your app is an "unofficial" version,
+  the use of the other app's branding, trademarks, and other content still can
+  violate policy by presenting content that isn’t yours.
+</p>
+
+<p>
+  Here are some guidelines:
+</p>
+
+<ul>
+  <li>
+    <strong>Don't pretend to be someone else</strong>&mdash; Don't represent
+    that your content is produced by another company or organization if that is
+    not the case.
+  </li>
+
+  <li>
+    <strong>Don't support infringing sites or apps</strong>&mdash; Don't divert
+    users or provide links to any other site that mimics Google Play or
+    represents itself as another application or service.
+  </li>
+
+  <li>
+    <strong>Don't use another app's branding</strong>&mdash; Don’t try to pass
+    off your app as the official version of someone else’s property by using a
+    person or entity (or brand) name in your app title or description.
+  </li>
+</ul>
+
+<p>
+  Below is an example of an "unofficial" app that violates Google Play policy
+  by impersonating another company and an existing product. Specifically:
+</p>
+
+<ul>
+  <li>The example app has a name and icon that appear to be impersonating an
+  existing product.
+  </li>
+
+  <li>The example developer name implies an endorsement or relationship to
+  another company and their products where none exists.
+  </li>
+</ul>
+
+<div class="example-block bad" style="width:100%;float:none;margin:.5em auto 2em 0;">
+  <div class="heading">App name, icon, and developer name that impersonate another</div>
+  <img src="{@docRoot}images/gp-policy-ip-impersonation-violation.png">
+</div>
+
+
+<h2 id="trademarks">Trademark Infringement</h2>
+
+<p>
+  A trademark is a brand that uniquely identifies a product and distinguishes
+  it from other products. It can be a word, name, symbol, or combination of
+  those that is intended to identify the source of the product. A trademark is
+  specifically acquired by a company or other entity through a legal process
+  and once acquired gives the owner exclusive rights to the trademark usage.
+</p>
+
+<div class="sidebox-wrapper">
+<div class="sidebox">
+<h2>How to report infringements</h2>
+<p>If you feel your trademark is being infringed, you can request a content review.
+See <a href="http://support.google.com/bin/static.py?&ts=1114905&page=ts.cs"
+target="_policies">Removing content from Google</a> for more information.</p>
+</div>
+</div>
+
+<p>
+  Trademark infringement is improper or unauthorized use of a trademark. Google
+  Play policies prohibit apps that infringe trademarks. If you publish apps in
+  Google Play that use another party's trademarks, your apps can be suspended
+  and your developer account terminated.
+</p>
+
+<p>
+  As you design your app and prepare for publishing, make sure to review Google
+  Play policies and analyze all of your content. If your app uses a trademark
+  not owned by you, or if you are not sure whether a brand is a trademark, you
+  should get legal advice before publishing. As with copyright, the rules vary
+  by country and can be complex.
+</p>
+
+<p>
+  Here are some guidelines for avoiding trademark infringement policy
+  violations:
+</p>
+
+<ul>
+  <li>
+    <strong>Understand and follow trademark laws</strong>&mdash;Don't let your
+    app infringe on the trademarks of others.
+  </li>
+
+  <li>
+    <strong>Know your app's content</strong>&mdash;Before you publish, look for
+    brands and potential trademarks used in your app and store listing and get
+    legal advice if necessary.
+  </li>
+
+  <li>
+    <strong>Use a distinct name</strong>&mdash;Don't give your app a name that
+    is confusingly similar to another company's trademark.
+  </li>
+
+  <li>
+    <strong>Don't use trademarks to imply a relationship</strong>&mdash;Don't
+    describe your app using another company's trademarks in a way that implies
+    an endorsement by or affiliation with the other company.
+  </li>
+
+  <li>
+    <strong>Use a distinct app icon and logo</strong>&mdash;Don't use a
+    modified version of another company’s trademarked logo.
+  </li>
+</ul>
+
+<p>
+  A common misunderstanding is believing that your app may use a brand or
+  trademark without permission, provided you clearly indicate that the app is
+  not the "official" or original app. That is not the case. Even if you let
+  users know that your app is "unofficial", it still violates Google Play
+  policies if it uses another party's trademarks. Also, this type of
+  "unofficial" app may violate <a href="#impersonation">impersonation
+  policies</a>.
+</p>
+
+<p>
+  Below is an example app that violates Google Play policies by infringing on
+  another party's trademarks. Specifically:
+</p>
+
+<ul>
+  <li>The example app name is confusingly similar to another party's trademark.</li>
+  <li>The example app icon is a modified version of a another party's logo.</li>
+</ul>
+
+<div class="example-block bad" style="width:100%;float:none;margin:.5em auto 2em 0;">
+  <div class="heading">App name and icon that infringe trademarks</div>
+  <img src="{@docRoot}images/gp-policy-ip-trademark-violation.png">
+</div>
+
+
+<h2 id="other">DDA 4.4 Prohibited Actions</h2>
+
+<p>
+  When you publish an app on Google Play, you agree to the terms of the
+  Developer Distribution Agreement (DDA). Section 4.4 of the DDA prohibits certain
+  types of actions on your part. For reference, you agree that you will not
+  engage in any activity with the Market, including the development or
+  distribution of Products, that interferes with, disrupts, damages, or
+  accesses in an unauthorized manner the devices, servers, networks, or other
+  properties or services of any third party including, but not limited to,
+  Android users, Google or any mobile network operator.
+</p>
+
+<p>
+  For details, please refer to the complete <a href=
+  "http://play.google.com/about/developer-distribution-agreement.html" target=
+  "_policies">Developer Distribution Agreement</a>.
+</p>
\ No newline at end of file
diff --git a/docs/html/distribute/googleplay/policies/spam.jd b/docs/html/distribute/googleplay/policies/spam.jd
new file mode 100644
index 0000000..602c89a
--- /dev/null
+++ b/docs/html/distribute/googleplay/policies/spam.jd
@@ -0,0 +1,421 @@
+page.title=Spam
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+  <h2>In This Document</h2>
+  <ol>
+    <li><a href="#keyword-spam">Spam in App Title and Description</a></li>
+    <li><a href="#ratings">Spam in Ratings and Reviews</a></li>
+    <li><a href="#webview-spam">Spam in WebViews</a></li>
+    <li><a href="#wizard-spam">Spam from Wizards</a></li> 
+    <li><a href="#message-spam">Spam in Messaging</a></li>
+  </ol>
+
+  <h2>More Resources</h2>
+  <ol>
+    <li><a href="http://play.google.com/about/developer-content-policy.html" target="_policies">Developer Program Policies</a></li>
+    <li><a href="http://play.google.com/about/developer-distribution-agreement.html" target="_policies">Developer Distribution Agreement</a></li>
+  </ol>
+</div>
+</div>
+
+<p>
+  Google Play policies prohibit spam, to help ensure the best experience for
+  Android users. Please do not publish deceptive, repetitive, or irrelevant
+  content on Google Play. Not only will it lower your app's rating and cause
+  negative reviews, it can result in your app being suspended or your developer
+  account terminated.
+</p>
+
+<p>
+  As an app developer, it is your responsibility to ensure that your apps are
+  free from spam and conform to the Google Play policies highlighted in this
+  document. Before you publish, make sure that you understand what is
+  considered spam on Google Play and check your apps for violations, even those
+  that might be inadvertent. The sections below highlight best practices and
+  common spam examples to help you avoid the most common types of policy
+  violations.
+</p>
+
+<p>
+  For more information about Google Play policies that apply to your apps and
+  content, please see the <a href=
+  "http://play.google.com/about/developer-content-policy.html" target=
+  "_policies">Developer Program Policies</a> and <a href=
+  "http://play.google.com/about/developer-distribution-agreement.html" target=
+  "_policies">Developer Distribution Agreement</a>.
+</p>
+
+
+<h2 id="keyword-spam">Spam in App Title and Description</h2>
+
+<p>
+  When you publish an app on Google Play, you should pay special attention to
+  the app's title and description in its store listing. Those fields are
+  important because they make your app recognizable to users, and they help to
+  drive downloads by highlighting what's great about your app. A memorable
+  title and compelling description are essential to effective marketing, but
+  you should realize that these must follow Google Play policies, just as your
+  app content must do.
+</p>
+
+<p>
+  Many developers unknowingly violate spam policy in their app titles and
+  descriptions in ways that are easy to avoid. In general, you can
+  avoid spam violations in your app title and description by following these
+  best practices:
+</p>
+
+<ul>
+  <li>
+    <strong>Highlight what's great about your app</strong>&mdash;Share
+    interesting and exciting facts about your app with users. Help users
+    understand what makes your app special.
+  </li>
+
+  <li>
+    <strong>Describe your app accurately</strong>&mdash;Make sure the title
+    and description describe the app function and user experience accurately.
+  </li>
+
+  <li>
+    <strong>Don't use repetitive keywords</strong>&mdash;Avoid keywords that
+    are repetitive or excessive.
+  </li>
+
+  <li>
+    <strong>Don't include unrelated keywords or references</strong> &mdash;
+    Your description should not be loaded with irrelevant keywords in an
+    attempt to manipulate ranking or relevancy.
+  </li>
+
+  <li>
+    <strong>Keep it brief</strong>&mdash;Keep the description succinct and
+    straightforward. Shorter descriptions tend to give a better user experience
+    on devices with smaller displays. Excessive length, detail, or repetition
+    can violate spam policy.
+  </li>
+</ul>
+
+<p>
+  Here's an example app title and description that follows best practices and
+  does not violate Google Play spam policies.
+</p>
+
+<div class="example-block good" style="width:100%;float:none;margin:.5em auto 2em 0;">
+  <div class="heading">Best practice: App description</div>
+  <table>
+  <tr>
+    <td>App Title:</td>
+    <td>Kids puzzle: Identify Turtles</td>
+  </tr>
+  <tr>
+    <td style="white-space:nowrap;">App Description:</td>
+    <td>
+      <p>This is the perfect app to have a good time with your children. It
+        is designed to help kids learn different species of turtles through
+        cute pictures and amusing puzzle games.</p>
+      <p>The rules of Kids puzzle: Identify Turtles are quite simple. Have
+        your child drag images around the screen to fit them into the shaded
+        region. Phonics is also utilized, as a child can also tap the word
+        below the image and hear the name pronounced.</p>
+    </td>
+  </tr>
+  </table>
+</div>
+
+<p>
+  The sections below highlight common types of policy violations in an app
+  title and description, illustrated with variations on the best practice
+  example. 
+</p>
+
+<h3 id="repetitive-keywords">Repetitive keywords</h3>
+
+<p>
+  Your app description should not include keywords that are repetitive or excessive.
+</p>
+
+<div class="example-block bad" style="width:100%;float:none;margin:.5em auto 2em 0;">
+  <div class="heading">Description includes repetitive keywords</div>
+  <table>
+  <tr>
+    <td>App Title:</td>
+    <td>Kids puzzle: Identify Turtles</td>
+  </tr>
+  <tr>
+    <td style="white-space:nowrap;">App Description:</td>
+    <td>
+      <p>This is the perfect app to have a good time with your children. It is
+        designed to help kids learn different species of turtles through cute
+        pictures and amusing puzzle games.</p>
+      <p>The rules of Kids puzzle: Identify Turtles are quite simple. Have your
+        child drag images around the screen to fit them into the shaded region.
+        Phonics is also utilized, as a child can also tap the word below the image
+        and hear the name pronounced.</p>
+      <p style="border:2px solid red;">KEYWORDS: game, games, fun, funny, child,
+        children, kid, kids, puzzle, puzzle games, sound, turtle, turtles, sea turtles,
+        turtles, turtle, turtles, tortoise, tortoises, tortoise, tortoise,  turtles,
+        turtles, turtles, turtles, tortoises, tortoise</p>
+    </td>
+  </tr>
+  </table>
+</div>
+
+<h3 id="unrelated-keywords">Unrelated keywords or references</h3>
+
+<p>
+  The description should not be loaded with irrelevant keywords in an attempt
+  to manipulate ranking or relevancy in Google Play search results.
+</p>
+
+<p>
+  For example, if your app has nothing to do with Lady Gaga, then she shouldn’t
+  be included in your description. Also, do not add highly searched, irrelevant
+  keywords that are unrelated to the function of the app. This is in breach of
+  policy.
+</p>
+
+<div class="example-block bad" style="width:100%;float:none;margin:.5em auto 2em 0;">
+  <div class="heading">Description includes unrelated keywords or references</div>
+  <table>
+  <tr>
+    <td>App Title:</td>
+    <td>Kids puzzle: Identify Turtles</td>
+  </tr>
+  <tr>
+    <td style="white-space:nowrap;">App Description:</td>
+    <td>
+      <p>This is the perfect app to have a good time with your children. It is designed to
+        help kids learn different species of turtles through cute pictures and amusing puzzle
+        games.</p>
+      <p>The rules of Kids puzzle: Identify Turtles are quite simple. Have your child drag
+        images around the screen to fit them into the shaded region. Phonics is also utilized,
+        as a child can also tap the word below the image and hear the name pronounced.</p>
+      <p style="border:2px solid red;">This game is as addictive as Angry Birds, more social
+        than Facebook and Twitter, and has a soundtrack reminiscent of Katy Perry and Lady
+        Gaga.</p>
+      <p style="border:2px solid red;">KEYWORDS: Angry Birds, Facebook, Twitter, Katy Perry,
+        Lady Gaga</p>
+    </td>
+  </tr>
+  </table>
+</div>
+
+<h3 id="excessive-detail">Excessive detail, references to your other apps</h3>
+
+<p>
+  Your app description should avoid excessive detail and references to your
+  other apps or products. For example, you should not list all of the details
+  of content included in the app or its various components, as shown in the
+  example below. Also, the description should not include any references to
+  other apps you’ve published.
+</p>
+
+<div class="example-block bad" style="width:100%;float:none;margin:.5em auto 2em 0;">
+  <div class="heading">Description includes excessive detail, references to your other apps</div>
+  <table>
+  <tr>
+    <td>App Title:</td>
+    <td>Kids puzzle: Identify Turtles</td>
+  </tr>
+  <tr>
+    <td style="white-space:nowrap;">App Description:</td>
+    <td>
+      <p>This is the perfect app to have a good time with your children. It is designed
+        to help kids learn different species of turtles through cute pictures and amusing
+        puzzle games.</p>
+      <p>The rules of Kids puzzle: Identify Turtles are quite simple. Have your child
+        drag images around the screen to fit them into the shaded region. Phonics is also
+        utilized, as a child can also tap the word below the image and hear the name
+        pronounced.</p>
+      <p style="border:2px solid red;">Turtles included in the app: Alligator
+        Snapping Turtle, Asian Box Turtle, Bog Turtle, Common Musk Turtle, Common Snapping
+        Turtle, Diamondback Terrapin, Eastern Box Turtle, Eastern Mud Turtle, Eastern Painted
+        Turtle, False Map Turtle, Florida Pond Cooter, Florida Softshell Turtle, Green Sea
+        Turtle, Map Turtle, Matamata Ornate Box Turtle, Red-bellied Side-necked Turtle,
+        Red-eared Slider, Smooth Softshell Turtle, Spiny Softshell Turtle, Spotted Turtle,
+        Western Painted Turtle, Wood Turtle, Yellow-bellied Slider</p>
+      <p style="border:2px solid red;">If you like this app try our other free apps:<br />
+       ★ Fun Zoo<br />
+       ★ CD Guns<br />
+       ★ Dessert House<br />
+       ★ Playground<br />
+       ★ 578 Weapons</p>
+    </td>
+  </tr>
+  </table>
+</div>
+
+
+<h2 id="ratings">Spam in Ratings and Reviews</h2>
+
+<p>
+  Ratings and reviews are benchmarks of app quality and users depend on them to
+  be authentic and relevant. As an app developer, you should not attempt to
+  artificially influence your app's ratings and reviews or those of your
+  competitor, such as by posting fake ratings or reviews or including spam
+  content in app reviews. The sections below provide guidelines for rating and
+  reviewing apps.
+</p>
+
+<p>
+  So that you can stay in touch with any issues that users are having with your
+  app, you should read through your ratings and reviews on a regular basis. If
+  you choose to reply to reviews, make sure to keep your reply focused on the
+  actual issues raised in the user's comments and do not ask for a higher
+  rating.
+</p>
+
+<p>
+  If you see an app or developer reply that doesn’t follow these guidelines,
+  you can report it. See <a href=
+  "http://support.google.com/googleplay/android-developer/bin/answer.py?hl=en&answer=113417&topic=2364761&ctx=topic"
+  target="_policies">Inappropriate content in comments and applications</a> for
+  more information.
+</p>
+
+<div class="example-block bad" style="width:440px;">
+  <div class="heading">Inappropriate content in a review</div>
+  <img src="{@docRoot}images/gp-policy-spam-negreview.png">
+</div>
+
+<div class="example-block bad" style="margin-top:3em;">
+  <div class="heading">Soliciting ratings</div>
+  <img src="{@docRoot}images/gp-policy-spam-reqrating.png">
+</div>
+
+<h3 id="fake-ratings">Fake or inappropriate ratings and reviews</h3>
+
+<p>
+  To help ensure the quality of ratings and reviews, Google Play policies limit
+  the ways that individuals can use ratings and reviews. In particular, note
+  that it is a violation of policy to use ratings and reviews to influence the
+  placement of any app in Google Play.
+</p>
+
+<p>
+  As an app developer, make sure that you follow these guidelines:
+</p>
+
+<ul>
+  <li>
+    <strong>Don't try to manipulate ratings</strong>&mdash;Do not engage in
+    attempts to manipulate the ratings, reviews, or ranking of your apps,
+    either directly or indirectly, or by manipulating the ratings of your
+    competitors. Do not attempt to artificially boost reviews, ratings, or
+    installs through any means.
+  </li>
+
+  <li>
+    <strong>Don't solicit ratings through incentives</strong>&mdash;Do not
+    offer users any incentives to rate your app, such as offering rewards of
+    any kind or tying app functionality to rating.
+  </li>
+
+  <li>
+    <strong>Don't rate apps multiple times</strong>&mdash;Do not review or
+    rate any app multiple times in an attempt to influence its placement in
+    Google Play.
+  </li>
+
+  <li>
+    <strong>Don't add improper content to reviews</strong>&mdash;Do not
+    include affiliate, coupon, game codes, email addresses, or links to
+    websites or other apps in your reviews. If you are responding to a user
+    review, feel free to include references to helpful resources such as a
+    support address or FAQ page.
+  </li>
+</ul>
+
+<h3 id="solicited-ratings">Soliciting ratings from users</h3>
+
+<p>
+  In general, <strong>do not offer incentives for ratings</strong>. You should
+  not offer users incentives of any kind for rating your app (or any other app)
+  on Google Play, and you should not tie your app's functionality or content to
+  rating in any way.
+</p>
+
+<p>
+  It's acceptable to ask users to rate your app without incentives, for
+  example: "If you like this game, rate us in Google Play!" On the other hand,
+  it's a policy violation to ask users to rate your app based on incentives,
+  for example: "Rate this app and get 500 coins" or "Rate this app 5 stars and
+  get you 500 coins!"
+</p>
+
+
+<h2 id="webview-spam" style="clear:right">Spam in WebViews</h2>
+
+<p>
+  Apps published on Google Play should provide their own content. Do not
+  publish an app whose primary function is to reproduce or frame someone else’s
+  website (unless you have permission).
+</p>
+
+<p>
+  Similarly, do not publish an app whose primary function is to drive affiliate
+  traffic to a website. Although affiliate deals can exist where an app's
+  primary purpose is delivering its own content or functionality, it's a
+  violation of Google Play policies to publish an app whose primary (or
+  only) purpose is to direct affiliate traffic to another website.
+</p>
+
+<div class="example-block bad" style="width:100%;float:none;margin:.5em auto 2em 0;">
+  <div class="heading">WebView spam</div>
+  <table>
+  <tr>
+    <td>App Title:</td>
+    <td>Kids puzzle: Desktop Browser for Turtoogle Game</td>
+  </tr>
+  <tr>
+    <td>Developer:</td>
+    <td>AAZZZ <span style="border:2px solid red;">(not affiliated with Turtoogle
+      Inc.)</span></td>
+  </tr>
+  <tr>
+    <td style="white-space:nowrap;">App Description:</td>
+    <td>
+      <p>Have you ever wanted to use the full, desktop web version of Turtoogle
+        Game from your phone or tablet instead of the Turtoogle Game mobile app
+        or Turtoogle Game mobile web site?</p>
+      <p style="border:2px solid red;">This app lets you access Turtoogle Game
+        on your Android device in the same way as you access the game on your
+        desktop computer, and with all the same Turtoogle Game features.</p>
+    </td>
+  </tr>
+  </table>
+</div>
+
+
+<h2 id="wizard-spam">Spam from Wizards</h2>
+
+<p>
+  Apps that are created by an automated tool or wizard service must not be
+  submitted to Google Play by the operator of that service on behalf of other
+  persons. Such tools often produce too many duplicative or low-quality
+  apps which crowd the higher-quality apps in the Play Store.
+</p>
+
+<p>
+  Please be advised that apps created by an automated tool are only permissible
+  if the app end-product complies with Google Play policies and is published in
+  the Play Store through a developer account that is registered and owned by
+  you.
+</p>
+
+
+<h2 id="message-spam">Spam in Messaging</h2>
+
+<p>
+  Your app may not send SMS, email, or other messages on behalf of the user
+  without providing the user with the ability to confirm the content and intended
+  recipient.
+</p>
+
+<p>
+  Google Play will aggressively remove applications that are found to send or
+  modify SMS messages without user knowledge or consent.
+</p>
\ No newline at end of file
diff --git a/docs/html/images/example-bad.png b/docs/html/images/example-bad.png
new file mode 100644
index 0000000..b19a9f7
--- /dev/null
+++ b/docs/html/images/example-bad.png
Binary files differ
diff --git a/docs/html/images/example-good.png b/docs/html/images/example-good.png
new file mode 100644
index 0000000..6bd2408
--- /dev/null
+++ b/docs/html/images/example-good.png
Binary files differ
diff --git a/docs/html/images/gp-policy-ads-eula-violation.png b/docs/html/images/gp-policy-ads-eula-violation.png
new file mode 100644
index 0000000..e8ffa5b
--- /dev/null
+++ b/docs/html/images/gp-policy-ads-eula-violation.png
Binary files differ
diff --git a/docs/html/images/gp-policy-ads-eula.png b/docs/html/images/gp-policy-ads-eula.png
new file mode 100644
index 0000000..68a6b95
--- /dev/null
+++ b/docs/html/images/gp-policy-ads-eula.png
Binary files differ
diff --git a/docs/html/images/gp-policy-ads-impersonate-violation.png b/docs/html/images/gp-policy-ads-impersonate-violation.png
new file mode 100644
index 0000000..385ae6e
--- /dev/null
+++ b/docs/html/images/gp-policy-ads-impersonate-violation.png
Binary files differ
diff --git a/docs/html/images/gp-policy-ads-maturity-violation.png b/docs/html/images/gp-policy-ads-maturity-violation.png
new file mode 100644
index 0000000..d41870e
--- /dev/null
+++ b/docs/html/images/gp-policy-ads-maturity-violation.png
Binary files differ
diff --git a/docs/html/images/gp-policy-ads-notif-attr-violation.png b/docs/html/images/gp-policy-ads-notif-attr-violation.png
new file mode 100644
index 0000000..af53f10
--- /dev/null
+++ b/docs/html/images/gp-policy-ads-notif-attr-violation.png
Binary files differ
diff --git a/docs/html/images/gp-policy-ads-notif-attr.png b/docs/html/images/gp-policy-ads-notif-attr.png
new file mode 100644
index 0000000..4934d21
--- /dev/null
+++ b/docs/html/images/gp-policy-ads-notif-attr.png
Binary files differ
diff --git a/docs/html/images/gp-policy-ads-paywall-violation.png b/docs/html/images/gp-policy-ads-paywall-violation.png
new file mode 100644
index 0000000..8bbfd1b
--- /dev/null
+++ b/docs/html/images/gp-policy-ads-paywall-violation.png
Binary files differ
diff --git a/docs/html/images/gp-policy-ads-paywall.png b/docs/html/images/gp-policy-ads-paywall.png
new file mode 100644
index 0000000..e7b1e19
--- /dev/null
+++ b/docs/html/images/gp-policy-ads-paywall.png
Binary files differ
diff --git a/docs/html/images/gp-policy-ads-terms.png b/docs/html/images/gp-policy-ads-terms.png
new file mode 100644
index 0000000..dcbdf4a
--- /dev/null
+++ b/docs/html/images/gp-policy-ads-terms.png
Binary files differ
diff --git a/docs/html/images/gp-policy-ip-copyright-violation.png b/docs/html/images/gp-policy-ip-copyright-violation.png
new file mode 100644
index 0000000..a4e96a8
--- /dev/null
+++ b/docs/html/images/gp-policy-ip-copyright-violation.png
Binary files differ
diff --git a/docs/html/images/gp-policy-ip-impersonation-violation.png b/docs/html/images/gp-policy-ip-impersonation-violation.png
new file mode 100644
index 0000000..b1d9923
--- /dev/null
+++ b/docs/html/images/gp-policy-ip-impersonation-violation.png
Binary files differ
diff --git a/docs/html/images/gp-policy-ip-trademark-violation.png b/docs/html/images/gp-policy-ip-trademark-violation.png
new file mode 100644
index 0000000..c05b67b
--- /dev/null
+++ b/docs/html/images/gp-policy-ip-trademark-violation.png
Binary files differ
diff --git a/docs/html/images/gp-policy-spam-negreview.png b/docs/html/images/gp-policy-spam-negreview.png
new file mode 100644
index 0000000..f68eba3
--- /dev/null
+++ b/docs/html/images/gp-policy-spam-negreview.png
Binary files differ
diff --git a/docs/html/images/gp-policy-spam-reqrating.png b/docs/html/images/gp-policy-spam-reqrating.png
new file mode 100644
index 0000000..aaf9e53
--- /dev/null
+++ b/docs/html/images/gp-policy-spam-reqrating.png
Binary files differ
diff --git a/docs/html/tools/extras/oem-usb.jd b/docs/html/tools/extras/oem-usb.jd
index 774fe87..87734a1 100644
--- a/docs/html/tools/extras/oem-usb.jd
+++ b/docs/html/tools/extras/oem-usb.jd
@@ -306,6 +306,10 @@
 <tr><td>MTK</td> <td><a
 href="http://online.mediatek.com/Public%20Documents/MTK_Android_USB_Driver.zip">http://online.mediatek.com/Public%20Documents/MTK_Android_USB_Driver.zip</a></td>
 </tr>
+<tr><td>Oppo</td> <td><a
+href="http://www.oppo.com/index.php?q=software/view&sw_id=631"
+>http://www.oppo.com/index.php?q=software/view&sw_id=631</a></td>
+</tr>
 <tr><td>Pantech</td>	<td><a
 href="http://www.isky.co.kr/cs/software/software.sky?fromUrl=index">http://www.isky.co.kr/cs/software/software.sky?fromUrl=index</a></td>
 </tr><tr><td>Pegatron</td>	<td><a
@@ -327,6 +331,9 @@
 href="http://www.yulong.com/product/product/product/downloadList.html#downListUL">http://www.yulong.com/product/product/product/downloadList.html#downListUL</a></td>
 </tr>
 
+<tr><td>Xiaomi</td>  <td><a
+href="http://www.xiaomi.com/c/driver/index.html">http://www.xiaomi.com/c/driver/index.html</a></td>
+</tr>
 <tr>
 <td>ZTE</td>	<td><a
 href="http://support.zte.com.cn/support/news/NewsDetail.aspx?newsId=1000442">http://support.zte.com.cn/support/news/NewsDetail.aspx?newsId=1000442</a></td></tr>
diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java
index ea29b7d..5d1990a 100644
--- a/graphics/java/android/renderscript/Allocation.java
+++ b/graphics/java/android/renderscript/Allocation.java
@@ -277,21 +277,13 @@
                 throw new RSIllegalArgumentException("Invalid usage combination.");
             }
         }
-        if (t != null) {
-            // don't need to account for USAGE_SHARED Allocations
-            if ((usage & USAGE_SHARED) == 0) {
-                int numBytes = t.getCount() * t.getElement().getBytesSize();
-                rs.addAllocSizeForGC(numBytes);
-                mGCSize = numBytes;
-            }
-        }
+
         mType = t;
         mUsage = usage;
 
         if (t != null) {
             updateCacheInfo(t);
         }
-
     }
 
     private void validateIsInt32() {
@@ -355,12 +347,6 @@
             mType.updateFromNative();
             updateCacheInfo(mType);
         }
-        // don't need to account for USAGE_SHARED Allocations
-        if ((mUsage & USAGE_SHARED) == 0) {
-            int numBytes = mType.getCount() * mType.getElement().getBytesSize();
-            mRS.addAllocSizeForGC(numBytes);
-            mGCSize = numBytes;
-        }
     }
 
     /**
@@ -1264,7 +1250,6 @@
         if (type.getID(rs) == 0) {
             throw new RSInvalidStateException("Bad Type");
         }
-
         int id = rs.nAllocationCreateTyped(type.getID(rs), mips.mID, usage, 0);
         if (id == 0) {
             throw new RSRuntimeException("Allocation creation failed.");
@@ -1414,6 +1399,7 @@
             return alloc;
         }
 
+
         int id = rs.nAllocationCreateFromBitmap(t.getID(rs), mips.mID, b, usage);
         if (id == 0) {
             throw new RSRuntimeException("Load failed.");
diff --git a/graphics/java/android/renderscript/BaseObj.java b/graphics/java/android/renderscript/BaseObj.java
index c2ebc9f..f464f9b 100644
--- a/graphics/java/android/renderscript/BaseObj.java
+++ b/graphics/java/android/renderscript/BaseObj.java
@@ -71,9 +71,6 @@
     private int mID;
     private boolean mDestroyed;
     private String mName;
-
-    int mGCSize;
-
     RenderScript mRS;
 
     /**
@@ -138,9 +135,6 @@
             throw new RSInvalidStateException("Object already destroyed.");
         }
         mDestroyed = true;
-        if (mGCSize != 0) {
-            mRS.removeAllocSizeForGC(mGCSize);
-        }
         mRS.nObjDestroy(mID);
     }
 
diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java
index 33639dc..6f614c3 100644
--- a/graphics/java/android/renderscript/RenderScript.java
+++ b/graphics/java/android/renderscript/RenderScript.java
@@ -18,9 +18,7 @@
 
 import java.io.File;
 import java.lang.reflect.Field;
-import java.util.concurrent.locks.*;
 
-import android.app.ActivityManager;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -804,8 +802,6 @@
     int     mContext;
     @SuppressWarnings({"FieldCanBeLocal"})
     MessageThread mMessageThread;
-    GCThread mGCThread;
-
 
     Element mElement_U8;
     Element mElement_I8;
@@ -1095,60 +1091,6 @@
         }
     }
 
-    static class GCThread extends Thread {
-        RenderScript mRS;
-        boolean mRun = true;
-
-        long currentSize = 0;
-        long targetSize; // call System.gc after 512MB of allocs
-
-        final Lock lock = new ReentrantLock();
-        final Condition cond = lock.newCondition();
-
-        GCThread(RenderScript rs) {
-            super("RSGCThread");
-            mRS = rs;
-
-        }
-
-        public void run() {
-            ActivityManager am = (ActivityManager)mRS.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
-            ActivityManager.MemoryInfo meminfo = new ActivityManager.MemoryInfo();
-            am.getMemoryInfo(meminfo);
-            targetSize = (long)(meminfo.totalMem * .5f);
-
-            while(mRun) {
-                System.gc();
-                lock.lock();
-                try {
-                    cond.awaitUninterruptibly();
-                } finally {
-                    lock.unlock();
-                }
-            }
-
-            Log.d(LOG_TAG, "GCThread exiting.");
-        }
-
-        public synchronized void addAllocSize(long bytes) {
-            currentSize += bytes;
-            if (currentSize >= targetSize) {
-                lock.lock();
-                try {
-                    cond.signal();
-                } finally {
-                    lock.unlock();
-                }
-            }
-        }
-
-        public synchronized void removeAllocSize(long bytes) {
-            currentSize -= bytes;
-        }
-
-    }
-
-
     RenderScript(Context ctx) {
         if (ctx != null) {
             mApplicationContext = ctx.getApplicationContext();
@@ -1171,15 +1113,6 @@
         return create(ctx, sdkVersion, ContextType.NORMAL);
     }
 
-    void addAllocSizeForGC(int bytes) {
-        mGCThread.addAllocSize(bytes);
-    }
-
-    void removeAllocSizeForGC(int bytes) {
-        mGCThread.removeAllocSize(bytes);
-    }
-
-
     /**
      * Create a basic RenderScript context.
      *
@@ -1196,9 +1129,7 @@
             throw new RSDriverException("Failed to create RS context.");
         }
         rs.mMessageThread = new MessageThread(rs);
-        rs.mGCThread = new GCThread(rs);
         rs.mMessageThread.start();
-        rs.mGCThread.start();
         return rs;
     }
 
@@ -1253,11 +1184,8 @@
         validate();
         nContextDeinitToClient(mContext);
         mMessageThread.mRun = false;
-        mGCThread.mRun = false;
-        mGCThread.addAllocSize(0);
         try {
             mMessageThread.join();
-            mGCThread.join();
         } catch(InterruptedException e) {
         }
 
diff --git a/graphics/java/android/renderscript/RenderScriptGL.java b/graphics/java/android/renderscript/RenderScriptGL.java
index fad8838..52034b1 100644
--- a/graphics/java/android/renderscript/RenderScriptGL.java
+++ b/graphics/java/android/renderscript/RenderScriptGL.java
@@ -198,9 +198,6 @@
         }
         mMessageThread = new MessageThread(this);
         mMessageThread.start();
-        mGCThread = new GCThread(this);
-        mGCThread.start();
-
     }
 
     /**
diff --git a/graphics/java/android/renderscript/ScriptC.java b/graphics/java/android/renderscript/ScriptC.java
index 2f69775..221f760 100644
--- a/graphics/java/android/renderscript/ScriptC.java
+++ b/graphics/java/android/renderscript/ScriptC.java
@@ -60,8 +60,6 @@
             throw new RSRuntimeException("Loading of ScriptC script failed.");
         }
         setID(id);
-        mGCSize = 2 * 1024 * 1024;
-        rs.addAllocSizeForGC(mGCSize);
     }
 
     /**
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 45385ee..fb5e039 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -103,14 +103,6 @@
         }
     }
 
-    public boolean put(String key, byte[] value, int uid) {
-        return put(key, value, uid, FLAG_ENCRYPTED);
-    }
-
-    public boolean put(String key, byte[] value) {
-        return put(key, value, UID_SELF);
-    }
-
     public boolean delete(String key, int uid) {
         try {
             return mBinder.del(key, uid) == NO_ERROR;
@@ -205,14 +197,6 @@
         }
     }
 
-    public boolean generate(String key, int uid) {
-        return generate(key, uid, FLAG_ENCRYPTED);
-    }
-
-    public boolean generate(String key) {
-        return generate(key, UID_SELF);
-    }
-
     public boolean importKey(String keyName, byte[] key, int uid, int flags) {
         try {
             return mBinder.import_key(keyName, key, uid, flags) == NO_ERROR;
@@ -222,14 +206,6 @@
         }
     }
 
-    public boolean importKey(String keyName, byte[] key, int uid) {
-        return importKey(keyName, key, uid, FLAG_ENCRYPTED);
-    }
-
-    public boolean importKey(String keyName, byte[] key) {
-        return importKey(keyName, key, UID_SELF);
-    }
-
     public byte[] getPubkey(String key) {
         try {
             return mBinder.get_pubkey(key);
diff --git a/keystore/tests/src/android/security/AndroidKeyStoreTest.java b/keystore/tests/src/android/security/AndroidKeyStoreTest.java
index 05ffe109..507d41c 100644
--- a/keystore/tests/src/android/security/AndroidKeyStoreTest.java
+++ b/keystore/tests/src/android/security/AndroidKeyStoreTest.java
@@ -504,11 +504,13 @@
 
         assertAliases(new String[] {});
 
-        assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1));
+        assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertAliases(new String[] { TEST_ALIAS_1 });
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2 });
     }
@@ -530,11 +532,13 @@
 
         assertAliases(new String[] {});
 
-        assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1));
+        assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertTrue("Should contain generated private key", mKeyStore.containsAlias(TEST_ALIAS_1));
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertTrue("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_2));
 
@@ -547,7 +551,8 @@
 
         mKeyStore.load(null, null);
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertTrue("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_2));
     }
@@ -567,15 +572,19 @@
 
         // TEST_ALIAS_1
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1));
+                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         // TEST_ALIAS_2
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         // TEST_ALIAS_3
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_3, FAKE_CA_1));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_3, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2, TEST_ALIAS_3 });
 
@@ -608,9 +617,11 @@
 
         // TEST_ALIAS_1
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1));
+                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         // Should not throw when a non-existent entry is requested for delete.
         mKeyStore.deleteEntry(TEST_ALIAS_2);
@@ -621,7 +632,8 @@
 
         mKeyStore.load(null, null);
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertAliases(new String[] { TEST_ALIAS_1 });
 
@@ -652,7 +664,8 @@
 
         mKeyStore.load(null, null);
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         CertificateFactory f = CertificateFactory.getInstance("X.509");
         Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
@@ -668,9 +681,11 @@
         mKeyStore.load(null, null);
 
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1));
+                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         CertificateFactory f = CertificateFactory.getInstance("X.509");
         Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
@@ -686,13 +701,16 @@
         mKeyStore.load(null, null);
 
         // Insert TrustedCertificateEntry with CA name
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         // Insert PrivateKeyEntry that uses the same CA
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1));
+                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         CertificateFactory f = CertificateFactory.getInstance("X.509");
         Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
@@ -719,7 +737,8 @@
 
         mKeyStore.load(null, null);
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         CertificateFactory f = CertificateFactory.getInstance("X.509");
         Certificate userCert = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
@@ -734,9 +753,11 @@
         mKeyStore.load(null, null);
 
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1));
+                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         CertificateFactory cf = CertificateFactory.getInstance("X.509");
         Certificate[] expected = new Certificate[2];
@@ -771,9 +792,11 @@
         mKeyStore.load(null, null);
 
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1));
+                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         Date now = new Date();
         Date actual = mKeyStore.getCreationDate(TEST_ALIAS_1);
@@ -810,7 +833,8 @@
 
         mKeyStore.load(null, null);
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         Date now = new Date();
         Date actual = mKeyStore.getCreationDate(TEST_ALIAS_1);
@@ -829,9 +853,11 @@
         mKeyStore.load(null, null);
 
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1));
+                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
         assertNotNull("Entry should exist", entry);
@@ -930,9 +956,11 @@
         mKeyStore.load(null, null);
 
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1));
+                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
         assertNotNull("Key should exist", key);
@@ -977,7 +1005,8 @@
 
         mKeyStore.load(null, null);
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertNull("Certificate entries should return null", mKeyStore.getKey(TEST_ALIAS_1, null));
     }
@@ -1006,7 +1035,8 @@
         setupPassword();
         mKeyStore.load(null, null);
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertTrue("Should return true for CA certificate",
                 mKeyStore.isCertificateEntry(TEST_ALIAS_1));
@@ -1017,9 +1047,11 @@
         mKeyStore.load(null, null);
 
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1));
+                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertFalse("Should return false for PrivateKeyEntry",
                 mKeyStore.isCertificateEntry(TEST_ALIAS_1));
@@ -1045,9 +1077,11 @@
         mKeyStore.load(null, null);
 
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1));
+                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertTrue("Should return true for PrivateKeyEntry", mKeyStore.isKeyEntry(TEST_ALIAS_1));
     }
@@ -1056,7 +1090,8 @@
         setupPassword();
         mKeyStore.load(null, null);
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertFalse("Should return false for CA certificate", mKeyStore.isKeyEntry(TEST_ALIAS_1));
     }
@@ -1089,7 +1124,8 @@
         setupPassword();
         mKeyStore.load(null, null);
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertAliases(new String[] { TEST_ALIAS_1 });
 
@@ -1107,9 +1143,11 @@
         mKeyStore.load(null, null);
 
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1));
+                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertAliases(new String[] { TEST_ALIAS_1 });
 
@@ -1596,7 +1634,8 @@
         // Create key #1
         {
             final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1;
-            assertTrue(mAndroidKeyStore.generate(privateKeyAlias));
+            assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF,
+                    KeyStore.FLAG_ENCRYPTED));
 
             Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
 
@@ -1608,7 +1647,7 @@
                     TEST_SERIAL_1, TEST_DN_1, NOW, NOW_PLUS_10_YEARS);
 
             assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
-                    expectedCert.getEncoded()));
+                    expectedCert.getEncoded(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
             Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
 
@@ -1651,25 +1690,27 @@
         // Create key #1
         {
             final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1;
-            assertTrue(mAndroidKeyStore.generate(privateKeyAlias));
+            assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF,
+                    KeyStore.FLAG_ENCRYPTED));
 
             X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_1,
                     TEST_SERIAL_1, TEST_DN_1, NOW, NOW_PLUS_10_YEARS);
 
             assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
-                    cert.getEncoded()));
+                    cert.getEncoded(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
         }
 
         // Create key #2
         {
             final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_2;
-            assertTrue(mAndroidKeyStore.generate(privateKeyAlias));
+            assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF,
+                    KeyStore.FLAG_ENCRYPTED));
 
             X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_2,
                     TEST_SERIAL_2, TEST_DN_2, NOW, NOW_PLUS_10_YEARS);
 
             assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_2,
-                    cert.getEncoded()));
+                    cert.getEncoded(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
         }
 
         // Replace key #1 with key #2
@@ -1731,17 +1772,20 @@
         setupPassword();
         mKeyStore.load(null, null);
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertEquals("The keystore size should match expected", 1, mKeyStore.size());
         assertAliases(new String[] { TEST_ALIAS_1 });
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertEquals("The keystore size should match expected", 2, mKeyStore.size());
         assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2 });
 
-        assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_3));
+        assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_3,
+                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertEquals("The keystore size should match expected", 3, mKeyStore.size());
         assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2, TEST_ALIAS_3 });
@@ -1807,13 +1851,14 @@
 
     private void setupKey() throws Exception {
         final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1;
-        assertTrue(mAndroidKeyStore.generate(privateKeyAlias));
+        assertTrue(mAndroidKeyStore
+                .generate(privateKeyAlias, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_1, TEST_SERIAL_1,
                 TEST_DN_1, NOW, NOW_PLUS_10_YEARS);
 
         assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
-                cert.getEncoded()));
+                cert.getEncoded(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
     }
 
     public void testKeyStore_KeyOperations_Wrap_Encrypted_Success() throws Exception {
diff --git a/keystore/tests/src/android/security/KeyStoreTest.java b/keystore/tests/src/android/security/KeyStoreTest.java
index 1de1eaf..815f4ac 100644
--- a/keystore/tests/src/android/security/KeyStoreTest.java
+++ b/keystore/tests/src/android/security/KeyStoreTest.java
@@ -142,42 +142,51 @@
         assertNull(mKeyStore.get(TEST_KEYNAME));
         mKeyStore.password(TEST_PASSWD);
         assertNull(mKeyStore.get(TEST_KEYNAME));
-        assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE));
+        assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
+                KeyStore.FLAG_ENCRYPTED));
         assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
     }
 
     public void testPut() throws Exception {
         assertNull(mKeyStore.get(TEST_KEYNAME));
-        assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE));
+        assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
+                KeyStore.FLAG_ENCRYPTED));
         assertFalse(mKeyStore.contains(TEST_KEYNAME));
         mKeyStore.password(TEST_PASSWD);
-        assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE));
+        assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
+                KeyStore.FLAG_ENCRYPTED));
         assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
     }
 
     public void testPut_grantedUid_Wifi() throws Exception {
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-        assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID));
+        assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID,
+                KeyStore.FLAG_ENCRYPTED));
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
         mKeyStore.password(TEST_PASSWD);
-        assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID));
+        assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID,
+                KeyStore.FLAG_ENCRYPTED));
         assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
     }
 
     public void testPut_ungrantedUid_Bluetooth() throws Exception {
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
-        assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID));
+        assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID,
+                KeyStore.FLAG_ENCRYPTED));
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
         mKeyStore.password(TEST_PASSWD);
-        assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID));
+        assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID,
+                KeyStore.FLAG_ENCRYPTED));
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
     }
 
     public void testI18n() throws Exception {
-        assertFalse(mKeyStore.put(TEST_I18N_KEY, TEST_I18N_VALUE));
+        assertFalse(mKeyStore.put(TEST_I18N_KEY, TEST_I18N_VALUE, KeyStore.UID_SELF,
+                KeyStore.FLAG_ENCRYPTED));
         assertFalse(mKeyStore.contains(TEST_I18N_KEY));
         mKeyStore.password(TEST_I18N_KEY);
-        assertTrue(mKeyStore.put(TEST_I18N_KEY, TEST_I18N_VALUE));
+        assertTrue(mKeyStore.put(TEST_I18N_KEY, TEST_I18N_VALUE, KeyStore.UID_SELF,
+                KeyStore.FLAG_ENCRYPTED));
         assertTrue(mKeyStore.contains(TEST_I18N_KEY));
     }
 
@@ -186,7 +195,8 @@
         mKeyStore.password(TEST_PASSWD);
         assertFalse(mKeyStore.delete(TEST_KEYNAME));
 
-        assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE));
+        assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
+                KeyStore.FLAG_ENCRYPTED));
         assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
         assertTrue(mKeyStore.delete(TEST_KEYNAME));
         assertNull(mKeyStore.get(TEST_KEYNAME));
@@ -197,7 +207,8 @@
         mKeyStore.password(TEST_PASSWD);
         assertFalse(mKeyStore.delete(TEST_KEYNAME, Process.WIFI_UID));
 
-        assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID));
+        assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID,
+                KeyStore.FLAG_ENCRYPTED));
         assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
         assertTrue(mKeyStore.delete(TEST_KEYNAME, Process.WIFI_UID));
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
@@ -208,7 +219,8 @@
         mKeyStore.password(TEST_PASSWD);
         assertFalse(mKeyStore.delete(TEST_KEYNAME, Process.BLUETOOTH_UID));
 
-        assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID));
+        assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID,
+                KeyStore.FLAG_ENCRYPTED));
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
         assertFalse(mKeyStore.delete(TEST_KEYNAME, Process.BLUETOOTH_UID));
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
@@ -220,7 +232,8 @@
         assertTrue(mKeyStore.password(TEST_PASSWD));
         assertFalse(mKeyStore.contains(TEST_KEYNAME));
 
-        assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE));
+        assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
+                KeyStore.FLAG_ENCRYPTED));
         assertTrue(mKeyStore.contains(TEST_KEYNAME));
     }
 
@@ -230,7 +243,8 @@
         assertTrue(mKeyStore.password(TEST_PASSWD));
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
 
-        assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID));
+        assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID,
+                KeyStore.FLAG_ENCRYPTED));
         assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
     }
 
@@ -240,7 +254,8 @@
         assertTrue(mKeyStore.password(TEST_PASSWD));
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
 
-        assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID));
+        assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID,
+                KeyStore.FLAG_ENCRYPTED));
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
     }
 
@@ -250,8 +265,8 @@
         assertEquals(0, emptyResult.length);
 
         mKeyStore.password(TEST_PASSWD);
-        mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE);
-        mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE);
+        mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
+        mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
 
         String[] results = mKeyStore.saw(TEST_KEYNAME);
         assertEquals(new HashSet(Arrays.asList(TEST_KEYNAME1.substring(TEST_KEYNAME.length()),
@@ -264,8 +279,8 @@
         assertNull(results1);
 
         mKeyStore.password(TEST_PASSWD);
-        mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE);
-        mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE);
+        mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
+        mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
 
         String[] results2 = mKeyStore.saw(TEST_KEYNAME, Process.BLUETOOTH_UID);
         assertNull(results2);
@@ -277,8 +292,8 @@
         assertEquals(0, results1.length);
 
         mKeyStore.password(TEST_PASSWD);
-        mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, Process.WIFI_UID);
-        mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, Process.WIFI_UID);
+        mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, Process.WIFI_UID, KeyStore.FLAG_ENCRYPTED);
+        mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, Process.WIFI_UID, KeyStore.FLAG_ENCRYPTED);
 
         String[] results2 = mKeyStore.saw(TEST_KEYNAME, Process.WIFI_UID);
         assertEquals(new HashSet(Arrays.asList(TEST_KEYNAME1.substring(TEST_KEYNAME.length()),
@@ -292,8 +307,8 @@
         assertEquals(0, results1.length);
 
         mKeyStore.password(TEST_PASSWD);
-        mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, Process.VPN_UID);
-        mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, Process.VPN_UID);
+        mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, Process.VPN_UID, KeyStore.FLAG_ENCRYPTED);
+        mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, Process.VPN_UID, KeyStore.FLAG_ENCRYPTED);
 
         String[] results2 = mKeyStore.saw(TEST_KEYNAME, Process.VPN_UID);
         assertEquals(new HashSet(Arrays.asList(TEST_KEYNAME1.substring(TEST_KEYNAME.length()),
@@ -324,7 +339,7 @@
         assertTrue(mKeyStore.isEmpty());
         mKeyStore.password(TEST_PASSWD);
         assertTrue(mKeyStore.isEmpty());
-        mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE);
+        mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
         assertFalse(mKeyStore.isEmpty());
         mKeyStore.reset();
         assertTrue(mKeyStore.isEmpty());
@@ -332,20 +347,21 @@
 
     public void testGenerate_NotInitialized_Fail() throws Exception {
         assertFalse("Should fail when keystore is not initialized",
-                mKeyStore.generate(TEST_KEYNAME));
+                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
     }
 
     public void testGenerate_Locked_Fail() throws Exception {
         mKeyStore.password(TEST_PASSWD);
         mKeyStore.lock();
-        assertFalse("Should fail when keystore is locked", mKeyStore.generate(TEST_KEYNAME));
+        assertFalse("Should fail when keystore is locked",
+                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
     }
 
     public void testGenerate_Success() throws Exception {
         assertTrue(mKeyStore.password(TEST_PASSWD));
 
         assertTrue("Should be able to generate key when unlocked",
-                mKeyStore.generate(TEST_KEYNAME));
+                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
         assertTrue(mKeyStore.contains(TEST_KEYNAME));
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
     }
@@ -354,7 +370,7 @@
         assertTrue(mKeyStore.password(TEST_PASSWD));
 
         assertTrue("Should be able to generate key when unlocked",
-                mKeyStore.generate(TEST_KEYNAME, Process.WIFI_UID));
+                mKeyStore.generate(TEST_KEYNAME, Process.WIFI_UID, KeyStore.FLAG_ENCRYPTED));
         assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
         assertFalse(mKeyStore.contains(TEST_KEYNAME));
     }
@@ -362,7 +378,7 @@
     public void testGenerate_ungrantedUid_Bluetooth_Failure() throws Exception {
         assertTrue(mKeyStore.password(TEST_PASSWD));
 
-        assertFalse(mKeyStore.generate(TEST_KEYNAME, Process.BLUETOOTH_UID));
+        assertFalse(mKeyStore.generate(TEST_KEYNAME, Process.BLUETOOTH_UID, KeyStore.FLAG_ENCRYPTED));
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
         assertFalse(mKeyStore.contains(TEST_KEYNAME));
@@ -371,8 +387,8 @@
     public void testImport_Success() throws Exception {
         assertTrue(mKeyStore.password(TEST_PASSWD));
 
-        assertTrue("Should be able to import key when unlocked",
-                mKeyStore.importKey(TEST_KEYNAME, PRIVKEY_BYTES));
+        assertTrue("Should be able to import key when unlocked", mKeyStore.importKey(TEST_KEYNAME,
+                PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
         assertTrue(mKeyStore.contains(TEST_KEYNAME));
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
     }
@@ -380,8 +396,8 @@
     public void testImport_grantedUid_Wifi_Success() throws Exception {
         assertTrue(mKeyStore.password(TEST_PASSWD));
 
-        assertTrue("Should be able to import key when unlocked",
-                mKeyStore.importKey(TEST_KEYNAME, PRIVKEY_BYTES, Process.WIFI_UID));
+        assertTrue("Should be able to import key when unlocked", mKeyStore.importKey(TEST_KEYNAME,
+                PRIVKEY_BYTES, Process.WIFI_UID, KeyStore.FLAG_ENCRYPTED));
         assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
         assertFalse(mKeyStore.contains(TEST_KEYNAME));
     }
@@ -389,7 +405,8 @@
     public void testImport_ungrantedUid_Bluetooth_Failure() throws Exception {
         assertTrue(mKeyStore.password(TEST_PASSWD));
 
-        assertFalse(mKeyStore.importKey(TEST_KEYNAME, PRIVKEY_BYTES, Process.BLUETOOTH_UID));
+        assertFalse(mKeyStore.importKey(TEST_KEYNAME, PRIVKEY_BYTES, Process.BLUETOOTH_UID,
+                KeyStore.FLAG_ENCRYPTED));
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
         assertFalse(mKeyStore.contains(TEST_KEYNAME));
@@ -398,8 +415,8 @@
     public void testImport_Failure_BadEncoding() throws Exception {
         mKeyStore.password(TEST_PASSWD);
 
-        assertFalse("Invalid DER-encoded key should not be imported",
-                mKeyStore.importKey(TEST_KEYNAME, TEST_DATA));
+        assertFalse("Invalid DER-encoded key should not be imported", mKeyStore.importKey(
+                TEST_KEYNAME, TEST_DATA, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
         assertFalse(mKeyStore.contains(TEST_KEYNAME));
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
     }
@@ -407,7 +424,7 @@
     public void testSign_Success() throws Exception {
         mKeyStore.password(TEST_PASSWD);
 
-        assertTrue(mKeyStore.generate(TEST_KEYNAME));
+        assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
         assertTrue(mKeyStore.contains(TEST_KEYNAME));
         final byte[] signature = mKeyStore.sign(TEST_KEYNAME, TEST_DATA);
 
@@ -417,7 +434,7 @@
     public void testVerify_Success() throws Exception {
         mKeyStore.password(TEST_PASSWD);
 
-        assertTrue(mKeyStore.generate(TEST_KEYNAME));
+        assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
         assertTrue(mKeyStore.contains(TEST_KEYNAME));
         final byte[] signature = mKeyStore.sign(TEST_KEYNAME, TEST_DATA);
 
@@ -444,7 +461,7 @@
                 mKeyStore.password(TEST_PASSWD));
 
         assertTrue("Should be able to generate key for testcase",
-                mKeyStore.generate(TEST_KEYNAME));
+                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertTrue("Should be able to grant key to other user",
                 mKeyStore.grant(TEST_KEYNAME, 0));
@@ -453,8 +470,8 @@
     public void testGrant_Imported_Success() throws Exception {
         assertTrue("Password should work for keystore", mKeyStore.password(TEST_PASSWD));
 
-        assertTrue("Should be able to import key for testcase",
-                mKeyStore.importKey(TEST_KEYNAME, PRIVKEY_BYTES));
+        assertTrue("Should be able to import key for testcase", mKeyStore.importKey(TEST_KEYNAME,
+                PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertTrue("Should be able to grant key to other user", mKeyStore.grant(TEST_KEYNAME, 0));
     }
@@ -477,7 +494,7 @@
                 mKeyStore.password(TEST_PASSWD));
 
         assertTrue("Should be able to generate key for testcase",
-                mKeyStore.generate(TEST_KEYNAME));
+                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertTrue("Should be able to grant key to other user",
                 mKeyStore.grant(TEST_KEYNAME, 0));
@@ -490,8 +507,8 @@
         assertTrue("Password should work for keystore",
                 mKeyStore.password(TEST_PASSWD));
 
-        assertTrue("Should be able to import key for testcase",
-                mKeyStore.importKey(TEST_KEYNAME, PRIVKEY_BYTES));
+        assertTrue("Should be able to import key for testcase", mKeyStore.importKey(TEST_KEYNAME,
+                PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertTrue("Should be able to grant key to other user",
                 mKeyStore.grant(TEST_KEYNAME, 0));
@@ -510,7 +527,7 @@
                 mKeyStore.password(TEST_PASSWD));
 
         assertTrue("Should be able to generate key for testcase",
-                mKeyStore.generate(TEST_KEYNAME));
+                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertFalse("Should not be able to revoke not existent grant",
                 mKeyStore.ungrant(TEST_KEYNAME, 0));
@@ -521,7 +538,7 @@
                 mKeyStore.password(TEST_PASSWD));
 
         assertTrue("Should be able to generate key for testcase",
-                mKeyStore.generate(TEST_KEYNAME));
+                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertTrue("Should be able to grant key to other user",
                 mKeyStore.grant(TEST_KEYNAME, 0));
@@ -538,7 +555,7 @@
                 mKeyStore.password(TEST_PASSWD));
 
         assertTrue("Should be able to generate key for testcase",
-                mKeyStore.generate(TEST_KEYNAME));
+                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertTrue("Should be able to grant key to other user",
                 mKeyStore.grant(TEST_KEYNAME, 0));
@@ -558,7 +575,7 @@
 
         assertFalse(mKeyStore.contains(TEST_KEYNAME));
 
-        assertTrue(mKeyStore.generate(TEST_KEYNAME));
+        assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertTrue(mKeyStore.contains(TEST_KEYNAME));
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
@@ -596,7 +613,7 @@
 
         assertFalse(mKeyStore.contains(TEST_KEYNAME));
 
-        assertTrue(mKeyStore.generate(TEST_KEYNAME));
+        assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertTrue(mKeyStore.contains(TEST_KEYNAME));
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
@@ -619,8 +636,8 @@
         assertTrue("Password should work for keystore",
                 mKeyStore.password(TEST_PASSWD));
 
-        assertTrue("Should be able to import key when unlocked",
-                mKeyStore.importKey(TEST_KEYNAME, PRIVKEY_BYTES));
+        assertTrue("Should be able to import key when unlocked", mKeyStore.importKey(TEST_KEYNAME,
+                PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         long now = System.currentTimeMillis();
         long actual = mKeyStore.getmtime(TEST_KEYNAME);
@@ -650,8 +667,8 @@
         assertTrue("Password should work for keystore",
                 mKeyStore.password(TEST_PASSWD));
 
-        assertTrue("Should be able to import key when unlocked",
-                mKeyStore.importKey(TEST_KEYNAME, PRIVKEY_BYTES));
+        assertTrue("Should be able to import key when unlocked", mKeyStore.importKey(TEST_KEYNAME,
+                PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertEquals("-1 should be returned for non-existent key",
                 -1L, mKeyStore.getmtime(TEST_KEYNAME2));
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index e0d96c9..b08c36b 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -33,7 +33,9 @@
 #include <utils/threads.h>
 #include <utils/Timers.h>
 #include <utils/ZipFileRO.h>
+#ifdef HAVE_ANDROID_OS
 #include <cutils/trace.h>
+#endif
 
 #include <assert.h>
 #include <dirent.h>
diff --git a/libs/hwui/Debug.h b/libs/hwui/Debug.h
index 46beb94..790c4f4 100644
--- a/libs/hwui/Debug.h
+++ b/libs/hwui/Debug.h
@@ -84,6 +84,9 @@
 // Turn on to insert an event marker for each display list op
 #define DEBUG_DISPLAY_LIST_OPS_AS_EVENTS 0
 
+// Turn on to highlight drawing batches and merged batches with different colors
+#define DEBUG_MERGE_BEHAVIOR 0
+
 #if DEBUG_INIT
     #define INIT_LOGD(...) ALOGD(__VA_ARGS__)
 #else
diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp
index d5007e1..f0084f2 100644
--- a/libs/hwui/DeferredDisplayList.cpp
+++ b/libs/hwui/DeferredDisplayList.cpp
@@ -23,6 +23,7 @@
 
 #include "Caches.h"
 #include "Debug.h"
+#include "DeferredDisplayList.h"
 #include "DisplayListOp.h"
 #include "OpenGLRenderer.h"
 
@@ -38,15 +39,27 @@
 // Depth of the save stack at the beginning of batch playback at flush time
 #define FLUSH_SAVE_STACK_DEPTH 2
 
+#define DEBUG_COLOR_BARRIER          0x1f000000
+#define DEBUG_COLOR_MERGEDBATCH      0x5f7f7fff
+#define DEBUG_COLOR_MERGEDBATCH_SOLO 0x5f7fff7f
+
 /////////////////////////////////////////////////////////////////////////////////
 // Operation Batches
 /////////////////////////////////////////////////////////////////////////////////
 
-class DrawOpBatch {
+class Batch {
 public:
-    DrawOpBatch() { mOps.clear(); }
+    virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int index) = 0;
+    virtual ~Batch() {}
+};
 
-    virtual ~DrawOpBatch() { mOps.clear(); }
+class DrawBatch : public Batch {
+public:
+    DrawBatch(int batchId, mergeid_t mergeId) : mBatchId(batchId), mMergeId(mergeId) {
+        mOps.clear();
+    }
+
+    virtual ~DrawBatch() { mOps.clear(); }
 
     void add(DrawOp* op) {
         // NOTE: ignore empty bounds special case, since we don't merge across those ops
@@ -54,7 +67,7 @@
         mOps.add(op);
     }
 
-    virtual bool intersects(Rect& rect) {
+    bool intersects(Rect& rect) {
         if (!rect.intersects(mBounds)) return false;
 
         for (unsigned int i = 0; i < mOps.size(); i++) {
@@ -71,8 +84,9 @@
         return false;
     }
 
-    virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty) {
-        DEFER_LOGD("replaying draw batch %p", this);
+    virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int index) {
+        DEFER_LOGD("%d  replaying DrawingBatch %p, with %d ops (batch id %x, merge id %p)",
+                index, this, mOps.size(), mOps[0]->getBatchId(), mOps[0]->getMergeId());
 
         status_t status = DrawGlInfo::kStatusDone;
         DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance();
@@ -84,31 +98,127 @@
 #if DEBUG_DISPLAY_LIST_OPS_AS_EVENTS
             renderer.eventMark(op->name());
 #endif
-            status |= op->applyDraw(renderer, dirty, 0);
+            status |= op->applyDraw(renderer, dirty);
             logBuffer.writeCommand(0, op->name());
+
+#if DEBUG_MERGE_BEHAVIOR
+            Rect& bounds = mOps[i]->state.mBounds;
+            int batchColor = 0x1f000000;
+            if (getBatchId() & 0x1) batchColor |= 0x0000ff;
+            if (getBatchId() & 0x2) batchColor |= 0x00ff00;
+            if (getBatchId() & 0x4) batchColor |= 0xff0000;
+            renderer.drawScreenSpaceColorRect(bounds.left, bounds.top, bounds.right, bounds.bottom,
+                    batchColor);
+#endif
         }
         return status;
     }
 
+    inline int getBatchId() const { return mBatchId; }
+    inline mergeid_t getMergeId() const { return mMergeId; }
     inline int count() const { return mOps.size(); }
-private:
+
+protected:
     Vector<DrawOp*> mOps;
     Rect mBounds;
+private:
+    int mBatchId;
+    mergeid_t mMergeId;
 };
 
-class StateOpBatch : public DrawOpBatch {
+// compare alphas approximately, with a small margin
+#define NEQ_FALPHA(lhs, rhs) \
+        fabs((float)lhs - (float)rhs) > 0.001f
+
+class MergingDrawBatch : public DrawBatch {
+public:
+    MergingDrawBatch(int batchId, mergeid_t mergeId) : DrawBatch(batchId, mergeId) {}
+
+    /*
+     * Checks if a (mergeable) op can be merged into this batch
+     *
+     * If true, the op's multiDraw must be guaranteed to handle both ops simultaneously, so it is
+     * important to consider all paint attributes used in the draw calls in deciding both a) if an
+     * op tries to merge at all, and b) if the op
+     *
+     * False positives can lead to information from the paints of subsequent merged operations being
+     * dropped, so we make simplifying qualifications on the ops that can merge, per op type.
+     */
+    bool canMergeWith(DrawOp* op) {
+        if (!op->state.mMatrix.isPureTranslate()) return false;
+
+        bool isTextBatch = getBatchId() == DeferredDisplayList::kOpBatch_Text ||
+                getBatchId() == DeferredDisplayList::kOpBatch_ColorText;
+
+        // Overlapping other operations is only allowed for text without shadow. For other ops,
+        // multiDraw isn't guaranteed to overdraw correctly
+        if (!isTextBatch || op->state.mDrawModifiers.mHasShadow) {
+            if (intersects(op->state.mBounds)) return false;
+        }
+
+        const DeferredDisplayState& lhs = op->state;
+        const DeferredDisplayState& rhs = mOps[0]->state;
+
+        if (NEQ_FALPHA(lhs.mAlpha, rhs.mAlpha)) return false;
+
+        // if paints are equal, then modifiers + paint attribs don't need to be compared
+        if (op->mPaint == mOps[0]->mPaint) return true;
+
+        if (op->getPaintAlpha() != mOps[0]->getPaintAlpha()) return false;
+
+        /* Draw Modifiers compatibility check
+         *
+         * Shadows are ignored, as only text uses them, and in that case they are drawn
+         * per-DrawTextOp, before the unified text draw. Because of this, it's always safe to merge
+         * text UNLESS a later draw's shadow should overlays a previous draw's text. This is covered
+         * above with the intersection check.
+         *
+         * OverrideLayerAlpha is also ignored, as it's only used for drawing layers, which are never
+         * merged.
+         *
+         * These ignore cases prevent us from simply memcmp'ing the drawModifiers
+         */
+
+        const DrawModifiers& lhsMod = lhs.mDrawModifiers;
+        const DrawModifiers& rhsMod = rhs.mDrawModifiers;
+        if (lhsMod.mShader != rhsMod.mShader) return false;
+        if (lhsMod.mColorFilter != rhsMod.mColorFilter) return false;
+
+        // Draw filter testing expects bit fields to be clear if filter not set.
+        if (lhsMod.mHasDrawFilter != rhsMod.mHasDrawFilter) return false;
+        if (lhsMod.mPaintFilterClearBits != rhsMod.mPaintFilterClearBits) return false;
+        if (lhsMod.mPaintFilterSetBits != rhsMod.mPaintFilterSetBits) return false;
+
+        return true;
+    }
+
+    virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int index) {
+        DEFER_LOGD("%d  replaying DrawingBatch %p, with %d ops (batch id %x, merge id %p)",
+                index, this, mOps.size(), getBatchId(), getMergeId());
+        if (mOps.size() == 1) {
+            return DrawBatch::replay(renderer, dirty, false);
+        }
+
+        DrawOp* op = mOps[0];
+        status_t status = op->multiDraw(renderer, dirty, mOps, mBounds);
+        DisplayListLogBuffer& buffer = DisplayListLogBuffer::getInstance();
+        buffer.writeCommand(0, "multiDraw");
+        buffer.writeCommand(1, op->name());
+
+#if DEBUG_MERGE_BEHAVIOR
+        renderer.drawScreenSpaceColorRect(mBounds.left, mBounds.top, mBounds.right, mBounds.bottom,
+                DEBUG_COLOR_MERGEDBATCH);
+#endif
+        return status;
+    }
+};
+
+class StateOpBatch : public Batch {
 public:
     // creates a single operation batch
     StateOpBatch(StateOp* op) : mOp(op) {}
 
-    bool intersects(Rect& rect) {
-        // if something checks for intersection, it's trying to go backwards across a state op,
-        // something not currently supported - state ops are always barriers
-        CRASH();
-        return false;
-    }
-
-    virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty) {
+    virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int index) {
         DEFER_LOGD("replaying state op batch %p", this);
         renderer.restoreDisplayState(mOp->state);
 
@@ -124,18 +234,11 @@
     const StateOp* mOp;
 };
 
-class RestoreToCountBatch : public DrawOpBatch {
+class RestoreToCountBatch : public Batch {
 public:
     RestoreToCountBatch(StateOp* op, int restoreCount) : mOp(op), mRestoreCount(restoreCount) {}
 
-    bool intersects(Rect& rect) {
-        // if something checks for intersection, it's trying to go backwards across a state op,
-        // something not currently supported - state ops are always barriers
-        CRASH();
-        return false;
-    }
-
-    virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty) {
+    virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int index) {
         DEFER_LOGD("batch %p restoring to count %d", this, mRestoreCount);
 
         renderer.restoreDisplayState(mOp->state);
@@ -155,14 +258,30 @@
     const int mRestoreCount;
 };
 
+#if DEBUG_MERGE_BEHAVIOR
+class BarrierDebugBatch : public Batch {
+    virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int index) {
+        renderer.drawScreenSpaceColorRect(0, 0, 10000, 10000, DEBUG_COLOR_BARRIER);
+        return DrawGlInfo::kStatusDrew;
+    }
+};
+#endif
+
 /////////////////////////////////////////////////////////////////////////////////
 // DeferredDisplayList
 /////////////////////////////////////////////////////////////////////////////////
 
 void DeferredDisplayList::resetBatchingState() {
     for (int i = 0; i < kOpBatch_Count; i++) {
-        mBatchIndices[i] = -1;
+        mBatchLookup[i] = NULL;
+        mMergingBatches[i].clear();
     }
+#if DEBUG_MERGE_BEHAVIOR
+    if (mBatches.size() != 0) {
+        mBatches.add(new BarrierDebugBatch());
+    }
+#endif
+    mEarliestBatchIndex = mBatches.size();
 }
 
 void DeferredDisplayList::clear() {
@@ -174,6 +293,7 @@
     }
     mBatches.clear();
     mSaveStack.clear();
+    mEarliestBatchIndex = 0;
 }
 
 /////////////////////////////////////////////////////////////////////////////////
@@ -282,28 +402,35 @@
         return; // quick rejected
     }
 
-    op->onDrawOpDeferred(renderer);
+    int batchId = kOpBatch_None;
+    mergeid_t mergeId = (mergeid_t) -1;
+    bool mergeable = op->onDefer(renderer, &batchId, &mergeId);
+
+    // complex clip has a complex set of expectations on the renderer state - for now, avoid taking
+    // the merge path in those cases
+    mergeable &= !recordingComplexClip();
 
     if (CC_UNLIKELY(renderer.getCaches().drawReorderDisabled)) {
         // TODO: elegant way to reuse batches?
-        DrawOpBatch* b = new DrawOpBatch();
+        DrawBatch* b = new DrawBatch(batchId, mergeId);
         b->add(op);
         mBatches.add(b);
         return;
     }
 
-    // disallowReorder isn't set, so find the latest batch of the new op's type, and try to merge
-    // the new op into it
-    DrawOpBatch* targetBatch = NULL;
-    int batchId = op->getBatchId();
+    // find the latest batch of the new op's type, and try to merge the new op into it
+    DrawBatch* targetBatch = NULL;
 
+    // insertion point of a new batch, will hopefully be immediately after similar batch
+    // (eventually, should be similar shader)
+    int insertBatchIndex = mBatches.size();
     if (!mBatches.isEmpty()) {
         if (op->state.mBounds.isEmpty()) {
             // don't know the bounds for op, so add to last batch and start from scratch on next op
-            mBatches.top()->add(op);
-            for (int i = 0; i < kOpBatch_Count; i++) {
-                mBatchIndices[i] = -1;
-            }
+            DrawBatch* b = new DrawBatch(batchId, mergeId);
+            b->add(op);
+            mBatches.add(b);
+            resetBatchingState();
 #if DEBUG_DEFER
             DEFER_LOGD("Warning: Encountered op with empty bounds, resetting batches");
             op->output(2);
@@ -311,13 +438,36 @@
             return;
         }
 
-        if (batchId >= 0 && mBatchIndices[batchId] != -1) {
-            int targetIndex = mBatchIndices[batchId];
-            targetBatch = mBatches[targetIndex];
+        if (mergeable) {
+            // Try to merge with any existing batch with same mergeId.
+            if (mMergingBatches[batchId].get(mergeId, targetBatch)) {
+                if (!((MergingDrawBatch*) targetBatch)->canMergeWith(op)) {
+                    targetBatch = NULL;
+                }
+            }
+        } else {
+            // join with similar, non-merging batch
+            targetBatch = (DrawBatch*)mBatchLookup[batchId];
+        }
+
+        if (targetBatch || mergeable) {
             // iterate back toward target to see if anything drawn since should overlap the new op
-            for (int i = mBatches.size() - 1; i > targetIndex; i--) {
-                DrawOpBatch* overBatch = mBatches[i];
+            // if no target, merging ops still interate to find similar batch to insert after
+            for (int i = mBatches.size() - 1; i >= mEarliestBatchIndex; i--) {
+                DrawBatch* overBatch = (DrawBatch*)mBatches[i];
+
+                if (overBatch == targetBatch) break;
+
+                // TODO: also consider shader shared between batch types
+                if (batchId == overBatch->getBatchId()) {
+                    insertBatchIndex = i + 1;
+                    if (!targetBatch) break; // found insert position, quit
+                }
+
                 if (overBatch->intersects(op->state.mBounds)) {
+                    // NOTE: it may be possible to optimize for special cases where two operations
+                    // of the same batch/paint could swap order, such as with a non-mergeable
+                    // (clipped) and a mergeable text operation
                     targetBatch = NULL;
 #if DEBUG_DEFER
                     DEFER_LOGD("op couldn't join batch %d, was intersected by batch %d",
@@ -329,13 +479,21 @@
             }
         }
     }
+
     if (!targetBatch) {
-        targetBatch = new DrawOpBatch();
-        mBatches.add(targetBatch);
-        if (batchId >= 0) {
-            mBatchIndices[batchId] = mBatches.size() - 1;
+        if (mergeable) {
+            targetBatch = new MergingDrawBatch(batchId, mergeId);
+            mMergingBatches[batchId].put(mergeId, targetBatch);
+        } else {
+            targetBatch = new DrawBatch(batchId, mergeId);
+            mBatchLookup[batchId] = targetBatch;
+            DEFER_LOGD("creating Batch %p, bid %x, at %d",
+                    targetBatch, batchId, insertBatchIndex);
         }
+
+        mBatches.insertAt(targetBatch, insertBatchIndex);
     }
+
     targetBatch->add(op);
 }
 
@@ -363,16 +521,14 @@
 // Replay / flush
 /////////////////////////////////////////////////////////////////////////////////
 
-static status_t replayBatchList(Vector<DrawOpBatch*>& batchList,
+static status_t replayBatchList(const Vector<Batch*>& batchList,
         OpenGLRenderer& renderer, Rect& dirty) {
     status_t status = DrawGlInfo::kStatusDone;
 
-    int opCount = 0;
     for (unsigned int i = 0; i < batchList.size(); i++) {
-        status |= batchList[i]->replay(renderer, dirty);
-        opCount += batchList[i]->count();
+        status |= batchList[i]->replay(renderer, dirty, i);
     }
-    DEFER_LOGD("--flushed, drew %d batches (total %d ops)", batchList.size(), opCount);
+    DEFER_LOGD("--flushed, drew %d batches", batchList.size());
     return status;
 }
 
@@ -400,7 +556,6 @@
     renderer.setDrawModifiers(restoreDrawModifiers);
 
     DEFER_LOGD("--flush complete, returning %x", status);
-
     clear();
     return status;
 }
diff --git a/libs/hwui/DeferredDisplayList.h b/libs/hwui/DeferredDisplayList.h
index 653f315..9782c1c 100644
--- a/libs/hwui/DeferredDisplayList.h
+++ b/libs/hwui/DeferredDisplayList.h
@@ -22,6 +22,9 @@
 
 #include "Matrix.h"
 #include "Rect.h"
+#include "utils/TinyHashMap.h"
+
+class SkBitmap;
 
 namespace android {
 namespace uirenderer {
@@ -31,16 +34,21 @@
 class SaveOp;
 class SaveLayerOp;
 class StateOp;
-class DrawOpBatch;
 class OpenGLRenderer;
 
+class Batch;
+class DrawBatch;
+class MergingDrawBatch;
+
+typedef void* mergeid_t;
+
 class DeferredDisplayList {
 public:
     DeferredDisplayList() { clear(); }
     ~DeferredDisplayList() { clear(); }
 
     enum OpBatchId {
-        kOpBatch_None = -1, // Don't batch
+        kOpBatch_None = 0, // Don't batch
         kOpBatch_Bitmap,
         kOpBatch_Patch,
         kOpBatch_AlphaVertices,
@@ -96,8 +104,20 @@
     Vector<int> mSaveStack;
     int mComplexClipStackStart;
 
-    Vector<DrawOpBatch*> mBatches;
-    int mBatchIndices[kOpBatch_Count];
+    Vector<Batch*> mBatches;
+
+    // Maps batch ids to the most recent *non-merging* batch of that id
+    Batch* mBatchLookup[kOpBatch_Count];
+
+    // Points to the index after the most recent barrier
+    int mEarliestBatchIndex;
+
+    /**
+     * Maps the mergeid_t returned by an op's getMergeId() to the most recently seen
+     * MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not
+     * collide, which avoids the need to resolve mergeid collisions.
+     */
+    TinyHashMap<mergeid_t, DrawBatch*> mMergingBatches[kOpBatch_Count];
 };
 
 }; // namespace uirenderer
diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp
index 36c95f9..26abec2 100644
--- a/libs/hwui/DisplayList.cpp
+++ b/libs/hwui/DisplayList.cpp
@@ -485,7 +485,7 @@
 
 #if DEBUG_DISPLAY_LIST
     Rect* clipRect = renderer.getClipRect();
-    DISPLAY_LIST_LOGD("%*sStart display list (%p, %s), clipRect: %.0f, %.f, %.0f, %.0f",
+    DISPLAY_LIST_LOGD("%*sStart display list (%p, %s), clipRect: %.0f, %.0f, %.0f, %.0f",
             level * 2, "", this, mName.string(), clipRect->left, clipRect->top,
             clipRect->right, clipRect->bottom);
 #endif
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index a5dee9f..ad7edb1 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -121,6 +121,7 @@
 };
 
 class DrawOp : public DisplayListOp {
+friend class MergingDrawBatch;
 public:
     DrawOp(SkPaint* paint)
             : mPaint(paint), mQuickRejected(false) {}
@@ -145,12 +146,41 @@
             return;
         }
 
-        replayStruct.mDrawGlStatus |= applyDraw(replayStruct.mRenderer, replayStruct.mDirty, level);
+        replayStruct.mDrawGlStatus |= applyDraw(replayStruct.mRenderer, replayStruct.mDirty);
     }
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) = 0;
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) = 0;
 
-    virtual void onDrawOpDeferred(OpenGLRenderer& renderer) {
+    /**
+     * Draw multiple instances of an operation, must be overidden for operations that merge
+     *
+     * Currently guarantees certain similarities between ops (see MergingDrawBatch::canMergeWith),
+     * and pure translation transformations. Other guarantees of similarity should be enforced by
+     * reducing which operations are tagged as mergeable.
+     */
+    virtual status_t multiDraw(OpenGLRenderer& renderer, Rect& dirty,
+            const Vector<DrawOp*>& ops, const Rect& bounds) {
+        status_t status = DrawGlInfo::kStatusDone;
+        for (unsigned int i = 0; i < ops.size(); i++) {
+            renderer.restoreDisplayState(ops[i]->state);
+            status |= ops[i]->applyDraw(renderer, dirty);
+        }
+        return status;
+    }
+
+    /*
+     * When this method is invoked the state field is initialized to have the
+     * final rendering state. We can thus use it to process data as it will be
+     * used at draw time.
+     *
+     * Additionally, this method allows subclasses to provide defer-time preferences for batching
+     * and merging.
+     *
+     * Return true if the op can merge with others of its kind (such subclasses should implement
+     * multiDraw)
+     */
+    virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) {
+        return false;
     }
 
     // returns true if bounds exist
@@ -160,12 +190,11 @@
     void setQuickRejected(bool quickRejected) { mQuickRejected = quickRejected; }
     bool getQuickRejected() { return mQuickRejected; }
 
-    /** Batching disabled by default, turned on for individual ops */
-    virtual DeferredDisplayList::OpBatchId getBatchId() {
-        return DeferredDisplayList::kOpBatch_None;
+    inline int getPaintAlpha() {
+        return OpenGLRenderer::getAlphaDirect(mPaint);
     }
 
-    float strokeWidthOutset() {
+    inline float strokeWidthOutset() {
         float width = mPaint->getStrokeWidth();
         if (width == 0) return 0.5f; // account for hairline
         return width * 0.5f;
@@ -207,6 +236,14 @@
         return true;
     }
 
+    bool mergeAllowed() {
+        // checks that we're unclipped, and srcover
+        const Rect& opBounds = state.mBounds;
+        return fabs(opBounds.getWidth() - mLocalBounds.getWidth()) < 0.1 &&
+                fabs(opBounds.getHeight() - mLocalBounds.getHeight()) < 0.1 &&
+                (OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode);
+    }
+
 protected:
     Rect mLocalBounds; // displayed area in LOCAL coord. doesn't incorporate stroke, so check paint
 };
@@ -686,20 +723,58 @@
                     paint),
             mBitmap(bitmap) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         return renderer.drawBitmap(mBitmap, mLocalBounds.left, mLocalBounds.top,
                 getPaint(renderer));
     }
 
+#define SET_TEXTURE(ptr, posRect, offsetRect, texCoordsRect, xDim, yDim) \
+    TextureVertex::set(ptr++, posRect.xDim - offsetRect.left, posRect.yDim - offsetRect.top, \
+            texCoordsRect.xDim, texCoordsRect.yDim)
+
+    virtual status_t multiDraw(OpenGLRenderer& renderer, Rect& dirty,
+            const Vector<DrawOp*>& ops, const Rect& bounds) {
+        renderer.restoreDisplayState(state, true); // restore all but the clip
+        renderer.setFullScreenClip(); // ensure merged ops aren't clipped
+        TextureVertex vertices[6 * ops.size()];
+        TextureVertex* vertex = &vertices[0];
+
+        // TODO: manually handle rect clip for bitmaps by adjusting texCoords per op, and allowing
+        // them to be merged in getBatchId()
+        const Rect texCoords(0, 0, 1, 1);
+
+        const float width = mBitmap->width();
+        const float height = mBitmap->height();
+        for (unsigned int i = 0; i < ops.size(); i++) {
+            const Rect& opBounds = ops[i]->state.mBounds;
+            SET_TEXTURE(vertex, opBounds, bounds, texCoords, left, top);
+            SET_TEXTURE(vertex, opBounds, bounds, texCoords, right, top);
+            SET_TEXTURE(vertex, opBounds, bounds, texCoords, left, bottom);
+
+            SET_TEXTURE(vertex, opBounds, bounds, texCoords, left, bottom);
+            SET_TEXTURE(vertex, opBounds, bounds, texCoords, right, top);
+            SET_TEXTURE(vertex, opBounds, bounds, texCoords, right, bottom);
+        }
+
+        return renderer.drawBitmaps(mBitmap, ops.size(), &vertices[0], bounds, mPaint);
+    }
+
     virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Draw bitmap %p at %f %f", mBitmap, mLocalBounds.left, mLocalBounds.top);
     }
 
     virtual const char* name() { return "DrawBitmap"; }
-    virtual DeferredDisplayList::OpBatchId getBatchId() {
-        return DeferredDisplayList::kOpBatch_Bitmap;
+
+    virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) {
+        *batchId = DeferredDisplayList::kOpBatch_Bitmap;
+        *mergeId = (mergeid_t)mBitmap;
+
+        // don't merge A8 bitmaps - the paint's color isn't compared by mergeId, or in
+        // MergingDrawBatch::canMergeWith
+        return mergeAllowed() && (mBitmap->getConfig() != SkBitmap::kA8_Config);
     }
 
+    const SkBitmap* bitmap() { return mBitmap; }
 protected:
     SkBitmap* mBitmap;
 };
@@ -713,7 +788,7 @@
         transform.mapRect(mLocalBounds);
     }
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         return renderer.drawBitmap(mBitmap, mMatrix, getPaint(renderer));
     }
 
@@ -721,9 +796,11 @@
         OP_LOG("Draw bitmap %p matrix " MATRIX_STRING, mBitmap, MATRIX_ARGS(mMatrix));
     }
 
-    virtual const char* name() { return "DrawBitmap"; }
-    virtual DeferredDisplayList::OpBatchId getBatchId() {
-        return DeferredDisplayList::kOpBatch_Bitmap;
+    virtual const char* name() { return "DrawBitmapMatrix"; }
+
+    virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) {
+        *batchId = DeferredDisplayList::kOpBatch_Bitmap;
+        return false;
     }
 
 private:
@@ -738,7 +815,7 @@
             : DrawBoundedOp(dstLeft, dstTop, dstRight, dstBottom, paint),
             mBitmap(bitmap), mSrc(srcLeft, srcTop, srcRight, srcBottom) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         return renderer.drawBitmap(mBitmap, mSrc.left, mSrc.top, mSrc.right, mSrc.bottom,
                 mLocalBounds.left, mLocalBounds.top, mLocalBounds.right, mLocalBounds.bottom,
                 getPaint(renderer));
@@ -750,8 +827,10 @@
     }
 
     virtual const char* name() { return "DrawBitmapRect"; }
-    virtual DeferredDisplayList::OpBatchId getBatchId() {
-        return DeferredDisplayList::kOpBatch_Bitmap;
+
+    virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) {
+        *batchId = DeferredDisplayList::kOpBatch_Bitmap;
+        return false;
     }
 
 private:
@@ -764,7 +843,7 @@
     DrawBitmapDataOp(SkBitmap* bitmap, float left, float top, SkPaint* paint)
             : DrawBitmapOp(bitmap, left, top, paint) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         return renderer.drawBitmapData(mBitmap, mLocalBounds.left,
                 mLocalBounds.top, getPaint(renderer));
     }
@@ -774,8 +853,10 @@
     }
 
     virtual const char* name() { return "DrawBitmapData"; }
-    virtual DeferredDisplayList::OpBatchId getBatchId() {
-        return DeferredDisplayList::kOpBatch_Bitmap;
+
+    virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) {
+        *batchId = DeferredDisplayList::kOpBatch_Bitmap;
+        return false;
     }
 };
 
@@ -787,7 +868,7 @@
             mBitmap(bitmap), mMeshWidth(meshWidth), mMeshHeight(meshHeight),
             mVertices(vertices), mColors(colors) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         return renderer.drawBitmapMesh(mBitmap, mMeshWidth, mMeshHeight,
                 mVertices, mColors, getPaint(renderer));
     }
@@ -797,8 +878,10 @@
     }
 
     virtual const char* name() { return "DrawBitmapMesh"; }
-    virtual DeferredDisplayList::OpBatchId getBatchId() {
-        return DeferredDisplayList::kOpBatch_Bitmap;
+
+    virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) {
+        *batchId = DeferredDisplayList::kOpBatch_Bitmap;
+        return false;
     }
 
 private:
@@ -820,7 +903,7 @@
             mColors(colors), mxDivsCount(width), myDivsCount(height),
             mNumColors(numColors), mAlpha(alpha), mMode(mode) {};
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         // NOTE: not calling the virtual method, which takes a paint
         return renderer.drawPatch(mBitmap, mxDivs, myDivs, mColors,
                 mxDivsCount, myDivsCount, mNumColors,
@@ -833,8 +916,11 @@
     }
 
     virtual const char* name() { return "DrawPatch"; }
-    virtual DeferredDisplayList::OpBatchId getBatchId() {
-        return DeferredDisplayList::kOpBatch_Patch;
+
+    virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) {
+        *batchId = DeferredDisplayList::kOpBatch_Patch;
+        *mergeId = (mergeid_t)mBitmap;
+        return true;
     }
 
 private:
@@ -854,7 +940,7 @@
     DrawColorOp(int color, SkXfermode::Mode mode)
             : DrawOp(0), mColor(color), mMode(mode) {};
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         return renderer.drawColor(mColor, mMode);
     }
 
@@ -882,13 +968,15 @@
         return true;
     }
 
-    virtual DeferredDisplayList::OpBatchId getBatchId() {
+    virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) {
         if (mPaint->getPathEffect()) {
-            return DeferredDisplayList::kOpBatch_AlphaMaskTexture;
+            *batchId = DeferredDisplayList::kOpBatch_AlphaMaskTexture;
+        } else {
+            *batchId = mPaint->isAntiAlias() ?
+                    DeferredDisplayList::kOpBatch_AlphaVertices :
+                    DeferredDisplayList::kOpBatch_Vertices;
         }
-        return mPaint->isAntiAlias() ?
-                DeferredDisplayList::kOpBatch_AlphaVertices :
-                DeferredDisplayList::kOpBatch_Vertices;
+        return false;
     }
 };
 
@@ -897,7 +985,7 @@
     DrawRectOp(float left, float top, float right, float bottom, SkPaint* paint)
             : DrawStrokableOp(left, top, right, bottom, paint) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         return renderer.drawRect(mLocalBounds.left, mLocalBounds.top,
                 mLocalBounds.right, mLocalBounds.bottom, getPaint(renderer));
     }
@@ -915,7 +1003,7 @@
             : DrawBoundedOp(rects, count, paint),
             mRects(rects), mCount(count) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         return renderer.drawRects(mRects, mCount, getPaint(renderer));
     }
 
@@ -925,8 +1013,9 @@
 
     virtual const char* name() { return "DrawRects"; }
 
-    virtual DeferredDisplayList::OpBatchId getBatchId() {
-        return DeferredDisplayList::kOpBatch_Vertices;
+    virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) {
+        *batchId = DeferredDisplayList::kOpBatch_Vertices;
+        return false;
     }
 
 private:
@@ -940,7 +1029,7 @@
             float rx, float ry, SkPaint* paint)
             : DrawStrokableOp(left, top, right, bottom, paint), mRx(rx), mRy(ry) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         return renderer.drawRoundRect(mLocalBounds.left, mLocalBounds.top,
                 mLocalBounds.right, mLocalBounds.bottom, mRx, mRy, getPaint(renderer));
     }
@@ -962,7 +1051,7 @@
             : DrawStrokableOp(x - radius, y - radius, x + radius, y + radius, paint),
             mX(x), mY(y), mRadius(radius) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         return renderer.drawCircle(mX, mY, mRadius, getPaint(renderer));
     }
 
@@ -983,7 +1072,7 @@
     DrawOvalOp(float left, float top, float right, float bottom, SkPaint* paint)
             : DrawStrokableOp(left, top, right, bottom, paint) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         return renderer.drawOval(mLocalBounds.left, mLocalBounds.top,
                 mLocalBounds.right, mLocalBounds.bottom, getPaint(renderer));
     }
@@ -1002,7 +1091,7 @@
             : DrawStrokableOp(left, top, right, bottom, paint),
             mStartAngle(startAngle), mSweepAngle(sweepAngle), mUseCenter(useCenter) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         return renderer.drawArc(mLocalBounds.left, mLocalBounds.top,
                 mLocalBounds.right, mLocalBounds.bottom,
                 mStartAngle, mSweepAngle, mUseCenter, getPaint(renderer));
@@ -1033,13 +1122,16 @@
         mLocalBounds.set(left, top, left + width, top + height);
     }
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         return renderer.drawPath(mPath, getPaint(renderer));
     }
 
-    virtual void onDrawOpDeferred(OpenGLRenderer& renderer) {
+    virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) {
         SkPaint* paint = getPaint(renderer);
         renderer.getCaches().pathCache.precache(mPath, paint);
+
+        *batchId = DeferredDisplayList::kOpBatch_AlphaMaskTexture;
+        return false;
     }
 
     virtual void output(int level, uint32_t logFlags) {
@@ -1048,9 +1140,6 @@
 
     virtual const char* name() { return "DrawPath"; }
 
-    virtual DeferredDisplayList::OpBatchId getBatchId() {
-        return DeferredDisplayList::kOpBatch_AlphaMaskTexture;
-    }
 private:
     SkPath* mPath;
 };
@@ -1063,7 +1152,7 @@
         mLocalBounds.outset(strokeWidthOutset());
     }
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         return renderer.drawLines(mPoints, mCount, getPaint(renderer));
     }
 
@@ -1073,10 +1162,11 @@
 
     virtual const char* name() { return "DrawLines"; }
 
-    virtual DeferredDisplayList::OpBatchId getBatchId() {
-        return mPaint->isAntiAlias() ?
+    virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) {
+        *batchId = mPaint->isAntiAlias() ?
                 DeferredDisplayList::kOpBatch_AlphaVertices :
                 DeferredDisplayList::kOpBatch_Vertices;
+        return false;
     }
 
 protected:
@@ -1089,7 +1179,7 @@
     DrawPointsOp(float* points, int count, SkPaint* paint)
             : DrawLinesOp(points, count, paint) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         return renderer.drawPoints(mPoints, mCount, getPaint(renderer));
     }
 
@@ -1109,17 +1199,18 @@
         OP_LOG("Draw some text, %d bytes", mBytesCount);
     }
 
-    virtual void onDrawOpDeferred(OpenGLRenderer& renderer) {
+    virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) {
         SkPaint* paint = getPaint(renderer);
         FontRenderer& fontRenderer = renderer.getCaches().fontRenderer->getFontRenderer(paint);
         fontRenderer.precache(paint, mText, mCount, mat4::identity());
-    }
 
-    virtual DeferredDisplayList::OpBatchId getBatchId() {
-        return mPaint->getColor() == 0xff000000 ?
+        *batchId = mPaint->getColor() == 0xff000000 ?
                 DeferredDisplayList::kOpBatch_Text :
                 DeferredDisplayList::kOpBatch_ColorText;
+
+        return false;
     }
+
 protected:
     const char* mText;
     int mBytesCount;
@@ -1135,7 +1226,7 @@
         /* TODO: inherit from DrawBounded and init mLocalBounds */
     }
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         return renderer.drawTextOnPath(mText, mBytesCount, mCount, mPath,
                 mHOffset, mVOffset, getPaint(renderer));
     }
@@ -1156,7 +1247,7 @@
         /* TODO: inherit from DrawBounded and init mLocalBounds */
     }
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         return renderer.drawPosText(mText, mBytesCount, mCount, mPositions, getPaint(renderer));
     }
 
@@ -1189,12 +1280,7 @@
         memset(&mPrecacheTransform.data[0], 0xff, 16 * sizeof(float));
     }
 
-    /*
-     * When this method is invoked the state field  is initialized to have the
-     * final rendering state. We can thus use it to process data as it will be
-     * used at draw time.
-     */
-    virtual void onDrawOpDeferred(OpenGLRenderer& renderer) {
+    virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) {
         SkPaint* paint = getPaint(renderer);
         FontRenderer& fontRenderer = renderer.getCaches().fontRenderer->getFontRenderer(paint);
         const mat4& transform = renderer.findBestFontTransform(state.mMatrix);
@@ -1202,25 +1288,44 @@
             fontRenderer.precache(paint, mText, mCount, transform);
             mPrecacheTransform = transform;
         }
+        *batchId = mPaint->getColor() == 0xff000000 ?
+                DeferredDisplayList::kOpBatch_Text :
+                DeferredDisplayList::kOpBatch_ColorText;
+
+        *mergeId = (mergeid_t)mPaint->getColor();
+
+        // don't merge decorated text - the decorations won't draw in order
+        bool noDecorations = !(mPaint->getFlags() & (SkPaint::kUnderlineText_Flag |
+                        SkPaint::kStrikeThruText_Flag));
+        return mergeAllowed() && noDecorations;
     }
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         return renderer.drawText(mText, mBytesCount, mCount, mX, mY,
                 mPositions, getPaint(renderer), mLength);
     }
 
+    virtual status_t multiDraw(OpenGLRenderer& renderer, Rect& dirty,
+            const Vector<DrawOp*>& ops, const Rect& bounds) {
+        status_t status = DrawGlInfo::kStatusDone;
+        renderer.setFullScreenClip(); // ensure merged ops aren't clipped
+        for (unsigned int i = 0; i < ops.size(); i++) {
+            DrawOpMode drawOpMode = (i == ops.size() - 1) ? kDrawOpMode_Flush : kDrawOpMode_Defer;
+            renderer.restoreDisplayState(ops[i]->state, true); // restore all but the clip
+
+            DrawTextOp& op = *((DrawTextOp*)ops[i]);
+            status |= renderer.drawText(op.mText, op.mBytesCount, op.mCount, op.mX, op.mY,
+                    op.mPositions, op.getPaint(renderer), op.mLength, drawOpMode);
+        }
+        return status;
+    }
+
     virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Draw Text of count %d, bytes %d", mCount, mBytesCount);
     }
 
     virtual const char* name() { return "DrawText"; }
 
-    virtual DeferredDisplayList::OpBatchId getBatchId() {
-        return mPaint->getColor() == 0xff000000 ?
-                DeferredDisplayList::kOpBatch_Text :
-                DeferredDisplayList::kOpBatch_ColorText;
-    }
-
 private:
     const char* mText;
     int mBytesCount;
@@ -1241,7 +1346,7 @@
     DrawFunctorOp(Functor* functor)
             : DrawOp(0), mFunctor(functor) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         renderer.startMark("GL functor");
         status_t ret = renderer.callDrawGLFunction(mFunctor, dirty);
         renderer.endMark();
@@ -1269,14 +1374,14 @@
             mDisplayList->defer(deferStruct, level + 1);
         }
     }
-virtual void replay(ReplayStateStruct& replayStruct, int saveCount, int level) {
+    virtual void replay(ReplayStateStruct& replayStruct, int saveCount, int level) {
         if (mDisplayList && mDisplayList->isRenderable()) {
             mDisplayList->replay(replayStruct, level + 1);
         }
     }
 
     // NOT USED since replay() is overridden
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         return DrawGlInfo::kStatusDone;
     }
 
@@ -1299,7 +1404,7 @@
     DrawLayerOp(Layer* layer, float x, float y)
             : DrawOp(0), mLayer(layer), mX(x), mY(y) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         return renderer.drawLayer(mLayer, mX, mY);
     }
 
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 0b8f7e6..876c38a 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -276,6 +276,15 @@
     bitmap = refBitmap(bitmap);
     paint = refPaint(paint);
 
+    if (srcLeft == 0 && srcTop == 0 &&
+            srcRight == bitmap->width() && srcBottom == bitmap->height() &&
+            (srcBottom - srcTop == dstBottom - dstTop) &&
+            (srcRight - srcLeft == dstRight - dstLeft)) {
+        // transform simple rect to rect drawing case into position bitmap ops, since they merge
+        addDrawOp(new (alloc()) DrawBitmapOp(bitmap, dstLeft, dstTop, paint));
+        return DrawGlInfo::kStatusDone;
+    }
+
     addDrawOp(new (alloc()) DrawBitmapRectOp(bitmap,
                     srcLeft, srcTop, srcRight, srcBottom,
                     dstLeft, dstTop, dstRight, dstBottom, paint));
@@ -413,7 +422,9 @@
 }
 
 status_t DisplayListRenderer::drawText(const char* text, int bytesCount, int count,
-        float x, float y, const float* positions, SkPaint* paint, float length) {
+        float x, float y, const float* positions, SkPaint* paint,
+        float length, DrawOpMode drawOpMode) {
+
     if (!text || count <= 0) return DrawGlInfo::kStatusDone;
 
     if (length < 0.0f) length = paint->measureText(text, bytesCount);
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index 19f7eb6..75abad6 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -121,8 +121,9 @@
             float hOffset, float vOffset, SkPaint* paint);
     virtual status_t drawPosText(const char* text, int bytesCount, int count,
             const float* positions, SkPaint* paint);
-    virtual status_t drawText(const char* text, int bytesCount, int count,
-            float x, float y, const float* positions, SkPaint* paint, float length);
+    virtual status_t drawText(const char* text, int bytesCount, int count, float x, float y,
+            const float* positions, SkPaint* paint, float length, DrawOpMode drawOpMode);
+
     virtual status_t drawRects(const float* rects, int count, SkPaint* paint);
 
     virtual void resetShader();
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 6894ef9..543cfa2 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -57,7 +57,6 @@
 
     mGammaTable = NULL;
     mInitialized = false;
-    mMaxNumberOfQuads = 1024;
 
     mCurrentCacheTexture = NULL;
 
@@ -293,7 +292,7 @@
 }
 
 CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool allocate) {
-    CacheTexture* cacheTexture = new CacheTexture(width, height, mMaxNumberOfQuads);
+    CacheTexture* cacheTexture = new CacheTexture(width, height, gMaxNumberOfQuads);
 
     if (allocate) {
         Caches::getInstance().activeTexture(0);
@@ -320,12 +319,12 @@
 
 // Avoid having to reallocate memory and render quad by quad
 void FontRenderer::initVertexArrayBuffers() {
-    uint32_t numIndices = mMaxNumberOfQuads * 6;
+    uint32_t numIndices = gMaxNumberOfQuads * 6;
     uint32_t indexBufferSizeBytes = numIndices * sizeof(uint16_t);
     uint16_t* indexBufferData = (uint16_t*) malloc(indexBufferSizeBytes);
 
     // Four verts, two triangles , six indices per quad
-    for (uint32_t i = 0; i < mMaxNumberOfQuads; i++) {
+    for (uint32_t i = 0; i < gMaxNumberOfQuads; i++) {
         int i6 = i * 6;
         int i4 = i * 4;
 
@@ -594,7 +593,7 @@
 
 bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *text,
         uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y,
-        const float* positions, Rect* bounds, Functor* functor) {
+        const float* positions, Rect* bounds, Functor* functor, bool forceFinish) {
     if (!mCurrentFont) {
         ALOGE("No font set");
         return false;
@@ -602,7 +601,10 @@
 
     initRender(clip, bounds, functor);
     mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions);
-    finishRender();
+
+    if (forceFinish) {
+        finishRender();
+    }
 
     return mDrawn;
 }
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index 348b7e3..307a1d9 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -66,7 +66,8 @@
     // bounds is an out parameter
     bool renderPosText(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex,
             uint32_t len, int numGlyphs, int x, int y, const float* positions, Rect* bounds,
-            Functor* functor);
+            Functor* functor, bool forceFinish = true);
+
     // bounds is an out parameter
     bool renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex,
             uint32_t len, int numGlyphs, SkPath* path, float hOffset, float vOffset, Rect* bounds);
@@ -101,6 +102,8 @@
 private:
     friend class Font;
 
+    static const uint32_t gMaxNumberOfQuads = 2048;
+
     const uint8_t* mGammaTable;
 
     void allocateTextureMemory(CacheTexture* cacheTexture);
@@ -154,7 +157,6 @@
 
     bool mUploadTexture;
 
-    uint32_t mMaxNumberOfQuads;
     uint32_t mIndexBufferID;
 
     Functor* mFunctor;
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index dcd1eb8..f81b4ff 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -112,11 +112,9 @@
 
 OpenGLRenderer::OpenGLRenderer():
         mCaches(Caches::getInstance()), mExtensions(Extensions::getInstance()) {
-    mDrawModifiers.mShader = NULL;
-    mDrawModifiers.mColorFilter = NULL;
+    // *set* draw modifiers to be 0
+    memset(&mDrawModifiers, 0, sizeof(mDrawModifiers));
     mDrawModifiers.mOverrideLayerAlpha = 1.0f;
-    mDrawModifiers.mHasShadow = false;
-    mDrawModifiers.mHasDrawFilter = false;
 
     memcpy(mMeshVertices, gMeshVertices, sizeof(gMeshVertices));
 
@@ -1330,10 +1328,9 @@
         }
     }
 
-    if (stateDeferFlags & kStateDeferFlag_Clip) {
+    state.mClipValid = (stateDeferFlags & kStateDeferFlag_Clip);
+    if (state.mClipValid) {
         state.mClip.set(currentClip);
-    } else {
-        state.mClip.setEmpty();
     }
 
     // Transform, drawModifiers, and alpha always deferred, since they are used by state operations
@@ -1344,17 +1341,22 @@
     return false;
 }
 
-void OpenGLRenderer::restoreDisplayState(const DeferredDisplayState& state) {
+void OpenGLRenderer::restoreDisplayState(const DeferredDisplayState& state, bool skipClipRestore) {
     currentTransform().load(state.mMatrix);
     mDrawModifiers = state.mDrawModifiers;
     mSnapshot->alpha = state.mAlpha;
 
-    if (!state.mClip.isEmpty()) {
+    if (state.mClipValid && !skipClipRestore) {
         mSnapshot->setClip(state.mClip.left, state.mClip.top, state.mClip.right, state.mClip.bottom);
         dirtyClip();
     }
 }
 
+void OpenGLRenderer::setFullScreenClip() {
+    mSnapshot->setClip(0, 0, mWidth, mHeight);
+    dirtyClip();
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Transforms
 ///////////////////////////////////////////////////////////////////////////////
@@ -1963,6 +1965,42 @@
             (GLvoid*) gMeshTextureOffset, GL_TRIANGLE_STRIP, gMeshCount, ignoreTransform);
 }
 
+status_t OpenGLRenderer::drawBitmaps(SkBitmap* bitmap, int bitmapCount, TextureVertex* vertices,
+        const Rect& bounds, SkPaint* paint) {
+
+    // merged draw operations don't need scissor, but clip should still be valid
+    mCaches.setScissorEnabled(mScissorOptimizationDisabled);
+
+    mCaches.activeTexture(0);
+    Texture* texture = mCaches.textureCache.get(bitmap);
+    if (!texture) return DrawGlInfo::kStatusDone;
+    const AutoTexture autoCleanup(texture);
+
+    int alpha;
+    SkXfermode::Mode mode;
+    getAlphaAndMode(paint, &alpha, &mode);
+
+    texture->setWrap(GL_CLAMP_TO_EDGE, true);
+    texture->setFilter(GL_NEAREST, true); // merged ops are always pure-translation for now
+
+    const float x = (int) floorf(bounds.left + 0.5f);
+    const float y = (int) floorf(bounds.top + 0.5f);
+    if (CC_UNLIKELY(bitmap->getConfig() == SkBitmap::kA8_Config)) {
+        int color = paint != NULL ? paint->getColor() : 0;
+        drawAlpha8TextureMesh(x, y, x + bounds.getWidth(), y + bounds.getHeight(),
+                texture->id, paint != NULL, color, alpha, mode,
+                &vertices[0].position[0], &vertices[0].texture[0],
+                GL_TRIANGLES, bitmapCount * 6, true, true);
+    } else {
+        drawTextureMesh(x, y, x + bounds.getWidth(), y + bounds.getHeight(),
+                texture->id, alpha / 255.0f, mode, texture->blend,
+                &vertices[0].position[0], &vertices[0].texture[0],
+                GL_TRIANGLES, bitmapCount * 6, false, true, 0, true);
+    }
+
+    return DrawGlInfo::kStatusDrew;
+}
+
 status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint) {
     const float right = left + bitmap->width();
     const float bottom = top + bitmap->height();
@@ -2796,8 +2834,11 @@
 }
 
 status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count,
-        float x, float y, const float* positions, SkPaint* paint, float length) {
-    if (text == NULL || count == 0 || mSnapshot->isIgnored() || canSkipText(paint)) {
+        float x, float y, const float* positions, SkPaint* paint, float length,
+        DrawOpMode drawOpMode) {
+
+    if (drawOpMode == kDrawOpMode_Immediate &&
+            (text == NULL || count == 0 || mSnapshot->isIgnored() || canSkipText(paint))) {
         return DrawGlInfo::kStatusDone;
     }
 
@@ -2815,8 +2856,13 @@
 
     SkPaint::FontMetrics metrics;
     paint->getFontMetrics(&metrics, 0.0f);
-    if (quickReject(x, y + metrics.fTop, x + length, y + metrics.fBottom)) {
-        return DrawGlInfo::kStatusDone;
+    if (drawOpMode == kDrawOpMode_Immediate) {
+        if (quickReject(x, y + metrics.fTop, x + length, y + metrics.fBottom)) {
+            return DrawGlInfo::kStatusDone;
+        }
+    } else {
+        // merged draw operations don't need scissor, but clip should still be valid
+        mCaches.setScissorEnabled(mScissorOptimizationDisabled);
     }
 
     const float oldX = x;
@@ -2868,17 +2914,20 @@
 
     bool status;
     TextSetupFunctor functor(*this, x, y, pureTranslate, alpha, mode, paint);
+
+    // don't call issuedrawcommand, do it at end of batch
+    bool forceFinish = (drawOpMode != kDrawOpMode_Defer);
     if (CC_UNLIKELY(paint->getTextAlign() != SkPaint::kLeft_Align)) {
         SkPaint paintCopy(*paint);
         paintCopy.setTextAlign(SkPaint::kLeft_Align);
         status = fontRenderer.renderPosText(&paintCopy, clip, text, 0, bytesCount, count, x, y,
-                positions, hasActiveLayer ? &bounds : NULL, &functor);
+                positions, hasActiveLayer ? &bounds : NULL, &functor, forceFinish);
     } else {
         status = fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y,
-                positions, hasActiveLayer ? &bounds : NULL, &functor);
+                positions, hasActiveLayer ? &bounds : NULL, &functor, forceFinish);
     }
 
-    if (status && hasActiveLayer) {
+    if ((status || drawOpMode != kDrawOpMode_Immediate) && hasActiveLayer) {
         if (!pureTranslate) {
             transform.mapRect(bounds);
         }
@@ -3093,7 +3142,11 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void OpenGLRenderer::resetPaintFilter() {
+    // when clearing the PaintFilter, the masks should also be cleared for simple DrawModifier
+    // comparison, see MergingDrawBatch::canMergeWith
     mDrawModifiers.mHasDrawFilter = false;
+    mDrawModifiers.mPaintFilterClearBits = 0;
+    mDrawModifiers.mPaintFilterSetBits = 0;
 }
 
 void OpenGLRenderer::setupPaintFilter(int clearBits, int setBits) {
@@ -3365,7 +3418,7 @@
 void OpenGLRenderer::drawAlpha8TextureMesh(float left, float top, float right, float bottom,
         GLuint texture, bool hasColor, int color, int alpha, SkXfermode::Mode mode,
         GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount,
-        bool ignoreTransform, bool dirty) {
+        bool ignoreTransform, bool ignoreScale, bool dirty) {
 
     setupDraw();
     setupDrawWithTexture(true);
@@ -3377,7 +3430,11 @@
     setupDrawBlending(true, mode);
     setupDrawProgram();
     if (!dirty) setupDrawDirtyRegionsDisabled();
-    setupDrawModelView(left, top, right, bottom, ignoreTransform);
+    if (!ignoreScale) {
+        setupDrawModelView(left, top, right, bottom, ignoreTransform);
+    } else {
+        setupDrawModelViewTranslate(left, top, right, bottom, ignoreTransform);
+    }
     setupDrawTexture(texture);
     setupDrawPureColorUniforms();
     setupDrawColorFilterUniforms();
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index dd7a5a2..a0ad888 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -71,10 +71,17 @@
     kStateDeferFlag_Clip = 0x2
 };
 
+enum DrawOpMode {
+    kDrawOpMode_Immediate,
+    kDrawOpMode_Defer,
+    kDrawOpMode_Flush
+};
+
 struct DeferredDisplayState {
-    Rect mBounds; // local bounds, mapped with matrix to be in screen space coordinates, clipped.
+    Rect mBounds; // global op bounds, mapped by mMatrix to be in screen space coordinates, clipped.
 
     // the below are set and used by the OpenGLRenderer at record and deferred playback
+    bool mClipValid;
     Rect mClip;
     mat4 mMatrix;
     DrawModifiers mDrawModifiers;
@@ -232,6 +239,8 @@
     virtual void outputDisplayList(DisplayList* displayList);
     virtual status_t drawLayer(Layer* layer, float x, float y);
     virtual status_t drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint);
+    status_t drawBitmaps(SkBitmap* bitmap, int bitmapCount, TextureVertex* vertices,
+            const Rect& bounds, SkPaint* paint);
     virtual status_t drawBitmap(SkBitmap* bitmap, SkMatrix* matrix, SkPaint* paint);
     virtual status_t drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop,
             float srcRight, float srcBottom, float dstLeft, float dstTop,
@@ -261,7 +270,8 @@
     virtual status_t drawPosText(const char* text, int bytesCount, int count,
             const float* positions, SkPaint* paint);
     virtual status_t drawText(const char* text, int bytesCount, int count, float x, float y,
-            const float* positions, SkPaint* paint, float length = -1.0f);
+            const float* positions, SkPaint* paint, float length = -1.0f,
+            DrawOpMode drawOpMode = kDrawOpMode_Immediate);
     virtual status_t drawRects(const float* rects, int count, SkPaint* paint);
 
     virtual void resetShader();
@@ -282,7 +292,8 @@
     SkPaint* filterPaint(SkPaint* paint);
 
     bool storeDisplayState(DeferredDisplayState& state, int stateDeferFlags);
-    void restoreDisplayState(const DeferredDisplayState& state);
+    void restoreDisplayState(const DeferredDisplayState& state, bool skipClipRestore = false);
+    void setFullScreenClip();
 
     const DrawModifiers& getDrawModifiers() { return mDrawModifiers; }
     void setDrawModifiers(const DrawModifiers& drawModifiers) { mDrawModifiers = drawModifiers; }
@@ -336,20 +347,18 @@
      * @param mode Where to store the resulting xfermode
      */
     static inline void getAlphaAndModeDirect(SkPaint* paint, int* alpha, SkXfermode::Mode* mode) {
-        if (paint) {
-            *mode = getXfermode(paint->getXfermode());
+        *mode = getXfermodeDirect(paint);
+        *alpha = getAlphaDirect(paint);
+    }
 
-            // Skia draws using the color's alpha channel if < 255
-            // Otherwise, it uses the paint's alpha
-            int color = paint->getColor();
-            *alpha = (color >> 24) & 0xFF;
-            if (*alpha == 255) {
-                *alpha = paint->getAlpha();
-            }
-        } else {
-            *mode = SkXfermode::kSrcOver_Mode;
-            *alpha = 255;
-        }
+    static inline SkXfermode::Mode getXfermodeDirect(SkPaint* paint) {
+        if (!paint) return SkXfermode::kSrcOver_Mode;
+        return getXfermode(paint->getXfermode());
+    }
+
+    static inline int getAlphaDirect(SkPaint* paint) {
+        if (!paint) return 255;
+        return paint->getAlpha();
     }
 
     /**
@@ -358,6 +367,20 @@
      */
     mat4 findBestFontTransform(const mat4& transform) const;
 
+#if DEBUG_MERGE_BEHAVIOR
+    void drawScreenSpaceColorRect(float left, float top, float right, float bottom, int color) {
+        mCaches.setScissorEnabled(false);
+
+        // should only be called outside of other draw ops, so stencil can only be in test state
+        bool stencilWasEnabled = mCaches.stencil.isTestEnabled();
+        mCaches.stencil.disable();
+
+        drawColorRect(left, top, right, bottom, color, SkXfermode::kSrcOver_Mode, true);
+
+        if (stencilWasEnabled) mCaches.stencil.enableTest();
+    }
+#endif
+
 protected:
     /**
      * Computes the projection matrix, initialize the first snapshot
@@ -778,7 +801,7 @@
     void drawAlpha8TextureMesh(float left, float top, float right, float bottom,
             GLuint texture, bool hasColor, int color, int alpha, SkXfermode::Mode mode,
             GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount,
-            bool ignoreTransform, bool dirty = true);
+            bool ignoreTransform, bool ignoreScale = false, bool dirty = true);
 
     /**
      * Draws text underline and strike-through if needed.
diff --git a/libs/hwui/utils/TinyHashMap.h b/libs/hwui/utils/TinyHashMap.h
new file mode 100644
index 0000000..8855140
--- /dev/null
+++ b/libs/hwui/utils/TinyHashMap.h
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HWUI_TINYHASHMAP_H
+#define ANDROID_HWUI_TINYHASHMAP_H
+
+#include <utils/BasicHashtable.h>
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * A very simple hash map that doesn't allow duplicate keys, overwriting the older entry.
+ *
+ * Currently, expects simple keys that are handled by hash_t()
+ */
+template <typename TKey, typename TValue>
+class TinyHashMap {
+public:
+    typedef key_value_pair_t<TKey, TValue> TEntry;
+
+    /**
+     * Puts an entry in the hash, removing any existing entry with the same key
+     */
+    void put(TKey key, TValue value) {
+        hash_t hash = hash_t(key);
+
+        ssize_t index = mTable.find(-1, hash, key);
+        if (index != -1) {
+            mTable.removeAt(index);
+        }
+
+        TEntry initEntry(key, value);
+        mTable.add(hash, initEntry);
+    }
+
+    /**
+     * Return true if key is in the map, in which case stores the value in the output ref
+     */
+    bool get(TKey key, TValue& outValue) {
+        hash_t hash = hash_t(key);
+        ssize_t index = mTable.find(-1, hash, key);
+        if (index == -1) {
+            return false;
+        }
+        outValue = mTable.entryAt(index).value;
+        return true;
+    }
+
+    void clear() { mTable.clear(); }
+
+private:
+    BasicHashtable<TKey, TEntry> mTable;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_TINYHASHMAP_H
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 4eb0c56..6872278 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -215,6 +215,7 @@
 
     public static final int MEDIA_DRM_KEY_TYPE_STREAMING = 1;
     public static final int MEDIA_DRM_KEY_TYPE_OFFLINE = 2;
+    public static final int MEDIA_DRM_KEY_TYPE_RELEASE = 3;
 
     public final class KeyRequest {
         public KeyRequest() {}
@@ -223,28 +224,36 @@
     };
 
     /**
-     * A key request/response exchange occurs between the app and a license
-     * server to obtain the keys to decrypt encrypted content.  getKeyRequest()
-     * is used to obtain an opaque key request byte array that is delivered to the
-     * license server.  The opaque key request byte array is returned in
-     * KeyRequest.data.  The recommended URL to deliver the key request to is
+     * A key request/response exchange occurs between the app and a license server
+     * to obtain or release keys used to decrypt encrypted content.
+     * getKeyRequest() is used to obtain an opaque key request byte array that is
+     * delivered to the license server.  The opaque key request byte array is returned
+     * in KeyRequest.data.  The recommended URL to deliver the key request to is
      * returned in KeyRequest.defaultUrl.
      *
      * After the app has received the key request response from the server,
      * it should deliver to the response to the DRM engine plugin using the method
      * {@link #provideKeyResponse}.
      *
-     * @param sessonId the session ID for the drm session
+     * @param scope may be a sessionId or a keySetId, depending on the specified keyType.
+     * When the keyType is MEDIA_DRM_KEY_TYPE_STREAMING or MEDIA_DRM_KEY_TYPE_OFFLINE,
+     * scope should be set to the sessionId the keys will be provided to.  When the keyType
+     * is MEDIA_DRM_KEY_TYPE_RELEASE, scope should be set to the keySetId of the keys
+     * being released. Releasing keys from a device invalidates them for all sessions.
      * @param init container-specific data, its meaning is interpreted based on the
      * mime type provided in the mimeType parameter.  It could contain, for example,
      * the content ID, key ID or other data obtained from the content metadata that is
-     * required in generating the key request.
+     * required in generating the key request. init may be null when keyType is
+     * MEDIA_DRM_KEY_TYPE_RELEASE.
      * @param mimeType identifies the mime type of the content
-     * @param keyType specifes if the request is for streaming or offline content
+     * @param keyType specifes the type of the request. The request may be to acquire
+     * keys for streaming or offline content, or to release previously acquired
+     * keys, which are identified by a keySetId.
+
      * @param optionalParameters are included in the key request message to
      * allow a client application to provide additional message parameters to the server.
      */
-    public native KeyRequest getKeyRequest(byte[] sessionId, byte[] init,
+    public native KeyRequest getKeyRequest(byte[] scope, byte[] init,
                                            String mimeType, int keyType,
                                            HashMap<String, String> optionalParameters)
         throws MediaDrmException;
@@ -272,13 +281,11 @@
         throws MediaDrmException;
 
     /**
-     * Remove the persisted keys associated with an offline license.  Keys are persisted
-     * when {@link provideKeyResponse} is called with keys obtained from the method
-     * {@link getKeyRequest} using keyType = MEDIA_DRM_KEY_TYPE_OFFLINE.
+     * Remove the current keys from a session.
      *
-     * @param keySetId identifies the saved key set to remove
+     * @param sessionId the session ID for the DRM session
      */
-    public native void removeKeys(byte[] keySetId) throws MediaDrmException;
+    public native void removeKeys(byte[] sessionId) throws MediaDrmException;
 
     /**
      * Request an informative description of the key status for the session.  The status is
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index 1034e93..4a5e82e 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -278,7 +278,7 @@
     public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7;
     /**
      * Flag indicating a RemoteControlClient can receive changes in the media playback position
-     * through the {@link #OnPlaybackPositionUpdateListener} interface. This flag must be set
+     * through the {@link OnPlaybackPositionUpdateListener} interface. This flag must be set
      * in order for components that display the RemoteControlClient information, to display and
      * let the user control media playback position.
      * @see #setTransportControlFlags(int)
@@ -679,7 +679,7 @@
          * Called on the implementer to notify it that the playback head should be set at the given
          * position. If the position can be changed from its current value, the implementor of
          * the interface must also update the playback position using
-         * {@link RemoteControlClient#setPlaybackState(int, long, int)} to reflect the actual new
+         * {@link #setPlaybackState(int, long, float)} to reflect the actual new
          * position being used, regardless of whether it differs from the requested position.
          * Failure to do so would cause the system to not know the new actual playback position,
          * and user interface components would fail to show the user where playback resumed after
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index c2dc159..d98f08e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -105,7 +105,7 @@
     public static final boolean DEBUG = BaseStatusBar.DEBUG;
     public static final boolean SPEW = DEBUG;
     public static final boolean DUMPTRUCK = true; // extra dumpsys info
-    public static final boolean DEBUG_GESTURES = true;
+    public static final boolean DEBUG_GESTURES = false;
 
     public static final boolean DEBUG_CLINGS = false;
 
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java
index 965e378..7315aad 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java
@@ -151,7 +151,9 @@
     public void onResume(int reason) {
         if (DEBUG) Log.d(TAG, "onResume()");
         mIsShowing = KeyguardUpdateMonitor.getInstance(mContext).isKeyguardVisible();
-        maybeStartBiometricUnlock();
+        if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
+          maybeStartBiometricUnlock();
+        }
         KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateCallback);
 
         // Registers a callback which handles stopping the biometric unlock and restarting it in
@@ -269,6 +271,14 @@
         }
 
         @Override
+        public void onUserSwitchComplete(int userId) {
+            if (DEBUG) Log.d(TAG, "onUserSwitchComplete(" + userId + ")");
+            if (mBiometricUnlock != null) {
+                maybeStartBiometricUnlock();
+            }
+        }
+
+        @Override
         public void onKeyguardVisibilityChanged(boolean showing) {
             if (DEBUG) Log.d(TAG, "onKeyguardVisibilityChanged(" + showing + ")");
             boolean wasShowing = false;
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java
index ad87a4b..986dc49 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java
@@ -124,6 +124,8 @@
             mCallbacks = Lists.newArrayList();
     private ContentObserver mDeviceProvisionedObserver;
 
+    private boolean mSwitchingUser;
+
     private final Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
@@ -461,11 +463,13 @@
                         public void onUserSwitching(int newUserId, IRemoteCallback reply) {
                             mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCHING,
                                     newUserId, 0, reply));
+                            mSwitchingUser = true;
                         }
                         @Override
                         public void onUserSwitchComplete(int newUserId) throws RemoteException {
                             mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCH_COMPLETE,
                                     newUserId));
+                            mSwitchingUser = false;
                         }
                     });
         } catch (RemoteException e) {
@@ -529,7 +533,6 @@
                 cb.onUserSwitching(userId);
             }
         }
-        setAlternateUnlockEnabled(false);
         try {
             reply.sendResult(null);
         } catch (RemoteException e) {
@@ -733,6 +736,10 @@
         return mKeyguardIsVisible;
     }
 
+    public boolean isSwitchingUser() {
+        return mSwitchingUser;
+    }
+
     private static boolean isBatteryUpdateInteresting(BatteryStatus old, BatteryStatus current) {
         final boolean nowPluggedIn = current.isPluggedIn();
         final boolean wasPluggedIn = old.isPluggedIn();
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java
index c49228e..08a95a6 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java
@@ -319,8 +319,9 @@
                 mSwitchingUser = true;
                 resetStateLocked(null);
                 adjustStatusBarLocked();
-                // Disable face unlock when the user switches.
-                KeyguardUpdateMonitor.getInstance(mContext).setAlternateUnlockEnabled(false);
+                // When we switch users we want to bring the new user to the biometric unlock even
+                // if the current user has gone to the backup.
+                KeyguardUpdateMonitor.getInstance(mContext).setAlternateUnlockEnabled(true);
             }
         }
 
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index 110c4da..128a49f 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1817,11 +1817,13 @@
                 addServiceLocked(this, userState);
                 if (userState.mBindingServices.contains(mComponentName)) {
                     userState.mBindingServices.remove(mComponentName);
-                    onUserStateChangedLocked(userState);
                     try {
-                        mServiceInterface.setConnection(this, mId);
+                       mServiceInterface.setConnection(this, mId);
+                       onUserStateChangedLocked(userState);
                     } catch (RemoteException re) {
-                        Slog.w(LOG_TAG, "Error while setting connection for service: " + service, re);
+                        Slog.w(LOG_TAG, "Error while setting connection for service: "
+                                + service, re);
+                        binderDied();
                     }
                 } else {
                     binderDied();
@@ -2499,7 +2501,9 @@
             public void flush() {
                 synchronized (mLock) {
                     cancelAllPendingEventsLocked();
-                    mSentEventsVerifier.reset();
+                    if (mSentEventsVerifier != null) {
+                        mSentEventsVerifier.reset();
+                    }
                 }
             }
 
diff --git a/services/java/com/android/server/updates/ConfigUpdateInstallReceiver.java b/services/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
index c94f7c1..9601e9a 100644
--- a/services/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
+++ b/services/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
@@ -56,9 +56,9 @@
 
     private static final String UPDATE_CERTIFICATE_KEY = "config_update_certificate";
 
-    private final File updateDir;
-    private final File updateContent;
-    private final File updateVersion;
+    protected final File updateDir;
+    protected final File updateContent;
+    protected final File updateVersion;
 
     public ConfigUpdateInstallReceiver(String updateDir, String updateContentPath,
                                        String updateMetadataPath, String updateVersionPath) {
@@ -222,7 +222,7 @@
         return signer.verify(Base64.decode(signature.getBytes(), Base64.DEFAULT));
     }
 
-    private void writeUpdate(File dir, File file, byte[] content) throws IOException {
+    protected void writeUpdate(File dir, File file, byte[] content) throws IOException {
         FileOutputStream out = null;
         File tmp = null;
         try {
diff --git a/services/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java b/services/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java
index 748849e..e8337f6 100644
--- a/services/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java
+++ b/services/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java
@@ -18,28 +18,127 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.os.FileUtils;
 import android.os.SELinux;
+import android.os.SystemProperties;
 import android.provider.Settings;
 import android.util.Base64;
 import android.util.Slog;
 
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 
+import libcore.io.ErrnoException;
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+
 public class SELinuxPolicyInstallReceiver extends ConfigUpdateInstallReceiver {
 
+    private static final String TAG = "SELinuxPolicyInstallReceiver";
+
+    private static final String sepolicyPath = "sepolicy";
+    private static final String fileContextsPath = "file_contexts";
+    private static final String propertyContextsPath = "property_contexts";
+    private static final String seappContextsPath = "seapp_contexts";
+
     public SELinuxPolicyInstallReceiver() {
-        super("/data/security/", "sepolicy", "metadata/", "version");
+        super("/data/security/bundle", "sepolicy_bundle", "metadata/", "version");
     }
 
-    @Override
-    protected void install(byte[] encodedContent, int version) throws IOException {
-        super.install(Base64.decode(encodedContent, Base64.DEFAULT), version);
+    private void backupContexts(File contexts) {
+        new File(contexts, seappContextsPath).renameTo(
+                new File(contexts, seappContextsPath + "_backup"));
+
+        new File(contexts, propertyContextsPath).renameTo(
+                new File(contexts, propertyContextsPath + "_backup"));
+
+        new File(contexts, fileContextsPath).renameTo(
+                new File(contexts, fileContextsPath + "_backup"));
+
+        new File(contexts, sepolicyPath).renameTo(
+                new File(contexts, sepolicyPath + "_backup"));
+    }
+
+    private void copyUpdate(File contexts) {
+        new File(updateDir, seappContextsPath).renameTo(new File(contexts, seappContextsPath));
+        new File(updateDir, propertyContextsPath).renameTo(new File(contexts, propertyContextsPath));
+        new File(updateDir, fileContextsPath).renameTo(new File(contexts, fileContextsPath));
+        new File(updateDir, sepolicyPath).renameTo(new File(contexts, sepolicyPath));
+    }
+
+    private int readInt(BufferedInputStream reader) throws IOException {
+        int value = 0;
+        for (int i=0; i < 4; i++) {
+            value = (value << 8) | reader.read();
+        }
+        return value;
+    }
+
+    private int[] readChunkLengths(BufferedInputStream bundle) throws IOException {
+        int[] chunks = new int[4];
+        chunks[0] = readInt(bundle);
+        chunks[1] = readInt(bundle);
+        chunks[2] = readInt(bundle);
+        chunks[3] = readInt(bundle);
+        return chunks;
+    }
+
+    private void installFile(File destination, BufferedInputStream stream, int length)
+            throws IOException {
+        byte[] chunk = new byte[length];
+        stream.read(chunk, 0, length);
+        writeUpdate(updateDir, destination, Base64.decode(chunk, Base64.DEFAULT));
+    }
+
+    private void unpackBundle() throws IOException {
+        BufferedInputStream stream = new BufferedInputStream(new FileInputStream(updateContent));
+        int[] chunkLengths = readChunkLengths(stream);
+        installFile(new File(updateDir, seappContextsPath), stream, chunkLengths[0]);
+        installFile(new File(updateDir, propertyContextsPath), stream, chunkLengths[1]);
+        installFile(new File(updateDir, fileContextsPath), stream, chunkLengths[2]);
+        installFile(new File(updateDir, sepolicyPath), stream, chunkLengths[3]);
+    }
+
+    private void applyUpdate() throws IOException, ErrnoException {
+        Slog.i(TAG, "Applying SELinux policy");
+        File contexts = new File(updateDir.getParentFile(), "contexts");
+        File current = new File(updateDir.getParentFile(), "current");
+        File update = new File(updateDir.getParentFile(), "update");
+        File tmp = new File(updateDir.getParentFile(), "tmp");
+        if (current.exists()) {
+            Libcore.os.symlink(updateDir.getPath(), update.getPath());
+            Libcore.os.rename(update.getPath(), current.getPath());
+        } else {
+            Libcore.os.symlink(updateDir.getPath(), current.getPath());
+        }
+        contexts.mkdirs();
+        backupContexts(contexts);
+        copyUpdate(contexts);
+        Libcore.os.symlink(contexts.getPath(), tmp.getPath());
+        Libcore.os.rename(tmp.getPath(), current.getPath());
+        SystemProperties.set("selinux.reload_policy", "1");
+    }
+
+    private void setEnforcingMode(Context context) {
+        boolean mode = Settings.Global.getInt(context.getContentResolver(),
+            Settings.Global.SELINUX_STATUS, 0) == 1;
+        SELinux.setSELinuxEnforce(mode);
     }
 
     @Override
     protected void postInstall(Context context, Intent intent) {
-           boolean mode = Settings.Global.getInt(context.getContentResolver(),
-                                                Settings.Global.SELINUX_STATUS, 0) == 1;
-           SELinux.setSELinuxEnforce(mode);
+        try {
+            unpackBundle();
+            applyUpdate();
+            setEnforcingMode(context);
+        } catch (IllegalArgumentException e) {
+            Slog.e(TAG, "SELinux policy update malformed: ", e);
+        } catch (IOException e) {
+            Slog.e(TAG, "Could not update selinux policy: ", e);
+        } catch (ErrnoException e) {
+            Slog.e(TAG, "Could not update selinux policy: ", e);
+        }
     }
 }
diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java
index ca060f4..788d514 100644
--- a/services/java/com/android/server/wm/WindowState.java
+++ b/services/java/com/android/server/wm/WindowState.java
@@ -19,6 +19,7 @@
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD;
@@ -923,6 +924,7 @@
         return mContentChanged && !mExiting && !mWinAnimator.mLastHidden && mService.okToDisplay()
                 && (mFrame.top != mLastFrame.top
                         || mFrame.left != mLastFrame.left)
+                && (mAttrs.privateFlags&PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
                 && (mAttachedWindow == null || !mAttachedWindow.shouldAnimateMove());
     }
 
diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java
index d3d5b1b..23a4e71 100644
--- a/wifi/java/android/net/wifi/WifiConfigStore.java
+++ b/wifi/java/android/net/wifi/WifiConfigStore.java
@@ -1111,7 +1111,8 @@
                 break setVariables;
             }
 
-            if (config.enterpriseConfig != null) {
+            if (config.enterpriseConfig != null &&
+                    config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) {
 
                 WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
 
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index f73a13c..4e7497c 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -19,17 +19,12 @@
 import android.os.Parcelable;
 import android.os.Process;
 import android.security.Credentials;
+import android.security.KeyStore;
 import android.text.TextUtils;
 
-import com.android.org.bouncycastle.asn1.ASN1InputStream;
-import com.android.org.bouncycastle.asn1.ASN1Sequence;
-import com.android.org.bouncycastle.asn1.DEROctetString;
-import com.android.org.bouncycastle.asn1.x509.BasicConstraints;
-
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.security.KeyFactory;
-import java.security.KeyStore;
 import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.cert.Certificate;
@@ -481,7 +476,8 @@
         String caCertName = Credentials.CA_CERTIFICATE + name;
         if (mClientCertificate != null) {
             byte[] privKeyData = mClientPrivateKey.getEncoded();
-            ret = keyStore.importKey(privKeyName, privKeyData, Process.WIFI_UID);
+            ret = keyStore.importKey(privKeyName, privKeyData, Process.WIFI_UID,
+                            KeyStore.FLAG_ENCRYPTED);
             if (ret == false) {
                 return ret;
             }
@@ -525,7 +521,7 @@
             Certificate cert) {
         try {
             byte[] certData = Credentials.convertToPem(cert);
-            return keyStore.put(name, certData, Process.WIFI_UID);
+            return keyStore.put(name, certData, Process.WIFI_UID, KeyStore.FLAG_ENCRYPTED);
         } catch (IOException e1) {
             return false;
         } catch (CertificateException e2) {
@@ -533,7 +529,7 @@
         }
     }
 
-    void removeKeys(android.security.KeyStore keyStore) {
+    void removeKeys(KeyStore keyStore) {
         String client = getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX);
         // a valid client certificate is configured
         if (!TextUtils.isEmpty(client)) {