Merge "Vsyncs are hard" into lmp-mr1-dev
diff --git a/api/current.txt b/api/current.txt
index 05b7ce0..3e11cda 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -28619,6 +28619,7 @@
     method public int describeContents();
     method public java.lang.CharSequence getCarrierName();
     method public java.lang.String getCountryIso();
+    method public int getDataRoaming();
     method public java.lang.CharSequence getDisplayName();
     method public java.lang.String getIccId();
     method public int getIconTint();
@@ -28641,6 +28642,8 @@
     method public java.util.List<android.telephony.SubscriptionInfo> getActiveSubscriptionInfoList();
     method public boolean isNetworkRoaming(int);
     method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
+    field public static final int DATA_ROAMING_DISABLE = 0; // 0x0
+    field public static final int DATA_ROAMING_ENABLE = 1; // 0x1
   }
 
   public static class SubscriptionManager.OnSubscriptionsChangedListener {
@@ -28686,9 +28689,9 @@
     method public boolean isVoiceCapable();
     method public void listen(android.telephony.PhoneStateListener, int);
     method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
-    method public boolean setGlobalPreferredNetworkType();
     method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
     method public boolean setOperatorBrandOverride(java.lang.String);
+    method public boolean setPreferredNetworkTypeToGlobal();
     method public boolean setVoiceMailNumber(java.lang.String, java.lang.String);
     field public static final java.lang.String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE";
     field public static final java.lang.String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE";
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 1211260..7e15ec2 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -465,20 +465,20 @@
             = "android.app.action.SET_NEW_PASSWORD";
 
     /**
-     * Flag used by {@link #addCrossProfileIntentFilter} to allow access
-     * <em>from</em> a managed profile <em>to</em> its parent. That is, any
-     * matching activities in the parent profile are included in the
-     * disambiguation list shown when an app in the managed profile calls
-     * {@link Activity#startActivity(Intent)}.
+     * Flag used by {@link #addCrossProfileIntentFilter} to allow activities in
+     * the parent profile to access intents sent from the managed profile.
+     * That is, when an app in the managed profile calls
+     * {@link Activity#startActivity(Intent)}, the intent can be resolved by a
+     * matching activity in the parent profile.
      */
     public static final int FLAG_PARENT_CAN_ACCESS_MANAGED = 0x0001;
 
     /**
-     * Flag used by {@link #addCrossProfileIntentFilter} to allow access
-     * <em>from</em> a parent <em>to</em> its managed profile. That is, any
-     * matching activities in the managed profile are included in the
-     * disambiguation list shown when an app in the parent profile calls
-     * {@link Activity#startActivity(Intent)}.
+     * Flag used by {@link #addCrossProfileIntentFilter} to allow activities in
+     * the managed profile to access intents sent from the parent profile.
+     * That is, when an app in the parent profile calls
+     * {@link Activity#startActivity(Intent)}, the intent can be resolved by a
+     * matching activity in the managed profile.
      */
     public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 0x0002;
 
diff --git a/core/java/android/app/backup/BackupHelperDispatcher.java b/core/java/android/app/backup/BackupHelperDispatcher.java
index 5466db5..6811532 100644
--- a/core/java/android/app/backup/BackupHelperDispatcher.java
+++ b/core/java/android/app/backup/BackupHelperDispatcher.java
@@ -50,7 +50,6 @@
         Header header = new Header();
         TreeMap<String,BackupHelper> helpers = (TreeMap<String,BackupHelper>)mHelpers.clone();
         FileDescriptor oldStateFD = null;
-        FileDescriptor newStateFD = newState.getFileDescriptor();
 
         if (oldState != null) {
             oldStateFD = oldState.getFileDescriptor();
diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java
index 7ad3470..916f19d 100644
--- a/core/java/com/android/internal/util/StateMachine.java
+++ b/core/java/com/android/internal/util/StateMachine.java
@@ -25,6 +25,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Collection;
@@ -1940,19 +1941,25 @@
      * @param args
      */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println(this.toString());
+        // Cannot just invoke pw.println(this.toString()) because if the
+        // resulting string is to long it won't be displayed.
+        pw.println(getName() + ":");
+        pw.println(" total records=" + getLogRecCount());
+        for (int i = 0; i < getLogRecSize(); i++) {
+            pw.println(" rec[" + i + "]: " + getLogRec(i).toString());
+            pw.flush();
+        }
+        pw.println("curState=" + getCurrentState().getName());
     }
 
     @Override
     public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append(getName() + ":\n");
-        sb.append(" total records=" + getLogRecCount() + "\n");
-        for (int i = 0; i < getLogRecSize(); i++) {
-            sb.append(" rec[" + i + "]: " + getLogRec(i).toString() + "\n");
-        }
-        sb.append("curState=" + getCurrentState().getName());
-        return sb.toString();
+        StringWriter sr = new StringWriter();
+        PrintWriter pr = new PrintWriter(sr);
+        dump(null, pr, null);
+        pr.flush();
+        pr.close();
+        return sr.toString();
     }
 
     /**
diff --git a/core/res/res/layout-land/date_picker_holo.xml b/core/res/res/layout-land/date_picker_holo.xml
index 9a9b8b0..991888c 100644
--- a/core/res/res/layout-land/date_picker_holo.xml
+++ b/core/res/res/layout-land/date_picker_holo.xml
@@ -18,12 +18,14 @@
               android:layout_width="match_parent"
               android:layout_height="@dimen/datepicker_view_animator_height"
               android:gravity="center"
-              android:orientation="horizontal" >
+              android:orientation="horizontal"
+              android:minWidth="@dimen/datepicker_dialog_width" >
 
     <include
         layout="@layout/date_picker_selected_date"
         android:layout_width="wrap_content"
-        android:layout_height="match_parent" />
+        android:layout_height="match_parent"
+        android:layout_weight="1" />
 
     <include layout="@layout/date_picker_view_animator" />
 
diff --git a/core/res/res/layout/date_picker_selected_date.xml b/core/res/res/layout/date_picker_selected_date.xml
index d212cb0..9becb81 100644
--- a/core/res/res/layout/date_picker_selected_date.xml
+++ b/core/res/res/layout/date_picker_selected_date.xml
@@ -20,22 +20,22 @@
     android:layout_width="@dimen/datepicker_component_width"
     android:layout_height="wrap_content"
     android:gravity="center"
-    android:paddingBottom="8dp"
     android:orientation="vertical">
 
     <TextView
         android:id="@+id/date_picker_header"
-        android:layout_width="@dimen/datepicker_component_width"
+        android:layout_width="match_parent"
         android:layout_height="@dimen/datepicker_header_height"
         android:gravity="center"
-        android:importantForAccessibility="no"
-        android:layout_marginBottom="8dp" />
+        android:importantForAccessibility="no" />
 
     <LinearLayout
         android:id="@+id/date_picker_month_day_year_layout"
         android:layout_width="match_parent"
         android:layout_height="0dp"
         android:layout_weight="1"
+        android:paddingTop="4dp"
+        android:paddingBottom="4dp"
         android:orientation="vertical"
         android:gravity="center">
 
@@ -57,8 +57,8 @@
                 android:id="@+id/date_picker_day"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:layout_marginTop="-10dip"
-                android:layout_marginBottom="-10dip"
+                android:layout_marginTop="-23dp"
+                android:layout_marginBottom="-20dp"
                 android:duplicateParentState="true"
                 android:gravity="center" />
         </LinearLayout>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index ae76984..02fa128 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -385,12 +385,13 @@
     <dimen name="datepicker_month_day_label_text_size">12sp</dimen>
     <dimen name="datepicker_month_list_item_header_height">48dp</dimen>
     <dimen name="datepicker_day_number_select_circle_radius">16dp</dimen>
-    <dimen name="datepicker_view_animator_height">244dp</dimen>
+    <dimen name="datepicker_view_animator_height">226dp</dimen>
 
     <dimen name="datepicker_year_picker_padding_top">8dp</dimen>
     <dimen name="datepicker_year_label_height">64dp</dimen>
     <dimen name="datepicker_year_label_text_size">22dp</dimen>
-    <dimen name="datepicker_component_width">270dp</dimen>
+    <dimen name="datepicker_component_width">260dp</dimen>
+    <dimen name="datepicker_dialog_width">520dp</dimen>
     <dimen name="datepicker_selected_date_day_size">88dp</dimen>
     <dimen name="datepicker_selected_date_month_size">24dp</dimen>
     <dimen name="datepicker_selected_date_year_size">24dp</dimen>
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index ba1e86c..bb1d3cb 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -214,7 +214,7 @@
         final boolean canUseHardware = c.isHardwareAccelerated();
         if (mCanUseHardware != canUseHardware && mCanUseHardware) {
             // We've switched from hardware to non-hardware mode. Panic.
-            cancelHardwareAnimations(false);
+            cancelHardwareAnimations(true);
         }
         mCanUseHardware = canUseHardware;
 
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index cc42aac..fae4902 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -148,7 +148,7 @@
         final boolean canUseHardware = c.isHardwareAccelerated();
         if (mCanUseHardware != canUseHardware && mCanUseHardware) {
             // We've switched from hardware to non-hardware mode. Panic.
-            cancelHardwareAnimations(false);
+            cancelHardwareAnimations(true);
         }
         mCanUseHardware = canUseHardware;
 
diff --git a/include/androidfw/BackupHelpers.h b/include/androidfw/BackupHelpers.h
index 1bb04a7..0841af6 100644
--- a/include/androidfw/BackupHelpers.h
+++ b/include/androidfw/BackupHelpers.h
@@ -152,7 +152,7 @@
     KeyedVector<String8,FileRec> m_files;
 };
 
-#define TEST_BACKUP_HELPERS 1
+//#define TEST_BACKUP_HELPERS 1
 
 #if TEST_BACKUP_HELPERS
 int backup_helper_test_empty();
diff --git a/libs/androidfw/BackupHelpers.cpp b/libs/androidfw/BackupHelpers.cpp
index 52dce9f..33cf8ef 100644
--- a/libs/androidfw/BackupHelpers.cpp
+++ b/libs/androidfw/BackupHelpers.cpp
@@ -225,8 +225,6 @@
     file_metadata_v1 metadata;
 
     char* buf = (char*)malloc(bufsize);
-    int crc = crc32(0L, Z_NULL, 0);
-
 
     fileSize = lseek(fd, 0, SEEK_END);
     lseek(fd, 0, SEEK_SET);
@@ -310,8 +308,12 @@
 }
 
 static int
-compute_crc32(int fd)
-{
+compute_crc32(const char* file, FileRec* out) {
+    int fd = open(file, O_RDONLY);
+    if (fd < 0) {
+        return -1;
+    }
+
     const int bufsize = 4*1024;
     int amt;
 
@@ -324,8 +326,11 @@
         crc = crc32(crc, (Bytef*)buf, amt);
     }
 
+    close(fd);
     free(buf);
-    return crc;
+
+    out->s.crc32 = crc;
+    return NO_ERROR;
 }
 
 int
@@ -353,7 +358,8 @@
 
         err = stat(file, &st);
         if (err != 0) {
-            r.deleted = true;
+            // not found => treat as deleted
+            continue;
         } else {
             r.deleted = false;
             r.s.modTime_sec = st.st_mtime;
@@ -361,12 +367,17 @@
             //r.s.modTime_nsec = st.st_mtime_nsec;
             r.s.mode = st.st_mode;
             r.s.size = st.st_size;
-            // we compute the crc32 later down below, when we already have the file open.
 
             if (newSnapshot.indexOfKey(key) >= 0) {
                 LOGP("back_up_files key already in use '%s'", key.string());
                 return -1;
             }
+
+            // compute the CRC
+            if (compute_crc32(file, &r) != NO_ERROR) {
+                ALOGW("Unable to open file %s", file);
+                continue;
+            }
         }
         newSnapshot.add(key, r);
     }
@@ -374,49 +385,41 @@
     int n = 0;
     int N = oldSnapshot.size();
     int m = 0;
+    int M = newSnapshot.size();
 
-    while (n<N && m<fileCount) {
+    while (n<N && m<M) {
         const String8& p = oldSnapshot.keyAt(n);
         const String8& q = newSnapshot.keyAt(m);
         FileRec& g = newSnapshot.editValueAt(m);
         int cmp = p.compare(q);
-        if (g.deleted || cmp < 0) {
-            // file removed
+        if (cmp < 0) {
+            // file present in oldSnapshot, but not present in newSnapshot
             LOGP("file removed: %s", p.string());
-            g.deleted = true; // They didn't mention the file, but we noticed that it's gone.
-            dataStream->WriteEntityHeader(p, -1);
+            write_delete_file(dataStream, p);
             n++;
-        }
-        else if (cmp > 0) {
+        } else if (cmp > 0) {
             // file added
-            LOGP("file added: %s", g.file.string());
+            LOGP("file added: %s crc=0x%08x", g.file.string(), g.s.crc32);
             write_update_file(dataStream, q, g.file.string());
             m++;
-        }
-        else {
-            // both files exist, check them
+        } else {
+            // same file exists in both old and new; check whether to update
             const FileState& f = oldSnapshot.valueAt(n);
 
-            int fd = open(g.file.string(), O_RDONLY);
-            if (fd < 0) {
-                // We can't open the file.  Don't report it as a delete either.  Let the
-                // server keep the old version.  Maybe they'll be able to deal with it
-                // on restore.
-                LOGP("Unable to open file %s - skipping", g.file.string());
-            } else {
-                g.s.crc32 = compute_crc32(fd);
-
-                LOGP("%s", q.string());
-                LOGP("  new: modTime=%d,%d mode=%04o size=%-3d crc32=0x%08x",
-                        f.modTime_sec, f.modTime_nsec, f.mode, f.size, f.crc32);
-                LOGP("  old: modTime=%d,%d mode=%04o size=%-3d crc32=0x%08x",
-                        g.s.modTime_sec, g.s.modTime_nsec, g.s.mode, g.s.size, g.s.crc32);
-                if (f.modTime_sec != g.s.modTime_sec || f.modTime_nsec != g.s.modTime_nsec
-                        || f.mode != g.s.mode || f.size != g.s.size || f.crc32 != g.s.crc32) {
+            LOGP("%s", q.string());
+            LOGP("  old: modTime=%d,%d mode=%04o size=%-3d crc32=0x%08x",
+                    f.modTime_sec, f.modTime_nsec, f.mode, f.size, f.crc32);
+            LOGP("  new: modTime=%d,%d mode=%04o size=%-3d crc32=0x%08x",
+                    g.s.modTime_sec, g.s.modTime_nsec, g.s.mode, g.s.size, g.s.crc32);
+            if (f.modTime_sec != g.s.modTime_sec || f.modTime_nsec != g.s.modTime_nsec
+                    || f.mode != g.s.mode || f.size != g.s.size || f.crc32 != g.s.crc32) {
+                int fd = open(g.file.string(), O_RDONLY);
+                if (fd < 0) {
+                    ALOGE("Unable to read file for backup: %s", g.file.string());
+                } else {
                     write_update_file(dataStream, fd, g.s.mode, p, g.file.string());
+                    close(fd);
                 }
-
-                close(fd);
             }
             n++;
             m++;
@@ -425,12 +428,12 @@
 
     // these were deleted
     while (n<N) {
-        dataStream->WriteEntityHeader(oldSnapshot.keyAt(n), -1);
+        write_delete_file(dataStream, oldSnapshot.keyAt(n));
         n++;
     }
 
     // these were added
-    while (m<fileCount) {
+    while (m<M) {
         const String8& q = newSnapshot.keyAt(m);
         FileRec& g = newSnapshot.editValueAt(m);
         write_update_file(dataStream, q, g.file.string());
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
index 06e8574..d898555 100644
--- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -55,7 +55,7 @@
 
 public class CaptivePortalLoginActivity extends Activity {
     private static final String TAG = "CaptivePortalLogin";
-    private static final String DEFAULT_SERVER = "clients3.google.com";
+    private static final String DEFAULT_SERVER = "connectivitycheck.android.com";
     private static final int SOCKET_TIMEOUT_MS = 10000;
 
     // Keep this in sync with NetworkMonitor.
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 48f2e42..64fb24b 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -274,12 +274,16 @@
         // Hack level over 9000: Because the subscription id is not yet valid when we see the
         // first update in handleSimStateChange, we need to force refresh all all SIM states
         // so the subscription id for them is consistent.
+        ArrayList<SubscriptionInfo> changedSubscriptions = new ArrayList<>();
         for (int i = 0; i < subscriptionInfos.size(); i++) {
             SubscriptionInfo info = subscriptionInfos.get(i);
-            refreshSimState(info.getSubscriptionId(), info.getSimSlotIndex());
+            boolean changed = refreshSimState(info.getSubscriptionId(), info.getSimSlotIndex());
+            if (changed) {
+                changedSubscriptions.add(info);
+            }
         }
-        for (int i = 0; i < subscriptionInfos.size(); i++) {
-            SimData data = mSimDatas.get(mSubscriptionInfo.get(i).getSubscriptionId());
+        for (int i = 0; i < changedSubscriptions.size(); i++) {
+            SimData data = mSimDatas.get(changedSubscriptions.get(i).getSubscriptionId());
             for (int j = 0; j < mCallbacks.size(); j++) {
                 KeyguardUpdateMonitorCallback cb = mCallbacks.get(j).get();
                 if (cb != null) {
@@ -1242,7 +1246,10 @@
         }
     }
 
-    private void refreshSimState(int subId, int slotId) {
+    /**
+     * @return true if and only if the state has changed for the specified {@code slotId}
+     */
+    private boolean refreshSimState(int subId, int slotId) {
 
         // This is awful. It exists because there are two APIs for getting the SIM status
         // that don't return the complete set of values and have different types. In Keyguard we
@@ -1256,8 +1263,18 @@
         } catch(IllegalArgumentException ex) {
             Log.w(TAG, "Unknown sim state: " + simState);
             state = State.UNKNOWN;
-	}
-        mSimDatas.put(subId, new SimData(state, slotId, subId));
+        }
+        SimData data = mSimDatas.get(subId);
+        final boolean changed;
+        if (data == null) {
+            data = new SimData(state, slotId, subId);
+            mSimDatas.put(subId, data);
+            changed = true; // no data yet; force update
+        } else {
+            changed = data.simState != state;
+            data.simState = state;
+        }
+        return changed;
     }
 
     public static boolean isSimPinSecure(IccCardConstants.State state) {
diff --git a/packages/SystemUI/src/com/android/systemui/EventLogConstants.java b/packages/SystemUI/src/com/android/systemui/EventLogConstants.java
new file mode 100644
index 0000000..c8af2d4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/EventLogConstants.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui;
+
+/**
+ * Constants to be passed as sysui_* eventlog parameters.
+ */
+public class EventLogConstants {
+    /** The user swiped up on the lockscreen, unlocking the device. */
+    public static final int SYSUI_LOCKSCREEN_GESTURE_SWIPE_UP_UNLOCK = 1;
+    /** The user swiped down on the lockscreen, going to the full shade. */
+    public static final int SYSUI_LOCKSCREEN_GESTURE_SWIPE_DOWN_FULL_SHADE = 2;
+    /** The user tapped in an empty area, causing the unlock hint to be shown. */
+    public static final int SYSUI_LOCKSCREEN_GESTURE_TAP_UNLOCK_HINT = 3;
+    /** The user swiped inward on the camera icon, launching the camera. */
+    public static final int SYSUI_LOCKSCREEN_GESTURE_SWIPE_CAMERA = 4;
+    /** The user swiped inward on the dialer icon, launching the dialer. */
+    public static final int SYSUI_LOCKSCREEN_GESTURE_SWIPE_DIALER = 5;
+    /** The user tapped the lock, locking the device. */
+    public static final int SYSUI_LOCKSCREEN_GESTURE_TAP_LOCK = 6;
+    /** The user tapped a notification, needs to tap again to launch. */
+    public static final int SYSUI_LOCKSCREEN_GESTURE_TAP_NOTIFICATION_ACTIVATE = 7;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
index 191cba5..d2ce94b 100644
--- a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
+++ b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
@@ -9,7 +9,14 @@
 36001 sysui_heads_up_status (key|3),(visible|1)
 36002 sysui_fullscreen_notification (key|3)
 36003 sysui_heads_up_escalation (key|3)
-36004 sysui_status_bar_state (state|1)
+# sysui_status_bar_state: Logged whenever the status bar / keyguard state changes
+## state: 0: SHADE, 1: KEYGUARD, 2: SHADE_LOCKED
+## keyguardShowing: 1: Keyguard shown to the user (or keyguardOccluded)
+## keyguardOccluded: 1: Keyguard active, but another activity is occluding it
+## bouncerShowing: 1: Bouncer currently shown to the user
+## secure: 1: The user has set up a secure unlock method (PIN, password, etc.)
+## currentlyInsecure: 1: No secure unlock method set up (!secure), or trusted environment (TrustManager)
+36004 sysui_status_bar_state (state|1),(keyguardShowing|1),(keyguardOccluded|1),(bouncerShowing|1),(secure|1),(currentlyInsecure|1)
 
 # ---------------------------
 # PhoneStatusBarView.java
@@ -20,6 +27,15 @@
 # NotificationPanelView.java
 # ---------------------------
 36020 sysui_notificationpanel_touch (type|1),(x|1),(y|1)
+## type: 1: SWIPE_UP_UNLOCK           Swiped up to dismiss the lockscreen.
+##       2: SWIPE_DOWN_FULL_SHADE     Swiped down to enter full shade.
+##       3: TAP_UNLOCK_HINT           Tapped in empty area, causes unlock hint.
+##       4: SWIPE_CAMERA              Swiped the camera icon, launches.
+##       5: SWIPE_DIALER              Swiped the dialer icon, launches.
+##       6: TAP_LOCK                  Tapped the (unlocked) lock icon, locks the device.
+##       7: TAP_NOTIFICATION_ACTIVATE Tapped a lockscreen notification, causes "tap again" hint.
+##                                    Note: Second tap logged as notification_clicked.
+36021 sysui_lockscreen_gesture (type|1),(lengthDp|1),(velocityDp|1)
 
 # ---------------------------
 # SettingsPanelView.java
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 0516768..7c725b3 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -105,10 +105,6 @@
         static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
         static final int EGL_OPENGL_ES2_BIT = 4;
 
-        // TODO: Not currently used, keeping around until we know we don't need it
-        @SuppressWarnings({"UnusedDeclaration"})
-        private WallpaperObserver mReceiver;
-
         Bitmap mBackground;
         int mBackgroundWidth = -1, mBackgroundHeight = -1;
         int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
@@ -151,22 +147,6 @@
         private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
         private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
 
-        class WallpaperObserver extends BroadcastReceiver {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                if (DEBUG) {
-                    Log.d(TAG, "onReceive");
-                }
-
-                mLastSurfaceWidth = mLastSurfaceHeight = -1;
-                mBackground = null;
-                mBackgroundWidth = -1;
-                mBackgroundHeight = -1;
-                mRedrawNeeded = true;
-                drawFrame();
-            }
-        }
-
         public DrawableEngine() {
             super();
             setFixedSizeAllowed(true);
@@ -194,12 +174,6 @@
 
             super.onCreate(surfaceHolder);
 
-            // TODO: Don't need this currently because the wallpaper service
-            // will restart the image wallpaper whenever the image changes.
-            //IntentFilter filter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
-            //mReceiver = new WallpaperObserver();
-            //registerReceiver(mReceiver, filter, null, mHandler);
-
             updateSurfaceSize(surfaceHolder);
 
             setOffsetNotificationsEnabled(false);
@@ -208,9 +182,6 @@
         @Override
         public void onDestroy() {
             super.onDestroy();
-            if (mReceiver != null) {
-                unregisterReceiver(mReceiver);
-            }
             mBackground = null;
             mWallpaperManager.forgetLoadedWallpaper();
         }
@@ -562,7 +533,7 @@
             boolean status = mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
             checkEglError();
 
-            finishGL();
+            finishGL(texture, program);
 
             return status;
         }
@@ -615,21 +586,18 @@
 
             int program = glCreateProgram();
             glAttachShader(program, vertexShader);
-            checkGlError();
-
             glAttachShader(program, fragmentShader);
-            checkGlError();
-
             glLinkProgram(program);
             checkGlError();
 
+            glDeleteShader(vertexShader);
+            glDeleteShader(fragmentShader);
+
             int[] status = new int[1];
             glGetProgramiv(program, GL_LINK_STATUS, status, 0);
             if (status[0] != GL_TRUE) {
                 String error = glGetProgramInfoLog(program);
                 Log.d(GL_LOG_TAG, "Error while linking program:\n" + error);
-                glDeleteShader(vertexShader);
-                glDeleteShader(fragmentShader);
                 glDeleteProgram(program);
                 return 0;
             }
@@ -672,7 +640,11 @@
             }
         }
 
-        private void finishGL() {
+        private void finishGL(int texture, int program) {
+            int[] textures = new int[1];
+            textures[0] = texture;
+            glDeleteTextures(1, textures, 0);
+            glDeleteProgram(program);
             mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
             mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
             mEgl.eglDestroyContext(mEglDisplay, mEglContext);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
index df475d5..c9f0260 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
@@ -126,7 +126,8 @@
                 }
                 return true;
             case MotionEvent.ACTION_UP:
-                if (mDraggedFarEnough && mDragDownCallback.onDraggedDown(mStartingChild)) {
+                if (mDraggedFarEnough && mDragDownCallback.onDraggedDown(mStartingChild,
+                        (int) (y - mInitialTouchY))) {
                     if (mStartingChild == null) {
                         mDragDownCallback.setEmptyDragAmount(0f);
                     }
@@ -221,7 +222,7 @@
         /**
          * @return true if the interaction is accepted, false if it should be cancelled
          */
-        boolean onDraggedDown(View startingChild);
+        boolean onDraggedDown(View startingChild, int dragLengthY);
         void onDragDownReset();
         void onThresholdReached();
         void onTouchSlopExceeded();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
index 58a2d41..3b8fccc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
@@ -317,7 +317,7 @@
         animator.addListener(mFlingEndListener);
         if (!snapBack) {
             startFinishingCircleAnimation(vel * 0.375f, mAnimationEndRunnable);
-            mCallback.onAnimationToSideStarted(mTranslation < 0);
+            mCallback.onAnimationToSideStarted(mTranslation < 0, mTranslation, vel);
         } else {
             reset(true);
         }
@@ -461,7 +461,7 @@
          *
          * @param rightPage Is the page animated to the right page?
          */
-        void onAnimationToSideStarted(boolean rightPage);
+        void onAnimationToSideStarted(boolean rightPage, float translation, float vel);
 
         /**
          * Notifies the callback the animation to a side page has ended.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 503f588..acf7af9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -47,6 +47,8 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.EventLogConstants;
+import com.android.systemui.EventLogTags;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
@@ -320,6 +322,9 @@
     }
 
     private void handleTrustCircleClick() {
+        EventLogTags.writeSysuiLockscreenGesture(
+                EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_LOCK, 0 /* lengthDp - N/A */,
+                0 /* velocityDp - N/A */);
         mIndicationController.showTransientIndication(
                 R.string.keyguard_indication_trust_disabled);
         mLockPatternUtils.requireCredentialEntry(mLockPatternUtils.getCurrentUser());
@@ -388,7 +393,7 @@
         // TODO: Real icon for facelock.
         int iconRes = mUnlockMethodCache.isFaceUnlockRunning()
                 ? com.android.internal.R.drawable.ic_account_circle
-                : mUnlockMethodCache.isMethodInsecure() ? R.drawable.ic_lock_open_24dp
+                : mUnlockMethodCache.isCurrentlyInsecure() ? R.drawable.ic_lock_open_24dp
                 : R.drawable.ic_lock_24dp;
         if (mLastUnlockIconRes != iconRes) {
             Drawable icon = mContext.getDrawable(iconRes);
@@ -438,7 +443,7 @@
     }
 
     @Override
-    public void onMethodSecureChanged(boolean methodSecure) {
+    public void onUnlockMethodStateChanged() {
         updateLockIcon();
         updateCameraVisibility();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 3292f9b..5bc1321 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -212,6 +212,9 @@
         return false;
     }
 
+    /**
+     * WARNING: This method might cause Binder calls.
+     */
     public boolean isSecure() {
         return mKeyguardView == null || mKeyguardView.getSecurityMode() != SecurityMode.None;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 1b00e59..e30a5c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -39,6 +39,8 @@
 import android.widget.TextView;
 
 import com.android.keyguard.KeyguardStatusView;
+import com.android.systemui.EventLogTags;
+import com.android.systemui.EventLogConstants;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSContainer;
 import com.android.systemui.qs.QSPanel;
@@ -1670,13 +1672,20 @@
     }
 
     @Override
-    public void onAnimationToSideStarted(boolean rightPage) {
+    public void onAnimationToSideStarted(boolean rightPage, float translation, float vel) {
         boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? rightPage : !rightPage;
         mIsLaunchTransitionRunning = true;
         mLaunchAnimationEndRunnable = null;
+        float displayDensity = mStatusBar.getDisplayDensity();
+        int lengthDp = Math.abs((int) (translation / displayDensity));
+        int velocityDp = Math.abs((int) (vel / displayDensity));
         if (start) {
+            EventLogTags.writeSysuiLockscreenGesture(
+                    EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_DIALER, lengthDp, velocityDp);
             mKeyguardBottomArea.launchPhone();
         } else {
+            EventLogTags.writeSysuiLockscreenGesture(
+                    EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_CAMERA, lengthDp, velocityDp);
             mSecureCameraLaunchManager.startSecureCameraLaunch();
         }
         mStatusBar.startLaunchTransitionTimeout();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index a044743..d86ccee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -32,6 +32,8 @@
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 
+import com.android.systemui.EventLogConstants;
+import com.android.systemui.EventLogTags;
 import com.android.systemui.R;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -338,6 +340,15 @@
                     DozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
                             mStatusBar.isFalsingThresholdNeeded(),
                             mStatusBar.isScreenOnComingFromTouch());
+                    // Log collapse gesture if on lock screen.
+                    if (!expand && mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
+                        float displayDensity = mStatusBar.getDisplayDensity();
+                        int heightDp = (int) Math.abs((y - mInitialTouchY) / displayDensity);
+                        int velocityDp = (int) Math.abs(vel / displayDensity);
+                        EventLogTags.writeSysuiLockscreenGesture(
+                                EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_UP_UNLOCK,
+                                heightDp, velocityDp);
+                    }
                     fling(vel, expand);
                     mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
                     if (mUpdateFlingOnLayout) {
@@ -941,6 +952,9 @@
         switch (mStatusBar.getBarState()) {
             case StatusBarState.KEYGUARD:
                 if (!mDozingOnDown) {
+                    EventLogTags.writeSysuiLockscreenGesture(
+                            EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_UNLOCK_HINT,
+                            0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
                     startUnlockHintAnimation();
                 }
                 return true;
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 d6647af..140c3ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -121,6 +121,7 @@
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.BatteryMeterView;
 import com.android.systemui.DemoMode;
+import com.android.systemui.EventLogConstants;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
@@ -147,6 +148,7 @@
 import com.android.systemui.statusbar.SpeedBumpView;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
 import com.android.systemui.statusbar.policy.AccessibilityController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -183,7 +185,7 @@
 import java.util.Map;
 
 public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
-        DragDownHelper.DragDownCallback, ActivityStarter {
+        DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener {
     static final String TAG = "PhoneStatusBar";
     public static final boolean DEBUG = BaseStatusBar.DEBUG;
     public static final boolean SPEW = false;
@@ -493,6 +495,9 @@
     private boolean mLaunchTransitionFadingAway;
     private ExpandableNotificationRow mDraggedDownRow;
 
+    // Fingerprint (as computed by getLoggingFingerprint() of the last logged state.
+    private int mLastLoggedStateFingerprint;
+
     private static final int VISIBLE_LOCATIONS = ViewState.LOCATION_FIRST_CARD
             | ViewState.LOCATION_TOP_STACK_PEEKING
             | ViewState.LOCATION_MAIN_AREA
@@ -603,6 +608,7 @@
                     mHeadsUpObserver);
         }
         mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
+        mUnlockMethodCache.addListener(this);
         startKeyguard();
 
         mDozeServiceHost = new DozeServiceHost();
@@ -2159,8 +2165,8 @@
 
     public boolean isFalsingThresholdNeeded() {
         boolean onKeyguard = getBarState() == StatusBarState.KEYGUARD;
-        boolean isMethodInsecure = mUnlockMethodCache.isMethodInsecure();
-        return onKeyguard && (isMethodInsecure || mDozing || mScreenOnComingFromTouch);
+        boolean isCurrentlyInsecure = mUnlockMethodCache.isCurrentlyInsecure();
+        return onKeyguard && (isCurrentlyInsecure || mDozing || mScreenOnComingFromTouch);
     }
 
     public boolean isDozing() {
@@ -2177,6 +2183,18 @@
     }
 
     /**
+     * To be called when there's a state change in StatusBarKeyguardViewManager.
+     */
+    public void onKeyguardViewManagerStatesUpdated() {
+        logStateToEventlog();
+    }
+
+    @Override  // UnlockMethodCache.OnUnlockMethodChangedListener
+    public void onUnlockMethodStateChanged() {
+        logStateToEventlog();
+    }
+
+    /**
      * All changes to the status bar and notifications funnel through here and are batched.
      */
     private class H extends BaseStatusBar.H {
@@ -3061,6 +3079,10 @@
         }
     }
 
+    float getDisplayDensity() {
+        return mDisplayMetrics.density;
+    }
+
     public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned,
             final boolean dismissShade) {
         if (onlyProvisioned && !isDeviceProvisioned()) return;
@@ -3348,6 +3370,47 @@
         }
     }
 
+    // State logging
+
+    private void logStateToEventlog() {
+        boolean isShowing = mStatusBarKeyguardViewManager.isShowing();
+        boolean isOccluded = mStatusBarKeyguardViewManager.isOccluded();
+        boolean isBouncerShowing = mStatusBarKeyguardViewManager.isBouncerShowing();
+        boolean isSecure = mUnlockMethodCache.isMethodSecure();
+        boolean isCurrentlyInsecure = mUnlockMethodCache.isCurrentlyInsecure();
+        int stateFingerprint = getLoggingFingerprint(mState,
+                isShowing,
+                isOccluded,
+                isBouncerShowing,
+                isSecure,
+                isCurrentlyInsecure);
+        if (stateFingerprint != mLastLoggedStateFingerprint) {
+            EventLogTags.writeSysuiStatusBarState(mState,
+                    isShowing ? 1 : 0,
+                    isOccluded ? 1 : 0,
+                    isBouncerShowing ? 1 : 0,
+                    isSecure ? 1 : 0,
+                    isCurrentlyInsecure ? 1 : 0);
+            mLastLoggedStateFingerprint = stateFingerprint;
+        }
+    }
+
+    /**
+     * Returns a fingerprint of fields logged to eventlog
+     */
+    private static int getLoggingFingerprint(int statusBarState, boolean keyguardShowing,
+            boolean keyguardOccluded, boolean bouncerShowing, boolean secure,
+            boolean currentlyInsecure) {
+        // Reserve 8 bits for statusBarState. We'll never go higher than
+        // that, right? Riiiight.
+        return (statusBarState & 0xFF)
+                | ((keyguardShowing   ? 1 : 0) <<  8)
+                | ((keyguardOccluded  ? 1 : 0) <<  9)
+                | ((bouncerShowing    ? 1 : 0) << 10)
+                | ((secure            ? 1 : 0) << 11)
+                | ((currentlyInsecure ? 1 : 0) << 12);
+    }
+
     //
     // tracing
     //
@@ -3824,6 +3887,9 @@
 
     @Override
     public void onActivated(ActivatableNotificationView view) {
+        EventLogTags.writeSysuiLockscreenGesture(
+                EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_NOTIFICATION_ACTIVATE,
+                0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
         mKeyguardIndicationController.showTransientIndication(R.string.notification_tap_again);
         ActivatableNotificationView previousView = mStackScroller.getActivatedChild();
         if (previousView != null) {
@@ -3836,19 +3902,15 @@
      * @param state The {@link StatusBarState} to set.
      */
     public void setBarState(int state) {
-        if (state != mState) {
-            EventLogTags.writeSysuiStatusBarState(state);
-
-            // If we're visible and switched to SHADE_LOCKED (the user dragged
-            // down on the lockscreen), clear notification LED, vibration,
-            // ringing.
-            // Other transitions are covered in handleVisibleToUserChanged().
-            if (mVisible && state == StatusBarState.SHADE_LOCKED) {
-                try {
-                    mBarService.clearNotificationEffects();
-                } catch (RemoteException e) {
-                    // Ignore.
-                }
+        // If we're visible and switched to SHADE_LOCKED (the user dragged
+        // down on the lockscreen), clear notification LED, vibration,
+        // ringing.
+        // Other transitions are covered in handleVisibleToUserChanged().
+        if (state != mState && mVisible && state == StatusBarState.SHADE_LOCKED) {
+            try {
+                mBarService.clearNotificationEffects();
+            } catch (RemoteException e) {
+                // Ignore.
             }
         }
         mState = state;
@@ -3890,7 +3952,7 @@
 
     public void onTrackingStopped(boolean expand) {
         if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
-            if (!expand && !mUnlockMethodCache.isMethodInsecure()) {
+            if (!expand && !mUnlockMethodCache.isCurrentlyInsecure()) {
                 showBouncer();
             }
         }
@@ -3908,8 +3970,12 @@
     // ---------------------- DragDownHelper.OnDragDownListener ------------------------------------
 
     @Override
-    public boolean onDraggedDown(View startingChild) {
+    public boolean onDraggedDown(View startingChild, int dragLengthY) {
         if (hasActiveNotifications()) {
+            EventLogTags.writeSysuiLockscreenGesture(
+                    EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_DOWN_FULL_SHADE,
+                    (int) (dragLengthY / mDisplayMetrics.density),
+                    0 /* velocityDp - N/A */);
 
             // We have notifications, go to locked shade.
             goToLockedShade(startingChild);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index d6bd94b..0e8a794 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -88,7 +88,7 @@
 
     public void onTrackingStarted() {
         mExpanding = true;
-        mDarkenWhileDragging = !mUnlockMethodCache.isMethodInsecure();
+        mDarkenWhileDragging = !mUnlockMethodCache.isCurrentlyInsecure();
     }
 
     public void onExpandingFinished() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index f4edab5..1724e70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -303,6 +303,9 @@
         }
     }
 
+    /**
+     * WARNING: This method might cause Binder calls.
+     */
     public boolean isSecure() {
         return mBouncer.isSecure();
     }
@@ -396,6 +399,8 @@
         mLastOccluded = occluded;
         mLastBouncerShowing = bouncerShowing;
         mLastBouncerDismissible = bouncerDismissible;
+
+        mPhoneStatusBar.onKeyguardViewManagerStatesUpdated();
     }
 
     public boolean onMenuPressed() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
index e5eef9d..5ef345b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
@@ -36,7 +36,10 @@
     private final LockPatternUtils mLockPatternUtils;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final ArrayList<OnUnlockMethodChangedListener> mListeners = new ArrayList<>();
-    private boolean mMethodInsecure;
+    /** Whether the user configured a secure unlock method (PIN, password, etc.) */
+    private boolean mSecure;
+    /** Whether the unlock method is currently insecure (insecure method or trusted environment) */
+    private boolean mCurrentlyInsecure;
     private boolean mTrustManaged;
     private boolean mFaceUnlockRunning;
 
@@ -44,7 +47,7 @@
         mLockPatternUtils = new LockPatternUtils(ctx);
         mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(ctx);
         KeyguardUpdateMonitor.getInstance(ctx).registerCallback(mCallback);
-        updateMethodSecure(true /* updateAlways */);
+        update(true /* updateAlways */);
     }
 
     public static UnlockMethodCache getInstance(Context context) {
@@ -55,10 +58,17 @@
     }
 
     /**
-     * @return whether the current security method is secure, i. e. the bouncer will be shown
+     * @return whether the user configured a secure unlock method like PIN, password, etc.
      */
-    public boolean isMethodInsecure() {
-        return mMethodInsecure;
+    public boolean isMethodSecure() {
+        return mSecure;
+    }
+
+    /**
+     * @return whether the lockscreen is currently insecure, i. e. the bouncer won't be shown
+     */
+    public boolean isCurrentlyInsecure() {
+        return mCurrentlyInsecure;
     }
 
     public void addListener(OnUnlockMethodChangedListener listener) {
@@ -69,58 +79,59 @@
         mListeners.remove(listener);
     }
 
-    private void updateMethodSecure(boolean updateAlways) {
+    private void update(boolean updateAlways) {
         int user = mLockPatternUtils.getCurrentUser();
-        boolean methodInsecure = !mLockPatternUtils.isSecure() ||
-                mKeyguardUpdateMonitor.getUserHasTrust(user);
+        boolean secure = mLockPatternUtils.isSecure();
+        boolean currentlyInsecure = !secure ||  mKeyguardUpdateMonitor.getUserHasTrust(user);
         boolean trustManaged = mKeyguardUpdateMonitor.getUserTrustIsManaged(user);
         boolean faceUnlockRunning = mKeyguardUpdateMonitor.isFaceUnlockRunning(user)
                 && trustManaged;
-        boolean changed = methodInsecure != mMethodInsecure || trustManaged != mTrustManaged
-                || faceUnlockRunning != mFaceUnlockRunning;
+        boolean changed = secure != mSecure || currentlyInsecure != mCurrentlyInsecure ||
+                trustManaged != mTrustManaged  || faceUnlockRunning != mFaceUnlockRunning;
         if (changed || updateAlways) {
-            mMethodInsecure = methodInsecure;
+            mSecure = secure;
+            mCurrentlyInsecure = currentlyInsecure;
             mTrustManaged = trustManaged;
             mFaceUnlockRunning = faceUnlockRunning;
-            notifyListeners(mMethodInsecure);
+            notifyListeners();
         }
     }
 
-    private void notifyListeners(boolean secure) {
+    private void notifyListeners() {
         for (OnUnlockMethodChangedListener listener : mListeners) {
-            listener.onMethodSecureChanged(secure);
+            listener.onUnlockMethodStateChanged();
         }
     }
 
     private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
         @Override
         public void onUserSwitchComplete(int userId) {
-            updateMethodSecure(false /* updateAlways */);
+            update(false /* updateAlways */);
         }
 
         @Override
         public void onTrustChanged(int userId) {
-            updateMethodSecure(false /* updateAlways */);
+            update(false /* updateAlways */);
         }
 
         @Override
         public void onTrustManagedChanged(int userId) {
-            updateMethodSecure(false /* updateAlways */);
+            update(false /* updateAlways */);
         }
 
         @Override
         public void onScreenTurnedOn() {
-            updateMethodSecure(false /* updateAlways */);
+            update(false /* updateAlways */);
         }
 
         @Override
         public void onFingerprintRecognized(int userId) {
-            updateMethodSecure(false /* updateAlways */);
+            update(false /* updateAlways */);
         }
 
         @Override
         public void onFaceUnlockStateChanged(boolean running, int userId) {
-            updateMethodSecure(false /* updateAlways */);
+            update(false /* updateAlways */);
         }
     };
 
@@ -133,6 +144,6 @@
     }
 
     public static interface OnUnlockMethodChangedListener {
-        void onMethodSecureChanged(boolean methodSecure);
+        void onUnlockMethodStateChanged();
     }
 }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 0f8fd05..61a7073 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -2271,6 +2271,46 @@
         }
     }
 
+    // Is nai unneeded by all NetworkRequests (and should be disconnected)?
+    // For validated Networks this is simply whether it is satsifying any NetworkRequests.
+    // For unvalidated Networks this is whether it is satsifying any NetworkRequests or
+    // were it to become validated, would it have a chance of satisfying any NetworkRequests.
+    private boolean unneeded(NetworkAgentInfo nai) {
+        if (!nai.created || nai.isVPN()) return false;
+        boolean unneeded = true;
+        if (nai.everValidated) {
+            for (int i = 0; i < nai.networkRequests.size() && unneeded; i++) {
+                final NetworkRequest nr = nai.networkRequests.valueAt(i);
+                try {
+                    if (isRequest(nr)) unneeded = false;
+                } catch (Exception e) {
+                    loge("Request " + nr + " not found in mNetworkRequests.");
+                    loge("  it came from request list  of " + nai.name());
+                }
+            }
+        } else {
+            for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+                // If this Network is already the highest scoring Network for a request, or if
+                // there is hope for it to become one if it validated, then it is needed.
+                if (nri.isRequest && nai.satisfies(nri.request) &&
+                        (nai.networkRequests.get(nri.request.requestId) != null ||
+                        // Note that this catches two important cases:
+                        // 1. Unvalidated cellular will not be reaped when unvalidated WiFi
+                        //    is currently satisfying the request.  This is desirable when
+                        //    cellular ends up validating but WiFi does not.
+                        // 2. Unvalidated WiFi will not be reaped when validated cellular
+                        //    is currently satsifying the request.  This is desirable when
+                        //    WiFi ends up validating and out scoring cellular.
+                        mNetworkForRequestId.get(nri.request.requestId).getCurrentScore() <
+                                nai.getCurrentScoreAsValidated())) {
+                    unneeded = false;
+                    break;
+                }
+            }
+        }
+        return unneeded;
+    }
+
     private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) {
         NetworkRequestInfo nri = mNetworkRequests.get(request);
         if (nri != null) {
@@ -2292,16 +2332,9 @@
                                     ", leaving " + nai.networkRequests.size() +
                                     " requests.");
                         }
-                        // check if has any requests remaining and if not,
-                        // disconnect (unless it's a VPN).
-                        boolean keep = nai.isVPN();
-                        for (int i = 0; i < nai.networkRequests.size() && !keep; i++) {
-                            NetworkRequest r = nai.networkRequests.valueAt(i);
-                            if (isRequest(r)) keep = true;
-                        }
-                        if (!keep) {
+                        if (unneeded(nai)) {
                             if (DBG) log("no live requests for " + nai.name() + "; disconnecting");
-                            nai.asyncChannel.disconnect();
+                            teardownUnneededNetwork(nai);
                         }
                     }
                 }
@@ -4056,19 +4089,7 @@
         }
         // Linger any networks that are no longer needed.
         for (NetworkAgentInfo nai : affectedNetworks) {
-            boolean teardown = !nai.isVPN() && nai.everValidated;
-            for (int i = 0; i < nai.networkRequests.size() && teardown; i++) {
-                NetworkRequest nr = nai.networkRequests.valueAt(i);
-                try {
-                if (isRequest(nr)) {
-                    teardown = false;
-                }
-                } catch (Exception e) {
-                    loge("Request " + nr + " not found in mNetworkRequests.");
-                    loge("  it came from request list  of " + nai.name());
-                }
-            }
-            if (teardown) {
+            if (nai.everValidated && unneeded(nai)) {
                 nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_LINGER);
                 notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING);
             } else {
@@ -4168,27 +4189,7 @@
         }
         if (reapUnvalidatedNetworks == ReapUnvalidatedNetworks.REAP) {
             for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
-                if (!nai.created || nai.everValidated || nai.isVPN()) continue;
-                boolean reap = true;
-                for (NetworkRequestInfo nri : mNetworkRequests.values()) {
-                    // If this Network is already the highest scoring Network for a request, or if
-                    // there is hope for it to become one if it validated, then don't reap it.
-                    if (nri.isRequest && nai.satisfies(nri.request) &&
-                            (nai.networkRequests.get(nri.request.requestId) != null ||
-                            // Note that this catches two important cases:
-                            // 1. Unvalidated cellular will not be reaped when unvalidated WiFi
-                            //    is currently satisfying the request.  This is desirable when
-                            //    cellular ends up validating but WiFi does not.
-                            // 2. Unvalidated WiFi will not be reaped when validated cellular
-                            //    is currently satsifying the request.  This is desirable when
-                            //    WiFi ends up validating and out scoring cellular.
-                            mNetworkForRequestId.get(nri.request.requestId).getCurrentScore() <
-                                    nai.getCurrentScoreAsValidated())) {
-                        reap = false;
-                        break;
-                    }
-                }
-                if (reap) {
+                if (!nai.everValidated && unneeded(nai)) {
                     if (DBG) log("Reaping " + nai.name());
                     teardownUnneededNetwork(nai);
                 }
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index 9fa362c..af5ed83 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -68,7 +68,7 @@
 public class NetworkMonitor extends StateMachine {
     private static final boolean DBG = true;
     private static final String TAG = "NetworkMonitor";
-    private static final String DEFAULT_SERVER = "clients3.google.com";
+    private static final String DEFAULT_SERVER = "connectivitycheck.android.com";
     private static final int SOCKET_TIMEOUT_MS = 10000;
     public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
             "android.net.conn.NETWORK_CONDITIONS_MEASURED";
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index f549f3d..6e61e41 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -464,13 +464,13 @@
             try {
                 SurfaceControl.screenshot(SurfaceControl.getBuiltInDisplay(
                         SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN), s);
+                st.updateTexImage();
+                st.getTransformMatrix(mTexMatrix);
             } finally {
                 s.release();
+                st.release();
             }
 
-            st.updateTexImage();
-            st.getTransformMatrix(mTexMatrix);
-
             // Set up texture coordinates for a quad.
             // We might need to change this if the texture ends up being
             // a different size from the display for some reason.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index fb7a6aa..b9a2cbe 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1166,15 +1166,15 @@
         // Just search for the start of this layer.
         final int myLayer = win.mBaseLayer;
         int i;
-        for (i = 0; i < N; i++) {
+        for (i = N - 1; i >= 0; --i) {
             WindowState w = windows.get(i);
-            if (w.mBaseLayer > myLayer) {
+            if (w.mBaseLayer <= myLayer) {
                 break;
             }
         }
         if (true || DEBUG_FOCUS_LIGHT || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG,
-                "Based on layer: Adding window " + win + " at " + i + " of " + N);
-        windows.add(i, win);
+                "Based on layer: Adding window " + win + " at " + (i + 1) + " of " + N);
+        windows.add(i + 1, win);
         mWindowsChanged = true;
         return tokenWindowsPos;
     }
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index f3b6910..adbe1d8 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -254,9 +254,8 @@
     }
 
     /**
-     * @return the data roaming state for this subscription, either DATA_ROAMING_ENABLE or
-     * DATA_ROAMING_DISABLE.
-     * @hide
+     * @return the data roaming state for this subscription, either
+     * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or {@link SubscriptionManager#DATA_ROAMING_DISABLE}.
      */
     public int getDataRoaming() {
         return this.mDataRoaming;
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 9ef6f1f..c67629d 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -226,10 +226,10 @@
     /** @hide */
     public static final String DATA_ROAMING = "data_roaming";
 
-    /** @hide */
+    /** Indicates that data roaming is enabled for a subscription */
     public static final int DATA_ROAMING_ENABLE = 1;
 
-    /** @hide */
+    /** Indicates that data roaming is disabled for a subscription */
     public static final int DATA_ROAMING_DISABLE = 0;
 
     /** @hide */
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 99d0af1..dca1f82 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3099,7 +3099,7 @@
      *
      * @return true on success; false on any failure.
      */
-    public boolean setGlobalPreferredNetworkType() {
+    public boolean setPreferredNetworkTypeToGlobal() {
         return setPreferredNetworkType(RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA);
     }
 
@@ -3142,8 +3142,6 @@
      * call will return true. This access is granted by the owner of the UICC
      * card and does not depend on the registered carrier.
      *
-     * TODO: Add a link to documentation.
-     *
      * @return true if the app has carrier privileges.
      */
     public boolean hasCarrierPrivileges() {
diff --git a/tests/Split/res/layout/main.xml b/tests/Split/res/layout/main.xml
index 607cdb0..5a33c86 100644
--- a/tests/Split/res/layout/main.xml
+++ b/tests/Split/res/layout/main.xml
@@ -21,5 +21,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_centerInParent="true"
+        android:colorAccent="#ffffffff"
+        android:paddingStart="13dp"
         android:src="@drawable/image"/>
 </RelativeLayout>
diff --git a/tests/Split/res/values-v10/values.xml b/tests/Split/res/values-v10/values.xml
new file mode 100644
index 0000000..3d41a84
--- /dev/null
+++ b/tests/Split/res/values-v10/values.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<resources>
+    <style name="toop">
+        <item name="android:paddingStart">12dp</item>
+        <item name="android:layout_width">23dp</item>
+    </style>
+
+    <style name="temp">
+        <item name="android:versionName">hey</item>
+        <item name="android:allowBackup">true</item>
+        <item name="android:colorAccent">#ffffffff</item>
+    </style>
+</resources>
diff --git a/tests/Split/res/values-v17/values.xml b/tests/Split/res/values-v17/values.xml
new file mode 100644
index 0000000..c24eeae
--- /dev/null
+++ b/tests/Split/res/values-v17/values.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<resources>
+    <style name="toop">
+        <item name="android:paddingStart">12dp</item>
+        <item name="android:layout_width">23dp</item>
+        <item name="android:layout_height">45dp</item>
+    </style>
+
+</resources>
diff --git a/tests/Split/res/values/values.xml b/tests/Split/res/values/values.xml
index 68edc77..aabb232 100644
--- a/tests/Split/res/values/values.xml
+++ b/tests/Split/res/values/values.xml
@@ -44,4 +44,10 @@
         <item>@string/boom</item>
         <item>25dp</item>
     </array>
+
+    <style name="temp">
+        <item name="android:versionName">hey</item>
+        <item name="android:allowBackup">true</item>
+        <item name="android:colorAccent">#ffffffff</item>
+    </style>
 </resources>
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index e87138c..e000a1d 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -4329,38 +4329,80 @@
 }
 
 /**
- * Returns true if the given attribute ID comes from
- * a platform version from or after L.
+ * Returns the SDK version at which the attribute was
+ * made public, or -1 if the resource ID is not an attribute
+ * or is not public.
  */
-bool ResourceTable::isAttributeFromL(uint32_t attrId) {
-    const uint32_t baseAttrId = 0x010103f7;
-    if ((attrId & 0xffff0000) != (baseAttrId & 0xffff0000)) {
-        return false;
+int ResourceTable::getPublicAttributeSdkLevel(uint32_t attrId) const {
+    if (Res_GETPACKAGE(attrId) + 1 != 0x01 || Res_GETTYPE(attrId) + 1 != 0x01) {
+        return -1;
     }
 
     uint32_t specFlags;
     if (!mAssets->getIncludedResources().getResourceFlags(attrId, &specFlags)) {
-        return false;
+        return -1;
     }
 
-    return (specFlags & ResTable_typeSpec::SPEC_PUBLIC) != 0 &&
-        (attrId & 0x0000ffff) >= (baseAttrId & 0x0000ffff);
+    if ((specFlags & ResTable_typeSpec::SPEC_PUBLIC) == 0) {
+        return -1;
+    }
+
+    const size_t entryId = Res_GETENTRY(attrId);
+    if (entryId <= 0x021c) {
+        return 1;
+    } else if (entryId <= 0x021d) {
+        return 2;
+    } else if (entryId <= 0x0269) {
+        return SDK_CUPCAKE;
+    } else if (entryId <= 0x028d) {
+        return SDK_DONUT;
+    } else if (entryId <= 0x02ad) {
+        return SDK_ECLAIR;
+    } else if (entryId <= 0x02b3) {
+        return SDK_ECLAIR_0_1;
+    } else if (entryId <= 0x02b5) {
+        return SDK_ECLAIR_MR1;
+    } else if (entryId <= 0x02bd) {
+        return SDK_FROYO;
+    } else if (entryId <= 0x02cb) {
+        return SDK_GINGERBREAD;
+    } else if (entryId <= 0x0361) {
+        return SDK_HONEYCOMB;
+    } else if (entryId <= 0x0366) {
+        return SDK_HONEYCOMB_MR1;
+    } else if (entryId <= 0x03a6) {
+        return SDK_HONEYCOMB_MR2;
+    } else if (entryId <= 0x03ae) {
+        return SDK_JELLY_BEAN;
+    } else if (entryId <= 0x03cc) {
+        return SDK_JELLY_BEAN_MR1;
+    } else if (entryId <= 0x03da) {
+        return SDK_JELLY_BEAN_MR2;
+    } else if (entryId <= 0x03f1) {
+        return SDK_KITKAT;
+    } else if (entryId <= 0x03f6) {
+        return SDK_KITKAT_WATCH;
+    } else if (entryId <= 0x04ce) {
+        return SDK_LOLLIPOP;
+    } else {
+        // Anything else is marked as defined in
+        // SDK_LOLLIPOP_MR1 since after this
+        // version no attribute compat work
+        // needs to be done.
+        return SDK_LOLLIPOP_MR1;
+    }
 }
 
-static bool isMinSdkVersionLOrAbove(const Bundle* bundle) {
-    if (bundle->getMinSdkVersion() != NULL && strlen(bundle->getMinSdkVersion()) > 0) {
-        const char firstChar = bundle->getMinSdkVersion()[0];
-        if (firstChar >= 'L' && firstChar <= 'Z') {
-            // L is the code-name for the v21 release.
-            return true;
-        }
-
-        const int minSdk = atoi(bundle->getMinSdkVersion());
-        if (minSdk >= SDK_LOLLIPOP) {
-            return true;
-        }
+/**
+ * First check the Manifest, then check the command line flag.
+ */
+static int getMinSdkVersion(const Bundle* bundle) {
+    if (bundle->getManifestMinSdkVersion() != NULL && strlen(bundle->getManifestMinSdkVersion()) > 0) {
+        return atoi(bundle->getManifestMinSdkVersion());
+    } else if (bundle->getMinSdkVersion() != NULL && strlen(bundle->getMinSdkVersion()) > 0) {
+        return atoi(bundle->getMinSdkVersion());
     }
-    return false;
+    return 0;
 }
 
 /**
@@ -4406,9 +4448,10 @@
  * attribute will be respected.
  */
 status_t ResourceTable::modifyForCompat(const Bundle* bundle) {
-    if (isMinSdkVersionLOrAbove(bundle)) {
-        // If this app will only ever run on L+ devices,
-        // we don't need to do any compatibility work.
+    const int minSdk = getMinSdkVersion(bundle);
+    if (minSdk >= SDK_LOLLIPOP_MR1) {
+        // Lollipop MR1 and up handles public attributes differently, no
+        // need to do any compat modifications.
         return NO_ERROR;
     }
 
@@ -4447,20 +4490,19 @@
                     }
 
                     const ConfigDescription& config = entries.keyAt(ei);
-                    if (config.sdkVersion >= SDK_LOLLIPOP) {
-                        // We don't need to do anything if the resource is
-                        // already qualified for version 21 or higher.
+                    if (config.sdkVersion >= SDK_LOLLIPOP_MR1) {
                         continue;
                     }
 
-                    Vector<String16> attributesToRemove;
+                    KeyedVector<int, Vector<String16> > attributesToRemove;
                     const KeyedVector<String16, Item>& bag = e->getBag();
                     const size_t bagCount = bag.size();
                     for (size_t bi = 0; bi < bagCount; bi++) {
                         const Item& item = bag.valueAt(bi);
                         const uint32_t attrId = getResId(bag.keyAt(bi), &attr16);
-                        if (isAttributeFromL(attrId)) {
-                            attributesToRemove.add(bag.keyAt(bi));
+                        const int sdkLevel = getPublicAttributeSdkLevel(attrId);
+                        if (sdkLevel > 1 && sdkLevel > config.sdkVersion && sdkLevel > minSdk) {
+                            AaptUtil::appendValue(attributesToRemove, sdkLevel, bag.keyAt(bi));
                         }
                     }
 
@@ -4468,16 +4510,41 @@
                         continue;
                     }
 
-                    // Duplicate the entry under the same configuration
-                    // but with sdkVersion == SDK_LOLLIPOP.
-                    ConfigDescription newConfig(config);
-                    newConfig.sdkVersion = SDK_LOLLIPOP;
-                    entriesToAdd.add(key_value_pair_t<ConfigDescription, sp<Entry> >(
-                            newConfig, new Entry(*e)));
+                    const size_t sdkCount = attributesToRemove.size();
+                    for (size_t i = 0; i < sdkCount; i++) {
+                        const int sdkLevel = attributesToRemove.keyAt(i);
+
+                        // Duplicate the entry under the same configuration
+                        // but with sdkVersion == sdkLevel.
+                        ConfigDescription newConfig(config);
+                        newConfig.sdkVersion = sdkLevel;
+
+                        sp<Entry> newEntry = new Entry(*e);
+
+                        // Remove all items that have a higher SDK level than
+                        // the one we are synthesizing.
+                        for (size_t j = 0; j < sdkCount; j++) {
+                            if (j == i) {
+                                continue;
+                            }
+
+                            if (attributesToRemove.keyAt(j) > sdkLevel) {
+                                const size_t attrCount = attributesToRemove[j].size();
+                                for (size_t k = 0; k < attrCount; k++) {
+                                    newEntry->removeFromBag(attributesToRemove[j][k]);
+                                }
+                            }
+                        }
+
+                        entriesToAdd.add(key_value_pair_t<ConfigDescription, sp<Entry> >(
+                                newConfig, newEntry));
+                    }
 
                     // Remove the attribute from the original.
                     for (size_t i = 0; i < attributesToRemove.size(); i++) {
-                        e->removeFromBag(attributesToRemove[i]);
+                        for (size_t j = 0; j < attributesToRemove[i].size(); j++) {
+                            e->removeFromBag(attributesToRemove[i][j]);
+                        }
                     }
                 }
 
@@ -4494,7 +4561,7 @@
                     if (bundle->getVerbose()) {
                         entriesToAdd[i].value->getPos()
                                 .printf("using v%d attributes; synthesizing resource %s:%s/%s for configuration %s.",
-                                        SDK_LOLLIPOP,
+                                        entriesToAdd[i].key.sdkVersion,
                                         String8(p->getName()).string(),
                                         String8(t->getName()).string(),
                                         String8(entriesToAdd[i].value->getName()).string(),
@@ -4517,17 +4584,23 @@
                                         const String16& resourceName,
                                         const sp<AaptFile>& target,
                                         const sp<XMLNode>& root) {
-    if (isMinSdkVersionLOrAbove(bundle)) {
+    const int minSdk = getMinSdkVersion(bundle);
+    if (minSdk >= SDK_LOLLIPOP_MR1) {
+        // Lollipop MR1 and up handles public attributes differently, no
+        // need to do any compat modifications.
         return NO_ERROR;
     }
 
-    if (target->getResourceType() == "" || target->getGroupEntry().toParams().sdkVersion >= SDK_LOLLIPOP) {
+    const ConfigDescription config(target->getGroupEntry().toParams());
+    if (target->getResourceType() == "" || config.sdkVersion >= SDK_LOLLIPOP_MR1) {
         // Skip resources that have no type (AndroidManifest.xml) or are already version qualified with v21
         // or higher.
         return NO_ERROR;
     }
 
     sp<XMLNode> newRoot = NULL;
+    ConfigDescription newConfig(target->getGroupEntry().toParams());
+    newConfig.sdkVersion = SDK_LOLLIPOP_MR1;
 
     Vector<sp<XMLNode> > nodesToVisit;
     nodesToVisit.push(root);
@@ -4538,11 +4611,19 @@
         const Vector<XMLNode::attribute_entry>& attrs = node->getAttributes();
         for (size_t i = 0; i < attrs.size(); i++) {
             const XMLNode::attribute_entry& attr = attrs[i];
-            if (isAttributeFromL(attr.nameResId)) {
+            const int sdkLevel = getPublicAttributeSdkLevel(attr.nameResId);
+            if (sdkLevel > 1 && sdkLevel > config.sdkVersion && sdkLevel > minSdk) {
                 if (newRoot == NULL) {
                     newRoot = root->clone();
                 }
 
+                // Find the smallest sdk version that we need to synthesize for
+                // and do that one. Subsequent versions will be processed on
+                // the next pass.
+                if (sdkLevel < newConfig.sdkVersion) {
+                    newConfig.sdkVersion = sdkLevel;
+                }
+
                 if (bundle->getVerbose()) {
                     SourcePos(node->getFilename(), node->getStartLineNumber()).printf(
                             "removing attribute %s%s%s from <%s>",
@@ -4568,9 +4649,6 @@
         return NO_ERROR;
     }
 
-    ConfigDescription newConfig(target->getGroupEntry().toParams());
-    newConfig.sdkVersion = SDK_LOLLIPOP;
-
     // Look to see if we already have an overriding v21 configuration.
     sp<ConfigList> cl = getConfigList(String16(mAssets->getPackage()),
             String16(target->getResourceType()), resourceName);
@@ -4587,7 +4665,7 @@
         if (bundle->getVerbose()) {
             SourcePos(target->getSourceFile(), -1).printf(
                     "using v%d attributes; synthesizing resource %s:%s/%s for configuration %s.",
-                    SDK_LOLLIPOP,
+                    newConfig.sdkVersion,
                     mAssets->getPackage().string(),
                     newFile->getResourceType().string(),
                     String8(resourceName).string(),
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index 81590bc..eef0ae1 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -575,7 +575,7 @@
     const Item* getItem(uint32_t resID, uint32_t attrID) const;
     bool getItemValue(uint32_t resID, uint32_t attrID,
                       Res_value* outValue);
-    bool isAttributeFromL(uint32_t attrId);
+    int getPublicAttributeSdkLevel(uint32_t attrId) const;
 
 
     String16 mAssetsPackage;
diff --git a/tools/aapt/SdkConstants.h b/tools/aapt/SdkConstants.h
index 7fd1030..4e0fe10 100644
--- a/tools/aapt/SdkConstants.h
+++ b/tools/aapt/SdkConstants.h
@@ -37,6 +37,7 @@
     SDK_KITKAT = 19,
     SDK_KITKAT_WATCH = 20,
     SDK_LOLLIPOP = 21,
+    SDK_LOLLIPOP_MR1 = 22,
 };
 
 #endif // H_AAPT_SDK_CONSTANTS