Merge "Make Settings search API as a SystemAPI"
diff --git a/Android.mk b/Android.mk
index 2462d73..70e965b 100644
--- a/Android.mk
+++ b/Android.mk
@@ -245,6 +245,7 @@
 	core/java/android/view/accessibility/IAccessibilityManagerClient.aidl \
 	core/java/android/view/IApplicationToken.aidl \
 	core/java/android/view/IAssetAtlas.aidl \
+	core/java/android/view/IGraphicsStats.aidl \
 	core/java/android/view/IInputFilter.aidl \
 	core/java/android/view/IInputFilterHost.aidl \
 	core/java/android/view/IOnKeyguardExitResult.aidl \
diff --git a/api/current.txt b/api/current.txt
index ae828ce..11a5083 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -657,7 +657,7 @@
     field public static final int headerDividersEnabled = 16843310; // 0x101022e
     field public static final deprecated int headerMonthTextAppearance = 16843926; // 0x1010496
     field public static final deprecated int headerTimeTextAppearance = 16843935; // 0x101049f
-    field public static final int headerYearTextAppearance = 16843928; // 0x1010498
+    field public static final deprecated int headerYearTextAppearance = 16843928; // 0x1010498
     field public static final int height = 16843093; // 0x1010155
     field public static final int hideOnContentScroll = 16843843; // 0x1010443
     field public static final int hint = 16843088; // 0x1010150
@@ -1456,7 +1456,7 @@
     field public static final int x = 16842924; // 0x10100ac
     field public static final int xlargeScreens = 16843455; // 0x10102bf
     field public static final int y = 16842925; // 0x10100ad
-    field public static final int yearListItemTextAppearance = 16843929; // 0x1010499
+    field public static final deprecated int yearListItemTextAppearance = 16843929; // 0x1010499
     field public static final deprecated int yearListSelectorColor = 16843930; // 0x101049a
     field public static final int yesNoPreferenceStyle = 16842896; // 0x1010090
     field public static final int zAdjustment = 16843201; // 0x10101c1
@@ -38139,7 +38139,7 @@
     field protected android.database.Cursor mDataCursor;
   }
 
-  public class AnalogClock extends android.view.View {
+  public deprecated class AnalogClock extends android.view.View {
     ctor public AnalogClock(android.content.Context);
     ctor public AnalogClock(android.content.Context, android.util.AttributeSet);
     ctor public AnalogClock(android.content.Context, android.util.AttributeSet, int);
@@ -40200,12 +40200,16 @@
     ctor public TimePicker(android.content.Context, android.util.AttributeSet);
     ctor public TimePicker(android.content.Context, android.util.AttributeSet, int);
     ctor public TimePicker(android.content.Context, android.util.AttributeSet, int, int);
-    method public java.lang.Integer getCurrentHour();
-    method public java.lang.Integer getCurrentMinute();
+    method public deprecated java.lang.Integer getCurrentHour();
+    method public deprecated java.lang.Integer getCurrentMinute();
+    method public int getHour();
+    method public int getMinute();
     method public boolean is24HourView();
-    method public void setCurrentHour(java.lang.Integer);
-    method public void setCurrentMinute(java.lang.Integer);
+    method public deprecated void setCurrentHour(java.lang.Integer);
+    method public deprecated void setCurrentMinute(java.lang.Integer);
+    method public void setHour(int);
     method public void setIs24HourView(java.lang.Boolean);
+    method public void setMinute(int);
     method public void setOnTimeChangedListener(android.widget.TimePicker.OnTimeChangedListener);
   }
 
diff --git a/api/system-current.txt b/api/system-current.txt
index f913e8d..9513ab6 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -728,7 +728,7 @@
     field public static final int headerDividersEnabled = 16843310; // 0x101022e
     field public static final deprecated int headerMonthTextAppearance = 16843926; // 0x1010496
     field public static final deprecated int headerTimeTextAppearance = 16843935; // 0x101049f
-    field public static final int headerYearTextAppearance = 16843928; // 0x1010498
+    field public static final deprecated int headerYearTextAppearance = 16843928; // 0x1010498
     field public static final int height = 16843093; // 0x1010155
     field public static final int hideOnContentScroll = 16843843; // 0x1010443
     field public static final int hint = 16843088; // 0x1010150
@@ -1531,7 +1531,7 @@
     field public static final int x = 16842924; // 0x10100ac
     field public static final int xlargeScreens = 16843455; // 0x10102bf
     field public static final int y = 16842925; // 0x10100ad
-    field public static final int yearListItemTextAppearance = 16843929; // 0x1010499
+    field public static final deprecated int yearListItemTextAppearance = 16843929; // 0x1010499
     field public static final deprecated int yearListSelectorColor = 16843930; // 0x101049a
     field public static final int yesNoPreferenceStyle = 16842896; // 0x1010090
     field public static final int zAdjustment = 16843201; // 0x10101c1
@@ -40997,7 +40997,7 @@
     field protected android.database.Cursor mDataCursor;
   }
 
-  public class AnalogClock extends android.view.View {
+  public deprecated class AnalogClock extends android.view.View {
     ctor public AnalogClock(android.content.Context);
     ctor public AnalogClock(android.content.Context, android.util.AttributeSet);
     ctor public AnalogClock(android.content.Context, android.util.AttributeSet, int);
@@ -43058,12 +43058,16 @@
     ctor public TimePicker(android.content.Context, android.util.AttributeSet);
     ctor public TimePicker(android.content.Context, android.util.AttributeSet, int);
     ctor public TimePicker(android.content.Context, android.util.AttributeSet, int, int);
-    method public java.lang.Integer getCurrentHour();
-    method public java.lang.Integer getCurrentMinute();
+    method public deprecated java.lang.Integer getCurrentHour();
+    method public deprecated java.lang.Integer getCurrentMinute();
+    method public int getHour();
+    method public int getMinute();
     method public boolean is24HourView();
-    method public void setCurrentHour(java.lang.Integer);
-    method public void setCurrentMinute(java.lang.Integer);
+    method public deprecated void setCurrentHour(java.lang.Integer);
+    method public deprecated void setCurrentMinute(java.lang.Integer);
+    method public void setHour(int);
     method public void setIs24HourView(java.lang.Boolean);
+    method public void setMinute(int);
     method public void setOnTimeChangedListener(android.widget.TimePicker.OnTimeChangedListener);
   }
 
diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl
index 14b5748..579cdbe 100644
--- a/core/java/android/security/IKeystoreService.aidl
+++ b/core/java/android/security/IKeystoreService.aidl
@@ -73,4 +73,6 @@
     OperationResult update(IBinder token, in KeymasterArguments params, in byte[] input);
     OperationResult finish(IBinder token, in KeymasterArguments params, in byte[] signature);
     int abort(IBinder handle);
+    boolean isOperationAuthorized(IBinder token);
+    int addAuthToken(in byte[] authToken);
 }
diff --git a/core/java/android/view/IGraphicsStats.aidl b/core/java/android/view/IGraphicsStats.aidl
new file mode 100644
index 0000000..c235eb2
--- /dev/null
+++ b/core/java/android/view/IGraphicsStats.aidl
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.os.ParcelFileDescriptor;
+
+/**
+ * @hide
+ */
+interface IGraphicsStats {
+    ParcelFileDescriptor requestBufferForProcess(String packageName, IBinder token);
+}
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 031be07..87d5d9a 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -23,7 +23,9 @@
 import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.Binder;
 import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.Trace;
@@ -124,7 +126,7 @@
         mRootNode.setClipToBounds(false);
         mNativeProxy = nCreateProxy(translucent, rootNodePtr);
 
-        AtlasInitializer.sInstance.init(context, mNativeProxy);
+        ProcessInitializer.sInstance.init(context, mNativeProxy);
 
         loadSystemProperties();
     }
@@ -410,15 +412,44 @@
         nTrimMemory(level);
     }
 
-    private static class AtlasInitializer {
-        static AtlasInitializer sInstance = new AtlasInitializer();
+    public static void dumpProfileData(byte[] data, FileDescriptor fd) {
+        nDumpProfileData(data, fd);
+    }
+
+    private static class ProcessInitializer {
+        static ProcessInitializer sInstance = new ProcessInitializer();
+        static IGraphicsStats sGraphicsStatsService;
+        private static IBinder sProcToken;
 
         private boolean mInitialized = false;
 
-        private AtlasInitializer() {}
+        private ProcessInitializer() {}
 
         synchronized void init(Context context, long renderProxy) {
             if (mInitialized) return;
+            mInitialized = true;
+            initGraphicsStats(context, renderProxy);
+            initAssetAtlas(context, renderProxy);
+        }
+
+        private static void initGraphicsStats(Context context, long renderProxy) {
+            IBinder binder = ServiceManager.getService("graphicsstats");
+            if (binder == null) return;
+
+            sGraphicsStatsService = IGraphicsStats.Stub.asInterface(binder);
+            sProcToken = new Binder();
+            try {
+                final String pkg = context.getApplicationInfo().packageName;
+                ParcelFileDescriptor pfd = sGraphicsStatsService.
+                        requestBufferForProcess(pkg, sProcToken);
+                nSetProcessStatsBuffer(renderProxy, pfd.getFd());
+                pfd.close();
+            } catch (Exception e) {
+                Log.w(LOG_TAG, "Could not acquire gfx stats buffer", e);
+            }
+        }
+
+        private static void initAssetAtlas(Context context, long renderProxy) {
             IBinder binder = ServiceManager.getService("assetatlas");
             if (binder == null) return;
 
@@ -432,7 +463,6 @@
                             // TODO Remove after fixing b/15425820
                             validateMap(context, map);
                             nSetAtlas(renderProxy, buffer, map);
-                            mInitialized = true;
                         }
                         // If IAssetAtlas is not the same class as the IBinder
                         // we are using a remote service and we can safely
@@ -477,6 +507,7 @@
     static native void setupShadersDiskCache(String cacheFile);
 
     private static native void nSetAtlas(long nativeProxy, GraphicBuffer buffer, long[] map);
+    private static native void nSetProcessStatsBuffer(long nativeProxy, int fd);
 
     private static native long nCreateRootRenderNode();
     private static native long nCreateProxy(boolean translucent, long rootRenderNode);
@@ -514,4 +545,5 @@
 
     private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd,
             @DumpFlags int dumpFlags);
+    private static native void nDumpProfileData(byte[] data, FileDescriptor fd);
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index cfcc6fe..a69384a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -9959,6 +9959,8 @@
      * @param oldt Previous vertical scroll origin.
      */
     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+        notifySubtreeAccessibilityStateChangedIfNeeded();
+
         if (AccessibilityManager.getInstance(mContext).isEnabled()) {
             postSendViewScrolledAccessibilityEventCallback();
         }
@@ -11162,6 +11164,7 @@
             invalidateViewProperty(false, true);
 
             invalidateParentIfNeededAndWasQuickRejected();
+            notifySubtreeAccessibilityStateChangedIfNeeded();
         }
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 294174a..4158340 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6268,41 +6268,79 @@
 
 
             case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
-                if (mAccessibilityFocusedHost != null && mAccessibilityFocusedVirtualView != null) {
-                    // We care only for changes rooted in the focused host.
-                    final long eventSourceId = event.getSourceNodeId();
-                    final int hostViewId = AccessibilityNodeInfo.getAccessibilityViewId(
-                            eventSourceId);
-                    if (hostViewId != mAccessibilityFocusedHost.getAccessibilityViewId()) {
-                        break;
-                    }
-
-                    // We only care about changes that may change the virtual focused view bounds.
-                    final int changes = event.getContentChangeTypes();
-                    if ((changes & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0
-                            || changes == AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED) {
-                        AccessibilityNodeProvider provider = mAccessibilityFocusedHost
-                                .getAccessibilityNodeProvider();
-                        if (provider != null) {
-                            final int virtualChildId = AccessibilityNodeInfo.getVirtualDescendantId(
-                                    mAccessibilityFocusedVirtualView.getSourceNodeId());
-                            if (virtualChildId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
-                                mAccessibilityFocusedVirtualView = provider
-                                        .createAccessibilityNodeInfo(
-                                                AccessibilityNodeProvider.HOST_VIEW_ID);
-                            } else {
-                                mAccessibilityFocusedVirtualView = provider
-                                        .createAccessibilityNodeInfo(virtualChildId);
-                            }
-                        }
-                    }
-                }
+                handleWindowContentChangedEvent(event);
             } break;
         }
         mAccessibilityManager.sendAccessibilityEvent(event);
         return true;
     }
 
+    /**
+     * Updates the focused virtual view, when necessary, in response to a
+     * content changed event.
+     * <p>
+     * This is necessary to get updated bounds after a position change.
+     *
+     * @param event an accessibility event of type
+     *              {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED}
+     */
+    private void handleWindowContentChangedEvent(AccessibilityEvent event) {
+        // No virtual view focused, nothing to do here.
+        if (mAccessibilityFocusedHost == null || mAccessibilityFocusedVirtualView == null) {
+            return;
+        }
+
+        // If we have a node but no provider, abort.
+        final AccessibilityNodeProvider provider =
+                mAccessibilityFocusedHost.getAccessibilityNodeProvider();
+        if (provider == null) {
+            // TODO: Should we clear the focused virtual view?
+            return;
+        }
+
+        // We only care about change types that may affect the bounds of the
+        // focused virtual view.
+        final int changes = event.getContentChangeTypes();
+        if ((changes & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) == 0
+                && changes != AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED) {
+            return;
+        }
+
+        final long eventSourceNodeId = event.getSourceNodeId();
+        final int changedViewId = AccessibilityNodeInfo.getAccessibilityViewId(eventSourceNodeId);
+
+        // Search up the tree for subtree containment.
+        boolean hostInSubtree = false;
+        View root = mAccessibilityFocusedHost;
+        while (root != null && !hostInSubtree) {
+            if (changedViewId == root.getAccessibilityViewId()) {
+                hostInSubtree = true;
+            } else {
+                final ViewParent parent = root.getParent();
+                if (parent instanceof View) {
+                    root = (View) parent;
+                } else {
+                    root = null;
+                }
+            }
+        }
+
+        // We care only about changes in subtrees containing the host view.
+        if (!hostInSubtree) {
+            return;
+        }
+
+        final long focusedSourceNodeId = mAccessibilityFocusedVirtualView.getSourceNodeId();
+        int focusedChildId = AccessibilityNodeInfo.getVirtualDescendantId(focusedSourceNodeId);
+        if (focusedChildId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
+            // TODO: Should we clear the focused virtual view?
+            focusedChildId = AccessibilityNodeProvider.HOST_VIEW_ID;
+        }
+
+        // Refresh the node for the focused virtual view.
+        mAccessibilityFocusedVirtualView = provider.createAccessibilityNodeInfo(focusedChildId);
+    }
+
     @Override
     public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) {
         postSendWindowContentChangedCallback(source, changeType);
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index 1716dbd..45eee34 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -40,8 +40,10 @@
  * @attr ref android.R.styleable#AnalogClock_dial
  * @attr ref android.R.styleable#AnalogClock_hand_hour
  * @attr ref android.R.styleable#AnalogClock_hand_minute
+ * @deprecated This widget is no longer supported.
  */
 @RemoteView
+@Deprecated
 public class AnalogClock extends View {
     private Time mCalendar;
 
diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java
index 7b8a979..a157087 100755
--- a/core/java/android/widget/DatePickerCalendarDelegate.java
+++ b/core/java/android/widget/DatePickerCalendarDelegate.java
@@ -49,7 +49,6 @@
  * A delegate for picking up a date (day / month / year).
  */
 class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
-
     private static final int USE_LOCALE = 0;
 
     private static final int UNINITIALIZED = -1;
@@ -61,9 +60,9 @@
 
     private static final int ANIMATION_DURATION = 300;
 
-    public static final int[] ATTRS_TEXT_COLOR = new int[]{com.android.internal.R.attr.textColor};
-
-    public static final int[] ATTRS_DISABLED_ALPHA = new int[]{
+    private static final int[] ATTRS_TEXT_COLOR = new int[] {
+            com.android.internal.R.attr.textColor};
+    private static final int[] ATTRS_DISABLED_ALPHA = new int[] {
             com.android.internal.R.attr.disabledAlpha};
 
     private SimpleDateFormat mYearFormat;
@@ -157,6 +156,8 @@
             header.setBackground(a.getDrawable(R.styleable.DatePicker_headerBackground));
         }
 
+        a.recycle();
+
         // Set up picker container.
         mAnimator = (ViewAnimator) mContainer.findViewById(R.id.animator);
 
@@ -174,32 +175,10 @@
         mYearPickerView.setDate(mCurrentDate.getTimeInMillis());
         mYearPickerView.setOnYearSelectedListener(mOnYearSelectedListener);
 
-        final int yearTextAppearanceResId = a.getResourceId(
-                R.styleable.DatePicker_yearListItemTextAppearance, 0);
-        if (yearTextAppearanceResId != 0) {
-            mYearPickerView.setYearTextAppearance(yearTextAppearanceResId);
-        }
-
-        final int yearActivatedTextAppearanceResId = a.getResourceId(
-                R.styleable.DatePicker_yearListItemActivatedTextAppearance, 0);
-        if (yearActivatedTextAppearanceResId != 0) {
-            mYearPickerView.setYearActivatedTextAppearance(yearActivatedTextAppearanceResId);
-        }
-
-        a.recycle();
-
         // Set up content descriptions.
         mSelectDay = res.getString(R.string.select_day);
         mSelectYear = res.getString(R.string.select_year);
 
-        final Animation inAnim = new AlphaAnimation(0, 1);
-        inAnim.setDuration(ANIMATION_DURATION);
-        mAnimator.setInAnimation(inAnim);
-
-        final Animation outAnim = new AlphaAnimation(1, 0);
-        outAnim.setDuration(ANIMATION_DURATION);
-        mAnimator.setOutAnimation(outAnim);
-
         // Initialize for current locale. This also initializes the date, so no
         // need to call onDateChanged.
         onLocaleChanged(mCurrentLocale);
diff --git a/core/java/android/widget/DayPickerAdapter.java b/core/java/android/widget/DayPickerAdapter.java
index 4f9f09e..9a4b6f5 100644
--- a/core/java/android/widget/DayPickerAdapter.java
+++ b/core/java/android/widget/DayPickerAdapter.java
@@ -18,10 +18,15 @@
 
 import com.android.internal.widget.PagerAdapter;
 
+import android.annotation.IdRes;
+import android.annotation.LayoutRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.util.SparseArray;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.SimpleMonthView.OnDayClickListener;
@@ -37,9 +42,13 @@
     private final Calendar mMinDate = Calendar.getInstance();
     private final Calendar mMaxDate = Calendar.getInstance();
 
-    private final SparseArray<SimpleMonthView> mItems = new SparseArray<>();
+    private final SparseArray<ViewHolder> mItems = new SparseArray<>();
 
-    private Calendar mSelectedDay = Calendar.getInstance();
+    private final LayoutInflater mInflater;
+    private final int mLayoutResId;
+    private final int mCalendarViewId;
+
+    private Calendar mSelectedDay = null;
 
     private int mMonthTextAppearance;
     private int mDayOfWeekTextAppearance;
@@ -51,19 +60,29 @@
 
     private OnDaySelectedListener mOnDaySelectedListener;
 
+    private int mCount;
     private int mFirstDayOfWeek;
 
-    public DayPickerAdapter(Context context) {
+    public DayPickerAdapter(@NonNull Context context, @LayoutRes int layoutResId,
+            @IdRes int calendarViewId) {
+        mInflater = LayoutInflater.from(context);
+        mLayoutResId = layoutResId;
+        mCalendarViewId = calendarViewId;
+
         final TypedArray ta = context.obtainStyledAttributes(new int[] {
                 com.android.internal.R.attr.colorControlHighlight});
         mDayHighlightColor = ta.getColorStateList(0);
         ta.recycle();
     }
 
-    public void setRange(Calendar min, Calendar max) {
+    public void setRange(@NonNull Calendar min, @NonNull Calendar max) {
         mMinDate.setTimeInMillis(min.getTimeInMillis());
         mMaxDate.setTimeInMillis(max.getTimeInMillis());
 
+        final int diffYear = mMaxDate.get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR);
+        final int diffMonth = mMaxDate.get(Calendar.MONTH) - mMinDate.get(Calendar.MONTH);
+        mCount = diffMonth + MONTHS_IN_YEAR * diffYear + 1;
+
         // Positions are now invalid, clear everything and start over.
         notifyDataSetChanged();
     }
@@ -80,7 +99,7 @@
         // Update displayed views.
         final int count = mItems.size();
         for (int i = 0; i < count; i++) {
-            final SimpleMonthView monthView = mItems.valueAt(i);
+            final SimpleMonthView monthView = mItems.valueAt(i).calendar;
             monthView.setFirstDayOfWeek(weekStart);
         }
     }
@@ -94,23 +113,25 @@
      *
      * @param day the selected day
      */
-    public void setSelectedDay(Calendar day) {
+    public void setSelectedDay(@Nullable Calendar day) {
         final int oldPosition = getPositionForDay(mSelectedDay);
         final int newPosition = getPositionForDay(day);
 
         // Clear the old position if necessary.
-        if (oldPosition != newPosition) {
-            final SimpleMonthView oldMonthView = mItems.get(oldPosition, null);
+        if (oldPosition != newPosition && oldPosition >= 0) {
+            final ViewHolder oldMonthView = mItems.get(oldPosition, null);
             if (oldMonthView != null) {
-                oldMonthView.setSelectedDay(-1);
+                oldMonthView.calendar.setSelectedDay(-1);
             }
         }
 
         // Set the new position.
-        final SimpleMonthView newMonthView = mItems.get(newPosition, null);
-        if (newMonthView != null) {
-            final int dayOfMonth = day.get(Calendar.DAY_OF_MONTH);
-            newMonthView.setSelectedDay(dayOfMonth);
+        if (newPosition >= 0) {
+            final ViewHolder newMonthView = mItems.get(newPosition, null);
+            if (newMonthView != null) {
+                final int dayOfMonth = day.get(Calendar.DAY_OF_MONTH);
+                newMonthView.calendar.setSelectedDay(dayOfMonth);
+            }
         }
 
         mSelectedDay = day;
@@ -155,14 +176,13 @@
 
     @Override
     public int getCount() {
-        final int diffYear = mMaxDate.get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR);
-        final int diffMonth = mMaxDate.get(Calendar.MONTH) - mMinDate.get(Calendar.MONTH);
-        return diffMonth + MONTHS_IN_YEAR * diffYear + 1;
+        return mCount;
     }
 
     @Override
     public boolean isViewFromObject(View view, Object object) {
-        return view == object;
+        final ViewHolder holder = (ViewHolder) object;
+        return view == holder.container;
     }
 
     private int getMonthForPosition(int position) {
@@ -173,7 +193,11 @@
         return position / MONTHS_IN_YEAR + mMinDate.get(Calendar.YEAR);
     }
 
-    private int getPositionForDay(Calendar day) {
+    private int getPositionForDay(@Nullable Calendar day) {
+        if (day == null) {
+            return -1;
+        }
+
         final int yearOffset = (day.get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR));
         final int monthOffset = (day.get(Calendar.MONTH) - mMinDate.get(Calendar.MONTH));
         return yearOffset * MONTHS_IN_YEAR + monthOffset;
@@ -181,7 +205,9 @@
 
     @Override
     public Object instantiateItem(ViewGroup container, int position) {
-        final SimpleMonthView v = new SimpleMonthView(container.getContext());
+        final View itemView = mInflater.inflate(mLayoutResId, container, false);
+
+        final SimpleMonthView v = (SimpleMonthView) itemView.findViewById(mCalendarViewId);
         v.setOnDayClickListener(mOnDayClickListener);
         v.setMonthTextAppearance(mMonthTextAppearance);
         v.setDayOfWeekTextAppearance(mDayOfWeekTextAppearance);
@@ -205,7 +231,7 @@
         final int year = getYearForPosition(position);
 
         final int selectedDay;
-        if (mSelectedDay.get(Calendar.MONTH) == month) {
+        if (mSelectedDay != null && mSelectedDay.get(Calendar.MONTH) == month) {
             selectedDay = mSelectedDay.get(Calendar.DAY_OF_MONTH);
         } else {
             selectedDay = -1;
@@ -227,33 +253,34 @@
 
         v.setMonthParams(selectedDay, month, year, mFirstDayOfWeek,
                 enabledDayRangeStart, enabledDayRangeEnd);
+        v.setPrevEnabled(position > 0);
+        v.setNextEnabled(position < mCount - 1);
 
-        mItems.put(position, v);
+        final ViewHolder holder = new ViewHolder(position, itemView, v);
+        mItems.put(position, holder);
 
-        container.addView(v);
+        container.addView(itemView);
 
-        return v;
+        return holder;
     }
 
     @Override
     public void destroyItem(ViewGroup container, int position, Object object) {
-        container.removeView(mItems.get(position));
+        final ViewHolder holder = (ViewHolder) object;
+        container.removeView(holder.container);
 
         mItems.remove(position);
     }
 
     @Override
     public int getItemPosition(Object object) {
-        final int index = mItems.indexOfValue((SimpleMonthView) object);
-        if (index < 0) {
-            return mItems.keyAt(index);
-        }
-        return -1;
+        final ViewHolder holder = (ViewHolder) object;
+        return holder.position;
     }
 
     @Override
     public CharSequence getPageTitle(int position) {
-        final SimpleMonthView v = mItems.get(position);
+        final SimpleMonthView v = mItems.get(position).calendar;
         if (v != null) {
             return v.getTitle();
         }
@@ -275,9 +302,29 @@
                 }
             }
         }
+
+        @Override
+        public void onNavigationClick(SimpleMonthView view, int direction, boolean animate) {
+            if (mOnDaySelectedListener != null) {
+                mOnDaySelectedListener.onNavigationClick(DayPickerAdapter.this, direction, animate);
+            }
+        }
     };
 
+    private static class ViewHolder {
+        public final int position;
+        public final View container;
+        public final SimpleMonthView calendar;
+
+        public ViewHolder(int position, View container, SimpleMonthView calendar) {
+            this.position = position;
+            this.container = container;
+            this.calendar = calendar;
+        }
+    }
+
     public interface OnDaySelectedListener {
         public void onDaySelected(DayPickerAdapter view, Calendar day);
+        public void onNavigationClick(DayPickerAdapter view, int direction, boolean animate);
     }
 }
diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java
index a7ae926..e2f8efc 100644
--- a/core/java/android/widget/DayPickerView.java
+++ b/core/java/android/widget/DayPickerView.java
@@ -88,7 +88,8 @@
         a.recycle();
 
         // Set up adapter.
-        mAdapter = new DayPickerAdapter(context);
+        mAdapter = new DayPickerAdapter(context,
+                R.layout.date_picker_month_item_material, R.id.month_view);
         mAdapter.setMonthTextAppearance(monthTextAppearanceResId);
         mAdapter.setDayOfWeekTextAppearance(dayOfWeekTextAppearanceResId);
         mAdapter.setDayTextAppearance(dayTextAppearanceResId);
@@ -128,6 +129,14 @@
                     mOnDaySelectedListener.onDaySelected(DayPickerView.this, day);
                 }
             }
+
+            @Override
+            public void onNavigationClick(DayPickerAdapter view, int direction, boolean animate) {
+                // ViewPager clamps input values, so we don't need to worry
+                // about passing invalid indices.
+                final int nextItem = getCurrentItem() + direction;
+                setCurrentItem(nextItem, animate);
+            }
         });
     }
 
diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java
index 4e5a39a..3fb096c6 100644
--- a/core/java/android/widget/SimpleMonthView.java
+++ b/core/java/android/widget/SimpleMonthView.java
@@ -26,6 +26,7 @@
 import android.graphics.Paint.Style;
 import android.graphics.Rect;
 import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.text.TextPaint;
 import android.text.format.DateFormat;
@@ -59,6 +60,12 @@
     private static final String DEFAULT_TITLE_FORMAT = "MMMMy";
     private static final String DAY_OF_WEEK_FORMAT = "EEEEE";
 
+    /** Virtual view ID for previous button. */
+    private static final int ITEM_ID_PREV = 0x101;
+
+    /** Virtual view ID for next button. */
+    private static final int ITEM_ID_NEXT = 0x100;
+
     private final TextPaint mMonthPaint = new TextPaint();
     private final TextPaint mDayOfWeekPaint = new TextPaint();
     private final TextPaint mDayPaint = new TextPaint();
@@ -66,13 +73,27 @@
     private final Paint mDayHighlightPaint = new Paint();
 
     private final Calendar mCalendar = Calendar.getInstance();
-    private final Calendar mDayLabelCalendar = Calendar.getInstance();
+    private final Calendar mDayOfWeekLabelCalendar = Calendar.getInstance();
 
     private final MonthViewTouchHelper mTouchHelper;
 
     private final SimpleDateFormat mTitleFormatter;
     private final SimpleDateFormat mDayOfWeekFormatter;
 
+    private final int mMonthHeight;
+    private final int mDayOfWeekHeight;
+    private final int mDayHeight;
+    private final int mCellWidth;
+    private final int mDaySelectorRadius;
+
+    // Next/previous drawables.
+    private final Drawable mPrevDrawable;
+    private final Drawable mNextDrawable;
+    private final Rect mPrevHitArea;
+    private final Rect mNextHitArea;
+    private final CharSequence mPrevContentDesc;
+    private final CharSequence mNextContentDesc;
+
     private CharSequence mTitle;
 
     private int mMonth;
@@ -81,12 +102,6 @@
     private int mPaddedWidth;
     private int mPaddedHeight;
 
-    private final int mMonthHeight;
-    private final int mDayOfWeekHeight;
-    private final int mDayHeight;
-    private final int mCellWidth;
-    private final int mDaySelectorRadius;
-
     /** The day of month for the selected day, or -1 if no day is selected. */
     private int mActivatedDay = -1;
 
@@ -122,7 +137,10 @@
 
     private ColorStateList mDayTextColor;
 
-    private int mTouchedDay = -1;
+    private int mTouchedItem = -1;
+
+    private boolean mPrevEnabled;
+    private boolean mNextEnabled;
 
     public SimpleMonthView(Context context) {
         this(context, null);
@@ -146,6 +164,13 @@
         mCellWidth = res.getDimensionPixelSize(R.dimen.date_picker_day_width);
         mDaySelectorRadius = res.getDimensionPixelSize(R.dimen.date_picker_day_selector_radius);
 
+        mPrevDrawable = context.getDrawable(R.drawable.ic_chevron_left);
+        mNextDrawable = context.getDrawable(R.drawable.ic_chevron_right);
+        mPrevHitArea = mPrevDrawable != null ? new Rect() : null;
+        mNextHitArea = mNextDrawable != null ? new Rect() : null;
+        mPrevContentDesc = res.getText(R.string.date_picker_prev_month_button);
+        mNextContentDesc = res.getText(R.string.date_picker_next_month_button);
+
         // Set up accessibility components.
         mTouchHelper = new MonthViewTouchHelper(this);
         setAccessibilityDelegate(mTouchHelper);
@@ -160,6 +185,18 @@
         initPaints(res);
     }
 
+    public void setNextEnabled(boolean enabled) {
+        mNextEnabled = enabled;
+        mTouchHelper.invalidateRoot();
+        invalidate();
+    }
+
+    public void setPrevEnabled(boolean enabled) {
+        mPrevEnabled = enabled;
+        mTouchHelper.invalidateRoot();
+        invalidate();
+    }
+
     /**
      * Applies the specified text appearance resource to a paint, returning the
      * text color if one is set in the text appearance.
@@ -192,7 +229,16 @@
     }
 
     public void setMonthTextAppearance(int resId) {
-        applyTextAppearance(mMonthPaint, resId);
+        final ColorStateList monthColor = applyTextAppearance(mMonthPaint, resId);
+        if (monthColor != null) {
+            if (mPrevDrawable != null) {
+                mPrevDrawable.setTintList(monthColor);
+            }
+            if (mNextDrawable != null) {
+                mNextDrawable.setTintList(monthColor);
+            }
+        }
+
         invalidate();
     }
 
@@ -300,23 +346,26 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
+        final int x = (int) (event.getX() + 0.5f);
+        final int y = (int) (event.getY() + 0.5f);
+
         switch (event.getAction()) {
             case MotionEvent.ACTION_DOWN:
             case MotionEvent.ACTION_MOVE:
-                final int touchedDay = getDayAtLocation(event.getX(), event.getY());
-                if (mTouchedDay != touchedDay) {
-                    mTouchedDay = touchedDay;
+                final int touchedItem = getItemAtLocation(x, y);
+                if (mTouchedItem != touchedItem) {
+                    mTouchedItem = touchedItem;
                     invalidate();
                 }
                 break;
 
             case MotionEvent.ACTION_UP:
-                final int clickedDay = getDayAtLocation(event.getX(), event.getY());
-                onDayClicked(clickedDay);
+                final int clickedItem = getItemAtLocation(x, y);
+                onItemClicked(clickedItem, true);
                 // Fall through.
             case MotionEvent.ACTION_CANCEL:
                 // Reset touched day on stream end.
-                mTouchedDay = -1;
+                mTouchedItem = -1;
                 invalidate();
                 break;
         }
@@ -332,6 +381,7 @@
         drawMonth(canvas);
         drawDaysOfWeek(canvas);
         drawDays(canvas);
+        drawButtons(canvas);
 
         canvas.translate(-paddingLeft, -paddingTop);
     }
@@ -347,34 +397,43 @@
     }
 
     private void drawDaysOfWeek(Canvas canvas) {
-        final float cellWidthHalf = mPaddedWidth / (DAYS_IN_WEEK * 2);
+        final TextPaint p = mDayOfWeekPaint;
+        final int headerHeight = mMonthHeight;
+        final int rowHeight = mDayOfWeekHeight;
+        final int colWidth = mPaddedWidth / DAYS_IN_WEEK;
 
-        // Vertically centered within the cell height.
-        final float lineHeight = mDayOfWeekPaint.ascent() + mDayOfWeekPaint.descent();
-        final float y = mMonthHeight + (mDayOfWeekHeight - lineHeight) / 2f;
+        // Text is vertically centered within the day of week height.
+        final float halfLineHeight = (p.ascent() + p.descent()) / 2f;
+        final int rowCenter = headerHeight + rowHeight / 2;
 
-        for (int i = 0; i < DAYS_IN_WEEK; i++) {
-            final int calendarDay = (i + mWeekStart) % DAYS_IN_WEEK;
-            mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay);
-
-            final String dayLabel = mDayOfWeekFormatter.format(mDayLabelCalendar.getTime());
-            final float x = (2 * i + 1) * cellWidthHalf;
-            canvas.drawText(dayLabel, x, y, mDayOfWeekPaint);
+        for (int col = 0; col < DAYS_IN_WEEK; col++) {
+            final int colCenter = colWidth * col + colWidth / 2;
+            final int dayOfWeek = (col + mWeekStart) % DAYS_IN_WEEK;
+            final String label = getDayOfWeekLabel(dayOfWeek);
+            canvas.drawText(label, colCenter, rowCenter - halfLineHeight, p);
         }
     }
 
+    private String getDayOfWeekLabel(int dayOfWeek) {
+        mDayOfWeekLabelCalendar.set(Calendar.DAY_OF_WEEK, dayOfWeek);
+        return mDayOfWeekFormatter.format(mDayOfWeekLabelCalendar.getTime());
+    }
+
     /**
      * Draws the month days.
      */
     private void drawDays(Canvas canvas) {
-        final int cellWidthHalf = mPaddedWidth / (DAYS_IN_WEEK * 2);
+        final TextPaint p = mDayPaint;
+        final int headerHeight = mMonthHeight + mDayOfWeekHeight;
+        final int rowHeight = mDayHeight;
+        final int colWidth = mPaddedWidth / DAYS_IN_WEEK;
 
-        // Vertically centered within the cell height.
-        final float halfLineHeight = (mDayPaint.ascent() + mDayPaint.descent()) / 2;
-        float centerY = mMonthHeight + mDayOfWeekHeight + mDayHeight / 2f;
+        // Text is vertically centered within the row height.
+        final float halfLineHeight = (p.ascent() + p.descent()) / 2f;
+        int rowCenter = headerHeight + rowHeight / 2;
 
-        for (int day = 1, j = findDayOffset(); day <= mDaysInMonth; day++) {
-            final int x = (2 * j + 1) * cellWidthHalf;
+        for (int day = 1, col = findDayOffset(); day <= mDaysInMonth; day++) {
+            final int colCenter = colWidth * col + colWidth / 2;
             int stateMask = 0;
 
             if (day >= mEnabledDayStart && day <= mEnabledDayEnd) {
@@ -386,12 +445,12 @@
                 stateMask |= StateSet.VIEW_STATE_ACTIVATED;
 
                 // Adjust the circle to be centered on the row.
-                canvas.drawCircle(x, centerY, mDaySelectorRadius, mDaySelectorPaint);
-            } else if (mTouchedDay == day) {
+                canvas.drawCircle(colCenter, rowCenter, mDaySelectorRadius, mDaySelectorPaint);
+            } else if (mTouchedItem == day) {
                 stateMask |= StateSet.VIEW_STATE_PRESSED;
 
                 // Adjust the circle to be centered on the row.
-                canvas.drawCircle(x, centerY, mDaySelectorRadius, mDayHighlightPaint);
+                canvas.drawCircle(colCenter, rowCenter, mDaySelectorRadius, mDayHighlightPaint);
             }
 
             final boolean isDayToday = mToday == day;
@@ -402,19 +461,29 @@
                 final int[] stateSet = StateSet.get(stateMask);
                 dayTextColor = mDayTextColor.getColorForState(stateSet, 0);
             }
-            mDayPaint.setColor(dayTextColor);
+            p.setColor(dayTextColor);
 
-            canvas.drawText("" + day, x, centerY - halfLineHeight, mDayPaint);
+            canvas.drawText(Integer.toString(day), colCenter, rowCenter - halfLineHeight, p);
 
-            j++;
+            col++;
 
-            if (j == DAYS_IN_WEEK) {
-                j = 0;
-                centerY += mDayHeight;
+            if (col == DAYS_IN_WEEK) {
+                col = 0;
+                rowCenter += rowHeight;
             }
         }
     }
 
+    private void drawButtons(Canvas canvas) {
+        if (mPrevEnabled && mPrevDrawable != null) {
+            mPrevDrawable.draw(canvas);
+        }
+
+        if (mNextEnabled && mNextDrawable != null) {
+            mNextDrawable.draw(canvas);
+        }
+    }
+
     private static boolean isValidDayOfWeek(int day) {
         return day >= Calendar.SUNDAY && day <= Calendar.SATURDAY;
     }
@@ -569,8 +638,42 @@
 
     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        mPaddedWidth = w - getPaddingLeft() - getPaddingRight();
-        mPaddedHeight = w - getPaddingTop() - getPaddingBottom();
+        final int paddedLeft = getPaddingLeft();
+        final int paddedTop = getPaddingTop();
+        final int paddedRight = w - getPaddingRight();
+        final int paddedBottom = h - getPaddingBottom();
+        mPaddedWidth = paddedRight - paddedLeft;
+        mPaddedHeight = paddedBottom - paddedTop;
+
+        final int monthHeight = mMonthHeight;
+        final int cellWidth = mPaddedWidth / DAYS_IN_WEEK;
+
+        // Vertically center the previous/next drawables within the month
+        // header, horizontally center within the day cell, then expand the
+        // hit area to ensure it's at least 48x48dp.
+        final Drawable prevDrawable = mPrevDrawable;
+        if (prevDrawable != null) {
+            final int dW = prevDrawable.getIntrinsicWidth();
+            final int dH = prevDrawable.getIntrinsicHeight();
+            final int iconTop = (monthHeight - dH) / 2;
+            final int iconLeft = (cellWidth - dW) / 2;
+
+            // Button bounds don't include padding, but hit area does.
+            prevDrawable.setBounds(iconLeft, iconTop, iconLeft + dW, iconTop + dH);
+            mPrevHitArea.set(0, 0, paddedLeft + cellWidth, paddedTop + monthHeight);
+        }
+
+        final Drawable nextDrawable = mNextDrawable;
+        if (nextDrawable != null) {
+            final int dW = nextDrawable.getIntrinsicWidth();
+            final int dH = nextDrawable.getIntrinsicHeight();
+            final int iconTop = (monthHeight - dH) / 2;
+            final int iconRight = mPaddedWidth - (cellWidth - dW) / 2;
+
+            // Button bounds don't include padding, but hit area does.
+            nextDrawable.setBounds(iconRight - dW, iconTop, iconRight, iconTop + dH);
+            mNextHitArea.set(paddedRight - cellWidth, 0, w, paddedTop + monthHeight);
+        }
 
         // Invalidate cached accessibility information.
         mTouchHelper.invalidateRoot();
@@ -585,22 +688,29 @@
     }
 
     /**
-     * Calculates the day of the month at the specified touch position. Returns
-     * the day of the month or -1 if the position wasn't in a valid day.
+     * Calculates the day of the month or item identifier at the specified
+     * touch position. Returns the day of the month or -1 if the position
+     * wasn't in a valid day.
      *
      * @param x the x position of the touch event
      * @param y the y position of the touch event
-     * @return the day of the month at (x, y) or -1 if the position wasn't in a
-     *         valid day
+     * @return the day of the month at (x, y), an item identifier, or -1 if the
+     *         position wasn't in a valid day or item
      */
-    private int getDayAtLocation(float x, float y) {
-        final int paddedX = (int) (x - getPaddingLeft() + 0.5f);
+    private int getItemAtLocation(int x, int y) {
+        if (mNextEnabled && mNextDrawable != null && mNextHitArea.contains(x, y)) {
+            return ITEM_ID_NEXT;
+        } else if (mPrevEnabled && mPrevDrawable != null && mPrevHitArea.contains(x, y)) {
+            return ITEM_ID_PREV;
+        }
+
+        final int paddedX = x - getPaddingLeft();
         if (paddedX < 0 || paddedX >= mPaddedWidth) {
             return -1;
         }
 
         final int headerHeight = mMonthHeight + mDayOfWeekHeight;
-        final int paddedY = (int) (y - getPaddingTop() + 0.5f);
+        final int paddedY = y - getPaddingTop();
         if (paddedY < headerHeight || paddedY >= mPaddedHeight) {
             return -1;
         }
@@ -619,47 +729,97 @@
     /**
      * Calculates the bounds of the specified day.
      *
-     * @param day the day of the month
+     * @param id the day of the month, or an item identifier
      * @param outBounds the rect to populate with bounds
      */
-    private boolean getBoundsForDay(int day, Rect outBounds) {
-        if (day < 1 || day > mDaysInMonth) {
+    private boolean getBoundsForItem(int id, Rect outBounds) {
+        if (mNextEnabled && id == ITEM_ID_NEXT) {
+            if (mNextDrawable != null) {
+                outBounds.set(mNextHitArea);
+                return true;
+            }
+        } else if (mPrevEnabled && id == ITEM_ID_PREV) {
+            if (mPrevDrawable != null) {
+                outBounds.set(mPrevHitArea);
+                return true;
+            }
+        }
+
+        if (id < 1 || id > mDaysInMonth) {
             return false;
         }
 
-        final int index = day - 1 + findDayOffset();
-        final int row = index / DAYS_IN_WEEK;
+        final int index = id - 1 + findDayOffset();
+
+        // Compute left edge.
         final int col = index % DAYS_IN_WEEK;
+        final int colWidth = mPaddedWidth / DAYS_IN_WEEK;
+        final int left = getPaddingLeft() + col * colWidth;
 
+        // Compute top edge.
+        final int row = index / DAYS_IN_WEEK;
+        final int rowHeight = mDayHeight;
         final int headerHeight = mMonthHeight + mDayOfWeekHeight;
-        final int paddedY = row * mDayHeight + headerHeight;
-        final int paddedX = col * mPaddedWidth;
+        final int top = getPaddingTop() + headerHeight + row * rowHeight;
 
-        final int y = paddedY + getPaddingTop();
-        final int x = paddedX + getPaddingLeft();
-
-        final int cellHeight = mDayHeight;
-        final int cellWidth = mPaddedWidth / DAYS_IN_WEEK;
-        outBounds.set(x, y, (x + cellWidth), (y + cellHeight));
-
+        outBounds.set(left, top, left + colWidth, top + rowHeight);
         return true;
     }
 
     /**
+     * Called when an item is clicked.
+     *
+     * @param id the day number or item identifier
+     */
+    private boolean onItemClicked(int id, boolean animate) {
+        return onNavigationClicked(id, animate) || onDayClicked(id);
+    }
+
+    /**
      * Called when the user clicks on a day. Handles callbacks to the
      * {@link OnDayClickListener} if one is set.
      *
      * @param day the day that was clicked
      */
-    private void onDayClicked(int day) {
+    private boolean onDayClicked(int day) {
+        if (day < 0 || day > mDaysInMonth) {
+            return false;
+        }
+
         if (mOnDayClickListener != null) {
-            Calendar date = Calendar.getInstance();
+            final Calendar date = Calendar.getInstance();
             date.set(mYear, mMonth, day);
             mOnDayClickListener.onDayClick(this, date);
         }
 
         // This is a no-op if accessibility is turned off.
         mTouchHelper.sendEventForVirtualView(day, AccessibilityEvent.TYPE_VIEW_CLICKED);
+        return true;
+    }
+
+    /**
+     * Called when the user clicks on a navigation button. Handles callbacks to
+     * the {@link OnDayClickListener} if one is set.
+     *
+     * @param id the item identifier
+     */
+    private boolean onNavigationClicked(int id, boolean animate) {
+        final int direction;
+        if (id == ITEM_ID_NEXT) {
+            direction = 1;
+        } else if (id == ITEM_ID_PREV) {
+            direction = -1;
+        } else {
+            return false;
+        }
+
+        if (mOnDayClickListener != null) {
+            mOnDayClickListener.onNavigationClick(this, direction, animate);
+        }
+
+        // This is a no-op if accessibility is turned off.
+        mTouchHelper.sendEventForVirtualView(id, AccessibilityEvent.TYPE_VIEW_CLICKED);
+        return true;
     }
 
     /**
@@ -678,7 +838,7 @@
 
         @Override
         protected int getVirtualViewAt(float x, float y) {
-            final int day = getDayAtLocation(x, y);
+            final int day = getItemAtLocation((int) (x + 0.5f), (int) (y + 0.5f));
             if (day >= 0) {
                 return day;
             }
@@ -687,6 +847,14 @@
 
         @Override
         protected void getVisibleVirtualViews(IntArray virtualViewIds) {
+            if (mNextEnabled && mNextDrawable != null) {
+                virtualViewIds.add(ITEM_ID_PREV);
+            }
+
+            if (mPrevEnabled && mPrevDrawable != null) {
+                virtualViewIds.add(ITEM_ID_NEXT);
+            }
+
             for (int day = 1; day <= mDaysInMonth; day++) {
                 virtualViewIds.add(day);
             }
@@ -699,7 +867,7 @@
 
         @Override
         protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) {
-            final boolean hasBounds = getBoundsForDay(virtualViewId, mTempRect);
+            final boolean hasBounds = getBoundsForItem(virtualViewId, mTempRect);
 
             if (!hasBounds) {
                 // The day is invalid, kill the node.
@@ -710,12 +878,14 @@
                 return;
             }
 
+            node.setText(getItemText(virtualViewId));
             node.setContentDescription(getItemDescription(virtualViewId));
             node.setBoundsInParent(mTempRect);
             node.addAction(AccessibilityAction.ACTION_CLICK);
 
             if (virtualViewId == mActivatedDay) {
-                node.setSelected(true);
+                // TODO: This should use activated once that's supported.
+                node.setChecked(true);
             }
 
         }
@@ -725,31 +895,45 @@
                 Bundle arguments) {
             switch (action) {
                 case AccessibilityNodeInfo.ACTION_CLICK:
-                    onDayClicked(virtualViewId);
-                    return true;
+                    return onItemClicked(virtualViewId, false);
             }
 
             return false;
         }
 
         /**
-         * Generates a description for a given time object. Since this
-         * description will be spoken, the components are ordered by descending
-         * specificity as DAY MONTH YEAR.
+         * Generates a description for a given virtual view.
          *
-         * @param day The day to generate a description for
-         * @return A description of the time object
+         * @param id the day or item identifier to generate a description for
+         * @return a description of the virtual view
          */
-        private CharSequence getItemDescription(int day) {
-            mTempCalendar.set(mYear, mMonth, day);
-            final CharSequence date = DateFormat.format(DATE_FORMAT,
-                    mTempCalendar.getTimeInMillis());
-
-            if (day == mActivatedDay) {
-                return getContext().getString(R.string.item_is_selected, date);
+        private CharSequence getItemDescription(int id) {
+            if (id == ITEM_ID_NEXT) {
+                return mNextContentDesc;
+            } else if (id == ITEM_ID_PREV) {
+                return mPrevContentDesc;
+            } else if (id >= 1 && id <= mDaysInMonth) {
+                mTempCalendar.set(mYear, mMonth, id);
+                return DateFormat.format(DATE_FORMAT, mTempCalendar.getTimeInMillis());
             }
 
-            return date;
+            return "";
+        }
+
+        /**
+         * Generates displayed text for a given virtual view.
+         *
+         * @param id the day or item identifier to generate text for
+         * @return the visible text of the virtual view
+         */
+        private CharSequence getItemText(int id) {
+            if (id == ITEM_ID_NEXT || id == ITEM_ID_PREV) {
+                return null;
+            } else if (id >= 1 && id <= mDaysInMonth) {
+                return Integer.toString(id);
+            }
+
+            return null;
         }
     }
 
@@ -758,5 +942,6 @@
      */
     public interface OnDayClickListener {
         public void onDayClick(SimpleMonthView view, Calendar day);
+        public void onNavigationClick(SimpleMonthView view, int direction, boolean animate);
     }
 }
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 944b491..986c0f8 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -16,6 +16,7 @@
 
 package android.widget;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.Widget;
 import android.content.Context;
@@ -29,18 +30,13 @@
 import java.util.Locale;
 
 /**
- * A view for selecting the time of day, in either 24 hour or AM/PM mode. The
- * hour, each minute digit, and AM/PM (if applicable) can be conrolled by
- * vertical spinners. The hour can be entered by keyboard input. Entering in two
- * digit hours can be accomplished by hitting two digits within a timeout of
- * about a second (e.g. '1' then '2' to select 12). The minutes can be entered
- * by entering single digits. Under AM/PM mode, the user can hit 'a', 'A", 'p'
- * or 'P' to pick. For a dialog using this view, see
- * {@link android.app.TimePickerDialog}.
+ * A widget for selecting the time of day, in either 24-hour or AM/PM mode.
  * <p>
- * See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a>
- * guide.
- * </p>
+ * For a dialog using this view, see {@link android.app.TimePickerDialog}. See
+ * the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a>
+ * guide for more information.
+ *
+ * @attr ref android.R.styleable#TimePicker_timePickerMode
  */
 @Widget
 public class TimePicker extends FrameLayout {
@@ -96,44 +92,105 @@
     }
 
     /**
-     * Set the current hour.
+     * Sets the currently selected hour using 24-hour time.
+     *
+     * @param hour the hour to set, in the range (0-23)
+     * @see #getHour()
      */
-    public void setCurrentHour(Integer currentHour) {
-        mDelegate.setCurrentHour(currentHour);
+    public void setHour(int hour) {
+        mDelegate.setCurrentHour(hour);
     }
 
     /**
-     * @return The current hour in the range (0-23).
+     * Returns the currently selected hour using 24-hour time.
+     *
+     * @return the currently selected hour, in the range (0-23)
+     * @see #setHour(int)
      */
+    public int getHour() {
+        return mDelegate.getCurrentHour();
+    }
+
+    /**
+     * Sets the currently selected minute..
+     *
+     * @param minute the minute to set, in the range (0-59)
+     * @see #getMinute()
+     */
+    public void setMinute(int minute) {
+        mDelegate.setCurrentMinute(minute);
+    }
+
+    /**
+     * Returns the currently selected minute.
+     *
+     * @return the currently selected minute, in the range (0-59)
+     * @see #setMinute(int)
+     */
+    public int getMinute() {
+        return mDelegate.getCurrentMinute();
+    }
+
+    /**
+     * Sets the current hour.
+     *
+     * @deprecated Use {@link #setHour(int)}
+     */
+    @Deprecated
+    public void setCurrentHour(@NonNull Integer currentHour) {
+        setHour(currentHour);
+    }
+
+    /**
+     * @return the current hour in the range (0-23)
+     * @deprecated Use {@link #getHour()}
+     */
+    @NonNull
+    @Deprecated
     public Integer getCurrentHour() {
         return mDelegate.getCurrentHour();
     }
 
     /**
      * Set the current minute (0-59).
+     *
+     * @deprecated Use {@link #setMinute(int)}
      */
-    public void setCurrentMinute(Integer currentMinute) {
+    @Deprecated
+    public void setCurrentMinute(@NonNull Integer currentMinute) {
         mDelegate.setCurrentMinute(currentMinute);
     }
 
     /**
-     * @return The current minute.
+     * @return the current minute
+     * @deprecated Use {@link #getMinute()}
      */
+    @NonNull
+    @Deprecated
     public Integer getCurrentMinute() {
         return mDelegate.getCurrentMinute();
     }
 
     /**
-     * Set whether in 24 hour or AM/PM mode.
+     * Sets whether this widget displays time in 24-hour mode or 12-hour mode
+     * with an AM/PM picker.
      *
-     * @param is24HourView True = 24 hour mode. False = AM/PM.
+     * @param is24HourView {@code true} to display in 24-hour mode,
+     *                     {@code false} for 12-hour mode with AM/PM
+     * @see #is24HourView()
      */
-    public void setIs24HourView(Boolean is24HourView) {
+    public void setIs24HourView(@NonNull Boolean is24HourView) {
+        if (is24HourView == null) {
+            return;
+        }
+
         mDelegate.setIs24HourView(is24HourView);
     }
 
     /**
-     * @return true if this is in 24 hour view else false.
+     * @return {@code true} if this widget displays time in 24-hour mode,
+     *         {@code false} otherwise}
+     * @see #setIs24HourView(Boolean)
      */
     public boolean is24HourView() {
         return mDelegate.is24HourView();
@@ -210,13 +267,13 @@
      * for the real behavior.
      */
     interface TimePickerDelegate {
-        void setCurrentHour(Integer currentHour);
-        Integer getCurrentHour();
+        void setCurrentHour(int currentHour);
+        int getCurrentHour();
 
-        void setCurrentMinute(Integer currentMinute);
-        Integer getCurrentMinute();
+        void setCurrentMinute(int currentMinute);
+        int getCurrentMinute();
 
-        void setIs24HourView(Boolean is24HourView);
+        void setIs24HourView(boolean is24HourView);
         boolean is24HourView();
 
         void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener);
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index 718d216..c58d5cb 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -376,7 +376,7 @@
      * Set the current hour.
      */
     @Override
-    public void setCurrentHour(Integer currentHour) {
+    public void setCurrentHour(int currentHour) {
         if (mInitialHourOfDay == currentHour) {
             return;
         }
@@ -393,7 +393,7 @@
      * @return The current hour in the range (0-23).
      */
     @Override
-    public Integer getCurrentHour() {
+    public int getCurrentHour() {
         int currentHour = mRadialTimePickerView.getCurrentHour();
         if (mIs24HourView) {
             return currentHour;
@@ -412,7 +412,7 @@
      * Set the current minute (0-59).
      */
     @Override
-    public void setCurrentMinute(Integer currentMinute) {
+    public void setCurrentMinute(int currentMinute) {
         if (mInitialMinute == currentMinute) {
             return;
         }
@@ -427,7 +427,7 @@
      * @return The current minute.
      */
     @Override
-    public Integer getCurrentMinute() {
+    public int getCurrentMinute() {
         return mRadialTimePickerView.getCurrentMinute();
     }
 
@@ -437,7 +437,7 @@
      * @param is24HourView True = 24 hour mode. False = AM/PM.
      */
     @Override
-    public void setIs24HourView(Boolean is24HourView) {
+    public void setIs24HourView(boolean is24HourView) {
         if (is24HourView == mIs24HourView) {
             return;
         }
diff --git a/core/java/android/widget/TimePickerSpinnerDelegate.java b/core/java/android/widget/TimePickerSpinnerDelegate.java
index 513c55b..df6b0a9 100644
--- a/core/java/android/widget/TimePickerSpinnerDelegate.java
+++ b/core/java/android/widget/TimePickerSpinnerDelegate.java
@@ -279,13 +279,13 @@
     }
 
     @Override
-    public void setCurrentHour(Integer currentHour) {
+    public void setCurrentHour(int currentHour) {
         setCurrentHour(currentHour, true);
     }
 
-    private void setCurrentHour(Integer currentHour, boolean notifyTimeChanged) {
+    private void setCurrentHour(int currentHour, boolean notifyTimeChanged) {
         // why was Integer used in the first place?
-        if (currentHour == null || currentHour == getCurrentHour()) {
+        if (currentHour == getCurrentHour()) {
             return;
         }
         if (!is24HourView()) {
@@ -310,7 +310,7 @@
     }
 
     @Override
-    public Integer getCurrentHour() {
+    public int getCurrentHour() {
         int currentHour = mHourSpinner.getValue();
         if (is24HourView()) {
             return currentHour;
@@ -322,7 +322,7 @@
     }
 
     @Override
-    public void setCurrentMinute(Integer currentMinute) {
+    public void setCurrentMinute(int currentMinute) {
         if (currentMinute == getCurrentMinute()) {
             return;
         }
@@ -331,12 +331,12 @@
     }
 
     @Override
-    public Integer getCurrentMinute() {
+    public int getCurrentMinute() {
         return mMinuteSpinner.getValue();
     }
 
     @Override
-    public void setIs24HourView(Boolean is24HourView) {
+    public void setIs24HourView(boolean is24HourView) {
         if (mIs24HourView == is24HourView) {
             return;
         }
diff --git a/core/java/android/widget/YearPickerView.java b/core/java/android/widget/YearPickerView.java
index 7bd502e..7182414 100644
--- a/core/java/android/widget/YearPickerView.java
+++ b/core/java/android/widget/YearPickerView.java
@@ -111,16 +111,12 @@
         mAdapter.setRange(min, max);
     }
 
-    public void setYearTextAppearance(int resId) {
-        mAdapter.setItemTextAppearance(resId);
-    }
-
-    public void setYearActivatedTextAppearance(int resId) {
-        mAdapter.setItemActivatedTextAppearance(resId);
-    }
-
     private static class YearAdapter extends BaseAdapter {
         private static final int ITEM_LAYOUT = R.layout.year_label_text_view;
+        private static final int ITEM_TEXT_APPEARANCE =
+                R.style.TextAppearance_Material_DatePicker_List_YearLabel;
+        private static final int ITEM_TEXT_ACTIVATED_APPEARANCE =
+                R.style.TextAppearance_Material_DatePicker_List_YearLabel_Activated;
 
         private final LayoutInflater mInflater;
 
@@ -128,9 +124,6 @@
         private int mMinYear;
         private int mCount;
 
-        private int mItemTextAppearanceResId;
-        private int mItemActivatedTextAppearanceResId;
-
         public YearAdapter(Context context) {
             mInflater = LayoutInflater.from(context);
         }
@@ -155,16 +148,6 @@
             return false;
         }
 
-        public void setItemTextAppearance(int resId) {
-            mItemTextAppearanceResId = resId;
-            notifyDataSetChanged();
-        }
-
-        public void setItemActivatedTextAppearance(int resId) {
-            mItemActivatedTextAppearanceResId = resId;
-            notifyDataSetChanged();
-        }
-
         @Override
         public int getCount() {
             return mCount;
@@ -203,10 +186,10 @@
             final boolean activated = mActivatedYear == year;
 
             final int textAppearanceResId;
-            if (activated && mItemActivatedTextAppearanceResId != 0) {
-                textAppearanceResId = mItemActivatedTextAppearanceResId;
+            if (activated && ITEM_TEXT_ACTIVATED_APPEARANCE != 0) {
+                textAppearanceResId = ITEM_TEXT_ACTIVATED_APPEARANCE;
             } else {
-                textAppearanceResId = mItemTextAppearanceResId;
+                textAppearanceResId = ITEM_TEXT_APPEARANCE;
             }
 
             final TextView v = (TextView) convertView;
diff --git a/core/java/com/android/internal/logging/EventLogTags.logtags b/core/java/com/android/internal/logging/EventLogTags.logtags
index 9e178df..870d20d 100644
--- a/core/java/com/android/internal/logging/EventLogTags.logtags
+++ b/core/java/com/android/internal/logging/EventLogTags.logtags
@@ -4,4 +4,4 @@
 
 # interaction logs
 524287 sysui_view_visibility (category|1|5),(visible|1|6)
-524288 sysui_action (category|1|5),(type|1|6)
+524288 sysui_action (category|1|5)
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index 2de7394..9b45e34 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -31,6 +31,15 @@
     public static final int MANAGE_APPLICATIONS_ALL = 134;
     public static final int MANAGE_APPLICATIONS_NOTIFICATIONS = 135;
 
+    public static final int ACTION_WIFI_ADD_NETWORK = 136;
+    public static final int ACTION_WIFI_CONNECT = 137;
+    public static final int ACTION_WIFI_FORCE_SCAN = 138;
+    public static final int ACTION_WIFI_FORGET = 139;
+    public static final int ACTION_WIFI_OFF = 140;
+    public static final int ACTION_WIFI_ON = 141;
+
+    public static final int MANAGE_PERMISSIONS = 142;
+
     public static void visible(Context context, int category) throws IllegalArgumentException {
         if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
             throw new IllegalArgumentException("Must define metric category");
@@ -44,4 +53,11 @@
         }
         EventLogTags.writeSysuiViewVisibility(category, 0);
     }
+
+    public static void action(Context context, int category) {
+        if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
+            throw new IllegalArgumentException("Must define metric category");
+        }
+        EventLogTags.writeSysuiAction(category);
+    }
 }
diff --git a/core/java/com/android/internal/widget/ViewPager.java b/core/java/com/android/internal/widget/ViewPager.java
index 8018942..8d66191 100644
--- a/core/java/com/android/internal/widget/ViewPager.java
+++ b/core/java/com/android/internal/widget/ViewPager.java
@@ -42,6 +42,7 @@
 import android.view.ViewParent;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.view.accessibility.AccessibilityRecord;
 import android.view.animation.Interpolator;
 import android.widget.EdgeEffect;
@@ -371,8 +372,6 @@
         mCloseEnough = (int) (CLOSE_ENOUGH * density);
         mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density);
 
-        setAccessibilityDelegate(new MyAccessibilityDelegate());
-
         if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
         }
@@ -2695,29 +2694,6 @@
     }
 
     @Override
-    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
-        // Dispatch scroll events from this ViewPager.
-        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
-            return super.dispatchPopulateAccessibilityEvent(event);
-        }
-
-        // Dispatch all other accessibility events from the current page.
-        final int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            final View child = getChildAt(i);
-            if (child.getVisibility() == VISIBLE) {
-                final ItemInfo ii = infoForChild(child);
-                if (ii != null && ii.position == mCurItem &&
-                        child.dispatchPopulateAccessibilityEvent(event)) {
-                    return true;
-                }
-            }
-        }
-
-        return false;
-    }
-
-    @Override
     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
         return new LayoutParams();
     }
@@ -2737,60 +2713,63 @@
         return new LayoutParams(getContext(), attrs);
     }
 
-    class MyAccessibilityDelegate extends AccessibilityDelegate {
 
-        @Override
-        public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
-            super.onInitializeAccessibilityEvent(host, event);
-            event.setClassName(ViewPager.class.getName());
-            final AccessibilityRecord record = AccessibilityRecord.obtain();
-            record.setScrollable(canScroll());
-            if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED
-                    && mAdapter != null) {
-                record.setItemCount(mAdapter.getCount());
-                record.setFromIndex(mCurItem);
-                record.setToIndex(mCurItem);
-            }
+    @Override
+    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+        super.onInitializeAccessibilityEvent(event);
+
+        event.setClassName(ViewPager.class.getName());
+        event.setScrollable(canScroll());
+
+        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED && mAdapter != null) {
+            event.setItemCount(mAdapter.getCount());
+            event.setFromIndex(mCurItem);
+            event.setToIndex(mCurItem);
+        }
+    }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+
+        info.setClassName(ViewPager.class.getName());
+        info.setScrollable(canScroll());
+
+        if (canScrollHorizontally(1)) {
+            info.addAction(AccessibilityAction.ACTION_SCROLL_FORWARD);
         }
 
-        @Override
-        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
-            super.onInitializeAccessibilityNodeInfo(host, info);
-            info.setClassName(ViewPager.class.getName());
-            info.setScrollable(canScroll());
-            if (canScrollHorizontally(1)) {
-                info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
-            }
-            if (canScrollHorizontally(-1)) {
-                info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
-            }
+        if (canScrollHorizontally(-1)) {
+            info.addAction(AccessibilityAction.ACTION_SCROLL_BACKWARD);
+        }
+    }
+
+    @Override
+    public boolean performAccessibilityAction(int action, Bundle args) {
+        if (super.performAccessibilityAction(action, args)) {
+            return true;
         }
 
-        @Override
-        public boolean performAccessibilityAction(View host, int action, Bundle args) {
-            if (super.performAccessibilityAction(host, action, args)) {
-                return true;
-            }
-            switch (action) {
-                case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
-                    if (canScrollHorizontally(1)) {
-                        setCurrentItem(mCurItem + 1);
-                        return true;
-                    }
-                } return false;
-                case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
-                    if (canScrollHorizontally(-1)) {
-                        setCurrentItem(mCurItem - 1);
-                        return true;
-                    }
-                } return false;
-            }
-            return false;
+        switch (action) {
+            case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
+                if (canScrollHorizontally(1)) {
+                    setCurrentItem(mCurItem + 1);
+                    return true;
+                }
+                return false;
+            case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
+                if (canScrollHorizontally(-1)) {
+                    setCurrentItem(mCurItem - 1);
+                    return true;
+                }
+                return false;
         }
 
-        private boolean canScroll() {
-            return (mAdapter != null) && (mAdapter.getCount() > 1);
-        }
+        return false;
+    }
+
+    private boolean canScroll() {
+        return mAdapter != null && mAdapter.getCount() > 1;
     }
 
     private class PagerObserver extends DataSetObserver {
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index 49ee6c5..4c08b4b 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -89,7 +89,7 @@
 static void restore(JNIEnv* env, jobject, jlong canvasHandle) {
     Canvas* canvas = get_canvas(canvasHandle);
     if (canvas->getSaveCount() <= 1) {  // cannot restore anymore
-        // fail silently on underflow, so as not to break existing apps that miscount
+        doThrowISE(env, "Underflow in restore - more restores than saves");
         return;
     }
     canvas->restore();
@@ -98,7 +98,7 @@
 static void restoreToCount(JNIEnv* env, jobject, jlong canvasHandle, jint restoreCount) {
     Canvas* canvas = get_canvas(canvasHandle);
     if (restoreCount < 1 || restoreCount > canvas->getSaveCount()) {
-        // fail silently on underflow, so as not to break existing apps that miscount
+        doThrowIAE(env, "Underflow in restoreToCount - more restores than saves");
         return;
     }
     canvas->restoreToCount(restoreCount);
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 3d9a9ed..11b3805 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -21,6 +21,7 @@
 #include "jni.h"
 #include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
+#include <ScopedPrimitiveArray.h>
 
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
@@ -35,6 +36,7 @@
 #include <Animator.h>
 #include <AnimationContext.h>
 #include <IContextFactory.h>
+#include <JankTracker.h>
 #include <RenderNode.h>
 #include <renderthread/CanvasContext.h>
 #include <renderthread/RenderProxy.h>
@@ -219,6 +221,12 @@
     proxy->setTextureAtlas(buffer, map, len);
 }
 
+static void android_view_ThreadedRenderer_setProcessStatsBuffer(JNIEnv* env, jobject clazz,
+        jlong proxyPtr, jint fd) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+    proxy->setProcessStatsBuffer(fd);
+}
+
 static jlong android_view_ThreadedRenderer_createRootRenderNode(JNIEnv* env, jobject clazz) {
     RootRenderNode* node = new RootRenderNode(env);
     node->incStrong(0);
@@ -403,6 +411,16 @@
     proxy->dumpProfileInfo(fd, dumpFlags);
 }
 
+static void android_view_ThreadedRenderer_dumpProfileData(JNIEnv* env, jobject clazz,
+        jbyteArray jdata, jobject javaFileDescriptor) {
+    int fd = jniGetFDFromFileDescriptor(env, javaFileDescriptor);
+    ScopedByteArrayRO buffer(env, jdata);
+    if (buffer.get()) {
+        JankTracker::dumpBuffer(buffer.get(), buffer.size(), fd);
+    }
+}
+
+
 // ----------------------------------------------------------------------------
 // Shaders
 // ----------------------------------------------------------------------------
@@ -423,6 +441,7 @@
 
 static JNINativeMethod gMethods[] = {
     { "nSetAtlas", "(JLandroid/view/GraphicBuffer;[J)V",   (void*) android_view_ThreadedRenderer_setAtlas },
+    { "nSetProcessStatsBuffer", "(JI)V", (void*) android_view_ThreadedRenderer_setProcessStatsBuffer },
     { "nCreateRootRenderNode", "()J", (void*) android_view_ThreadedRenderer_createRootRenderNode },
     { "nCreateProxy", "(ZJ)J", (void*) android_view_ThreadedRenderer_createProxy },
     { "nDeleteProxy", "(J)V", (void*) android_view_ThreadedRenderer_deleteProxy },
@@ -449,6 +468,7 @@
     { "nStopDrawing", "(J)V", (void*) android_view_ThreadedRenderer_stopDrawing },
     { "nNotifyFramePending", "(J)V", (void*) android_view_ThreadedRenderer_notifyFramePending },
     { "nDumpProfileInfo", "(JLjava/io/FileDescriptor;I)V", (void*) android_view_ThreadedRenderer_dumpProfileInfo },
+    { "nDumpProfileData", "([BLjava/io/FileDescriptor;)V", (void*) android_view_ThreadedRenderer_dumpProfileData },
     { "setupShadersDiskCache", "(Ljava/lang/String;)V",
                 (void*) android_view_ThreadedRenderer_setupShadersDiskCache },
 };
diff --git a/core/res/res/color/date_picker_header_text_material.xml b/core/res/res/color/date_picker_header_text_material.xml
deleted file mode 100644
index baa8958..0000000
--- a/core/res/res/color/date_picker_header_text_material.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item
-        android:state_activated="true"
-        android:color="?attr/textColorPrimaryInverse" />
-    <item
-        android:color="?attr/textColorSecondaryInverse" />
-</selector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_chevron_left.xml b/core/res/res/drawable/ic_chevron_left.xml
new file mode 100644
index 0000000..dc24706
--- /dev/null
+++ b/core/res/res/drawable/ic_chevron_left.xml
@@ -0,0 +1,25 @@
+<!--
+    Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M15.41 7.41L14 6l-6 6 6 6 1.41,-1.41L10.83 12z"/>
+</vector>
diff --git a/core/res/res/drawable/ic_chevron_right.xml b/core/res/res/drawable/ic_chevron_right.xml
new file mode 100644
index 0000000..4e6d8e3
--- /dev/null
+++ b/core/res/res/drawable/ic_chevron_right.xml
@@ -0,0 +1,25 @@
+<!--
+    Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6,-6z"/>
+</vector>
diff --git a/core/res/res/layout/date_picker_month_item_material.xml b/core/res/res/layout/date_picker_month_item_material.xml
new file mode 100644
index 0000000..cb79cee
--- /dev/null
+++ b/core/res/res/layout/date_picker_month_item_material.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 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.
+-->
+
+<android.widget.SimpleMonthView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/month_view"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingStart="@dimen/day_picker_padding_horizontal"
+    android:paddingEnd="@dimen/day_picker_padding_horizontal"
+    android:paddingTop="@dimen/day_picker_padding_top" />
diff --git a/core/res/res/layout/date_picker_view_animator_material.xml b/core/res/res/layout/date_picker_view_animator_material.xml
index 98ef1dd..620ddfa 100644
--- a/core/res/res/layout/date_picker_view_animator_material.xml
+++ b/core/res/res/layout/date_picker_view_animator_material.xml
@@ -26,9 +26,8 @@
         android:id="@+id/date_picker_day_picker"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:paddingStart="@dimen/day_picker_padding_horizontal"
-        android:paddingEnd="@dimen/day_picker_padding_horizontal"
-        android:paddingTop="@dimen/day_picker_padding_top" />
+        android:inAnimation="@anim/fade_in"
+        android:outAnimation="@anim/fade_out" />
 
     <android.widget.YearPickerView
         android:id="@+id/date_picker_year_picker"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 9895027..dcb4b9e 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4444,11 +4444,6 @@
     </declare-styleable>
 
     <declare-styleable name="DatePicker">
-        <!-- The first year (inclusive), for example "1940". {@deprecated Use minDate instead.} -->
-        <attr name="startYear" format="integer" />
-        <!-- The last year (inclusive), for example "2010". {@deprecated Use maxDate instead.} -->
-        <attr name="endYear" format="integer" />
-
         <!-- The first day of week according to {@link java.util.Calendar}. -->
         <attr name="firstDayOfWeek" />
         <!-- The minimal date shown by this calendar view in mm/dd/yyyy format. -->
@@ -4474,12 +4469,14 @@
         <!-- The background for the selected date header. -->
         <attr name="headerBackground" />
 
-        <!-- The list year's text appearance in the list. -->
+        <!-- The list year's text appearance in the list.
+             {@deprecated Use yearListTextColor. }-->
         <attr name="yearListItemTextAppearance" format="reference" />
         <!-- @hide The list year's text appearance in the list when activated. -->
         <attr name="yearListItemActivatedTextAppearance" format="reference" />
         <!-- The text color list of the calendar. -->
         <attr name="calendarTextColor" format="color" />
+
         <!-- Defines the look of the widget. Prior to the L release, the only choice was
              spinner. As of L, with the Material theme selected, the default layout is calendar,
              but this attribute can be used to force spinner to be used instead. -->
@@ -4490,17 +4487,29 @@
             <enum name="calendar" value="2" />
         </attr>
 
-        <!-- @deprecated The text appearance for the month (ex. May) in the selected date header. -->
+        <!-- The first year (inclusive), for example "1940".
+             {@deprecated Use minDate instead.} -->
+        <attr name="startYear" format="integer" />
+        <!-- The last year (inclusive), for example "2010".
+             {@deprecated Use maxDate instead.} -->
+        <attr name="endYear" format="integer" />
+        <!-- The text appearance for the month (ex. May) in the selected date header.
+             {@deprecated Use headerTextColor instead.} -->
         <attr name="headerMonthTextAppearance" format="reference" />
-        <!-- @deprecated The text appearance for the day of month (ex. 28) in the selected date header. -->
+        <!-- The text appearance for the day of month (ex. 28) in the selected date header.
+             {@deprecated Use headerTextColor instead.} -->
         <attr name="headerDayOfMonthTextAppearance" format="reference" />
-        <!-- The text appearance for the year (ex. 2014) in the selected date header. -->
+        <!-- The text appearance for the year (ex. 2014) in the selected date header.
+             {@deprecated Use headerTextColor instead.} -->
         <attr name="headerYearTextAppearance" format="reference" />
-        <!-- @deprecated The background color for the header's day of week. -->
+        <!-- The background color for the header's day of week.
+             {@deprecated No longer displayed.} -->
         <attr name="dayOfWeekBackground" format="color" />
-        <!-- @deprecated The text color for the header's day of week. -->
+        <!-- The text color for the header's day of week.
+             {@deprecated No longer displayed.} -->
         <attr name="dayOfWeekTextAppearance" format="reference" />
-        <!-- @deprecated The list year's selected circle color in the list. -->
+        <!-- The list year's selected circle color in the list.
+             {@deprecated No longer displayed.} -->
         <attr name="yearListSelectorColor" format="color" />
     </declare-styleable>
 
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 3431f35..7d08e7f 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -364,27 +364,26 @@
 
     <dimen name="resolver_max_width">480dp</dimen>
 
-     <!-- Size of the offset applied to the position of the circular mask. This is only
-     used on circular displays. In the case where there is no "chin", this will default
-     to 0 -->
-     <dimen name="circular_display_mask_offset">0px</dimen>
+    <!-- Size of the offset applied to the position of the circular mask. This
+         is only used on circular displays. In the case where there is no
+         "chin", this will default to 0 -->
+    <dimen name="circular_display_mask_offset">0px</dimen>
+    <!-- Amount to reduce the size of the circular mask by (to compensate for
+         aliasing effects). This is only used on circular displays. -->
+    <dimen name="circular_display_mask_thickness">1px</dimen>
 
-     <!-- Amount to reduce the size of the circular mask by (to compensate for aliasing
-     effects). This is only used on circular displays. -->
-     <dimen name="circular_display_mask_thickness">1px</dimen>
+    <dimen name="lock_pattern_dot_line_width">3dp</dimen>
+    <dimen name="lock_pattern_dot_size">12dp</dimen>
+    <dimen name="lock_pattern_dot_size_activated">28dp</dimen>
 
-     <dimen name="lock_pattern_dot_line_width">3dp</dimen>
-     <dimen name="lock_pattern_dot_size">12dp</dimen>
-     <dimen name="lock_pattern_dot_size_activated">28dp</dimen>
+    <dimen name="text_handle_min_size">40dp</dimen>
 
-     <dimen name="text_handle_min_size">40dp</dimen>
-
-     <!-- Lighting and shadow properties -->
-     <dimen name="light_y">-200dp</dimen>
-     <dimen name="light_z">800dp</dimen>
-     <dimen name="light_radius">600dp</dimen>
-     <item type="dimen" format="float" name="ambient_shadow_alpha">0.075</item>
-     <item type="dimen" format="float" name="spot_shadow_alpha">0.15</item>
+    <!-- Lighting and shadow properties -->
+    <dimen name="light_y">-200dp</dimen>
+    <dimen name="light_z">800dp</dimen>
+    <dimen name="light_radius">600dp</dimen>
+    <item type="dimen" format="float" name="ambient_shadow_alpha">0.075</item>
+    <item type="dimen" format="float" name="spot_shadow_alpha">0.15</item>
 
      <!-- Floating toolbar dimensions -->
      <dimen name="floating_toolbar_height">48dp</dimen>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 88225bd..0c91f32 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4415,6 +4415,10 @@
     <string name="date_picker_increment_year_button">Increase year</string>
     <!-- Description of the button to decrease the DatePicker's year value. [CHAR LIMIT=NONE] -->
     <string name="date_picker_decrement_year_button">Decrease year</string>
+    <!-- Description of the button to move to the previous month. [CHAR LIMIT=NONE] -->
+    <string name="date_picker_prev_month_button">Previous month</string>
+    <!-- Description of the button to move to the next month. [CHAR LIMIT=NONE] -->
+    <string name="date_picker_next_month_button">Next month</string>
 
     <!-- KeyboardView - accessibility support -->
     <!-- Description of the Alt button in a KeyboardView. [CHAR LIMIT=NONE] -->
@@ -5078,8 +5082,6 @@
     <string name="select_day">Select month and day</string>
     <!-- Accessibility announcement for the year picker [CHAR LIMIT=NONE] -->
     <string name="select_year">Select year</string>
-    <!-- Accessibility description for the item that is currently selected. -->
-    <string name="item_is_selected"><xliff:g id="item" example="2013">%1$s</xliff:g> selected</string>
     <!-- Accessibility announcement when a number that had been typed in is deleted [CHAR_LIMIT=NONE] -->
     <string name="deleted_key"><xliff:g id="key" example="4">%1$s</xliff:g> deleted</string>
 
diff --git a/core/res/res/values/styles_device_defaults.xml b/core/res/res/values/styles_device_defaults.xml
index 223578d..23ac221 100644
--- a/core/res/res/values/styles_device_defaults.xml
+++ b/core/res/res/values/styles_device_defaults.xml
@@ -240,10 +240,6 @@
     <!-- @deprecated Action bars are now themed using the inheritable android:theme attribute. -->
     <style name="TextAppearance.DeviceDefault.Widget.ActionMode.Subtitle.Inverse" parent="TextAppearance.Material.Widget.ActionMode.Subtitle.Inverse"/>
     <style name="TextAppearance.DeviceDefault.Widget.ActionBar.Menu" parent="TextAppearance.Material.Widget.ActionBar.Menu"/>
-    <style name="TextAppearance.DeviceDefault.DatePicker.DayOfWeekLabel" parent="TextAppearance.Material.DatePicker.DayOfWeekLabel"/>
-    <style name="TextAppearance.DeviceDefault.DatePicker.MonthLabel" parent="TextAppearance.Material.DatePicker.MonthLabel"/>
-    <style name="TextAppearance.DeviceDefault.DatePicker.DayOfMonthLabel" parent="TextAppearance.Material.DatePicker.DayOfMonthLabel"/>
-    <style name="TextAppearance.DeviceDefault.DatePicker.YearLabel" parent="TextAppearance.Material.DatePicker.YearLabel"/>
 
     <!-- Preference Styles -->
     <style name="Preference.DeviceDefault" parent="Preference.Material"/>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 472908c..9cf7884 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -389,32 +389,14 @@
         <item name="textColor">@color/primary_text_secondary_when_activated_material_inverse</item>
     </style>
 
-    <style name="TextAppearance.Material.DatePicker.DayOfWeekLabel" parent="TextAppearance.Material">
-        <item name="includeFontPadding">false</item>
-        <item name="textColor">?attr/textColorPrimaryInverse</item>
-        <item name="textSize">@dimen/datepicker_header_text_size</item>
-    </style>
-
-    <style name="TextAppearance.Material.DatePicker.MonthLabel" parent="TextAppearance.Material">
-        <item name="includeFontPadding">false</item>
-        <item name="textColor">@color/date_picker_header_text_material</item>
-        <item name="textSize">@dimen/datepicker_selected_date_month_size</item>
-    </style>
-
-    <style name="TextAppearance.Material.DatePicker.DayOfMonthLabel" parent="TextAppearance.Material">
-        <item name="includeFontPadding">false</item>
-        <item name="textColor">@color/date_picker_header_text_material</item>
-        <item name="textSize">@dimen/datepicker_selected_date_day_size</item>
-    </style>
-
     <style name="TextAppearance.Material.DatePicker.YearLabel" parent="TextAppearance.Material">
-        <item name="textColor">@color/date_picker_header_text_material</item>
+        <item name="textColor">@color/primary_text_secondary_when_activated_material_inverse</item>
         <item name="textSize">@dimen/date_picker_year_label_size</item>
         <item name="fontFamily">sans-serif-medium</item>
     </style>
 
     <style name="TextAppearance.Material.DatePicker.DateLabel" parent="TextAppearance.Material">
-        <item name="textColor">@color/date_picker_header_text_material</item>
+        <item name="textColor">@color/primary_text_secondary_when_activated_material_inverse</item>
         <item name="textSize">@dimen/date_picker_date_label_size</item>
         <item name="fontFamily">sans-serif-medium</item>
     </style>
@@ -675,8 +657,7 @@
         <item name="calendarViewShown">true</item>
         <!-- Attributes for new-style DatePicker. -->
         <item name="internalLayout">@layout/date_picker_material</item>
-        <item name="yearListItemTextAppearance">@style/TextAppearance.Material.DatePicker.List.YearLabel</item>
-        <item name="yearListItemActivatedTextAppearance">@style/TextAppearance.Material.DatePicker.List.YearLabel.Activated</item>
+        <item name="headerTextColor">@color/primary_text_secondary_when_activated_material</item>
         <item name="headerBackground">#ff555555</item>
     </style>
 
@@ -1038,6 +1019,7 @@
     </style>
 
     <style name="Widget.Material.Light.DatePicker" parent="Widget.Material.DatePicker">
+        <item name="headerTextColor">@color/primary_text_secondary_when_activated_material_inverse</item>
         <item name="headerBackground">?attr/colorAccent</item>
     </style>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 67d54729..18302ce 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2040,7 +2040,6 @@
   <java-symbol type="integer" name="config_downtime_condition_lookahead_threshold_hrs" />
   <java-symbol type="string" name="muted_by" />
 
-  <java-symbol type="string" name="item_is_selected" />
   <java-symbol type="string" name="select_day" />
   <java-symbol type="string" name="select_year" />
 
@@ -2190,6 +2189,8 @@
   <java-symbol type="style" name="TextAppearance.Material.Widget.Calendar.Month" />
   <java-symbol type="style" name="TextAppearance.Material.Widget.Calendar.DayOfWeek" />
   <java-symbol type="style" name="TextAppearance.Material.Widget.Calendar.Day" />
+  <java-symbol type="style" name="TextAppearance.Material.DatePicker.List.YearLabel" />
+  <java-symbol type="style" name="TextAppearance.Material.DatePicker.List.YearLabel.Activated" />
   <java-symbol type="dimen" name="day_picker_padding_top"/>
   <java-symbol type="dimen" name="date_picker_day_of_week_height"/>
 
@@ -2205,4 +2206,11 @@
   <java-symbol type="dimen" name="floating_toolbar_menu_button_minimum_width" />
   <java-symbol type="dimen" name="floating_toolbar_default_width" />
   <java-symbol type="dimen" name="floating_toolbar_minimum_overflow_height" />
+
+  <java-symbol type="drawable" name="ic_chevron_left" />
+  <java-symbol type="drawable" name="ic_chevron_right" />
+  <java-symbol type="string" name="date_picker_prev_month_button" />
+  <java-symbol type="string" name="date_picker_next_month_button" />
+  <java-symbol type="layout" name="date_picker_month_item_material" />
+  <java-symbol type="id" name="month_view" />
 </resources>
diff --git a/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/AndroidKeyStore.java
index f3eb317..1d16ca1 100644
--- a/keystore/java/android/security/AndroidKeyStore.java
+++ b/keystore/java/android/security/AndroidKeyStore.java
@@ -494,6 +494,19 @@
             args.addInt(KeymasterDefs.KM_TAG_DIGEST,
                     KeyStoreKeyConstraints.Digest.toKeymaster(digest));
         }
+        if (keyAlgorithm == KeyStoreKeyConstraints.Algorithm.HMAC) {
+            if (digest == null) {
+                throw new IllegalStateException("Digest algorithm must be specified for key"
+                        + " algorithm " + keyAlgorithmString);
+            }
+            Integer digestOutputSizeBytes =
+                    KeyStoreKeyConstraints.Digest.getOutputSizeBytes(digest);
+            if (digestOutputSizeBytes != null) {
+                // TODO: Remove MAC length constraint once Keymaster API no longer requires it.
+                // TODO: Switch to bits instead of bytes, once this is fixed in Keymaster
+                args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, digestOutputSizeBytes);
+            }
+        }
 
         @KeyStoreKeyConstraints.PurposeEnum int purposes = (params.getPurposes() != null)
                 ? params.getPurposes()
diff --git a/keystore/java/android/security/AndroidKeyStoreProvider.java b/keystore/java/android/security/AndroidKeyStoreProvider.java
index 598bcd8..7313c48 100644
--- a/keystore/java/android/security/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/AndroidKeyStoreProvider.java
@@ -39,5 +39,9 @@
         // javax.crypto.KeyGenerator
         put("KeyGenerator.AES", KeyStoreKeyGeneratorSpi.AES.class.getName());
         put("KeyGenerator.HmacSHA256", KeyStoreKeyGeneratorSpi.HmacSHA256.class.getName());
+
+        // javax.crypto.Mac
+        put("Mac.HmacSHA256", KeyStoreHmacSpi.HmacSHA256.class.getName());
+        put("Mac.HmacSHA256 SupportedKeyClasses", KeyStoreSecretKey.class.getName());
     }
 }
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index f68b3f6..94a479b 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -476,4 +476,34 @@
             return SYSTEM_ERROR;
         }
     }
+
+    /**
+     * Check if the operation referenced by {@code token} is currently authorized.
+     *
+     * @param token An operation token returned by a call to {@link KeyStore.begin}.
+     */
+    public boolean isOperationAuthorized(IBinder token) {
+        try {
+            return mBinder.isOperationAuthorized(token);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Cannot connect to keystore", e);
+            return false;
+        }
+    }
+
+    /**
+     * Add an authentication record to the keystore authorization table.
+     *
+     * @param authToken The packed bytes of a hw_auth_token_t to be provided to keymaster.
+     * @return {@code KeyStore.NO_ERROR} on success, otherwise an error value corresponding to
+     * a {@code KeymasterDefs.KM_ERROR_} value or {@code KeyStore} ResponseCode.
+     */
+    public int addAuthToken(byte[] authToken) {
+        try {
+            return mBinder.addAuthToken(authToken);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Cannot connect to keystore", e);
+            return SYSTEM_ERROR;
+        }
+    }
 }
diff --git a/keystore/java/android/security/KeyStoreConnectException.java b/keystore/java/android/security/KeyStoreConnectException.java
new file mode 100644
index 0000000..4c465a4
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreConnectException.java
@@ -0,0 +1,12 @@
+package android.security;
+
+/**
+ * Indicates a communications error with keystore service.
+ *
+ * @hide
+ */
+public class KeyStoreConnectException extends CryptoOperationException {
+    public KeyStoreConnectException() {
+        super("Failed to communicate with keystore service");
+    }
+}
diff --git a/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java
new file mode 100644
index 0000000..a37ddce
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java
@@ -0,0 +1,228 @@
+package android.security;
+
+import android.security.keymaster.OperationResult;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Helper for streaming a crypto operation's input and output via {@link KeyStore} service's
+ * {@code update} and {@code finish} operations.
+ *
+ * <p>The helper abstracts away to issues that need to be solved in most code that uses KeyStore's
+ * update and finish operations. Firstly, KeyStore's update and finish operations can consume only a
+ * limited amount of data in one go because the operations are marshalled via Binder. Secondly, the
+ * update operation may consume less data than provided, in which case the caller has to buffer
+ * the remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and
+ * {@link #doFinal(byte[], int, int) doFinal} operations which can be used to conveniently implement
+ * various JCA crypto primitives.
+ *
+ * <p>KeyStore operation through which data is streamed is abstracted away as
+ * {@link KeyStoreOperation} to avoid having this class deal with operation tokens and occasional
+ * additional parameters to update and final operations.
+ *
+ * @hide
+ */
+public class KeyStoreCryptoOperationChunkedStreamer {
+    public interface KeyStoreOperation {
+        /**
+         * Returns the result of the KeyStore update operation or null if keystore couldn't be
+         * reached.
+         */
+        OperationResult update(byte[] input);
+
+        /**
+         * Returns the result of the KeyStore finish operation or null if keystore couldn't be
+         * reached.
+         */
+        OperationResult finish(byte[] input);
+    }
+
+    // Binder buffer is about 1MB, but it's shared between all active transactions of the process.
+    // Thus, it's safer to use a much smaller upper bound.
+    private static final int DEFAULT_MAX_CHUNK_SIZE = 64 * 1024;
+    private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+
+    private final KeyStoreOperation mKeyStoreOperation;
+    private final int mMaxChunkSize;
+
+    private byte[] mBuffered = EMPTY_BYTE_ARRAY;
+    private int mBufferedOffset;
+    private int mBufferedLength;
+
+    public KeyStoreCryptoOperationChunkedStreamer(KeyStoreOperation operation) {
+        this(operation, DEFAULT_MAX_CHUNK_SIZE);
+    }
+
+    public KeyStoreCryptoOperationChunkedStreamer(KeyStoreOperation operation, int maxChunkSize) {
+        mKeyStoreOperation = operation;
+        mMaxChunkSize = maxChunkSize;
+    }
+
+    public byte[] update(byte[] input, int inputOffset, int inputLength) throws KeymasterException {
+        if (inputLength == 0) {
+            // No input provided
+            return EMPTY_BYTE_ARRAY;
+        }
+
+        ByteArrayOutputStream bufferedOutput = null;
+
+        while (inputLength > 0) {
+            byte[] chunk;
+            int inputBytesInChunk;
+            if ((mBufferedLength + inputLength) > mMaxChunkSize) {
+                // Too much input for one chunk -- extract one max-sized chunk and feed it into the
+                // update operation.
+                chunk = new byte[mMaxChunkSize];
+                System.arraycopy(mBuffered, mBufferedOffset, chunk, 0, mBufferedLength);
+                inputBytesInChunk = chunk.length - mBufferedLength;
+                System.arraycopy(input, inputOffset, chunk, mBufferedLength, inputBytesInChunk);
+            } else {
+                // All of available input fits into one chunk.
+                if ((mBufferedLength == 0) && (inputOffset == 0)
+                        && (inputLength == input.length)) {
+                    // Nothing buffered and all of input array needs to be fed into the update
+                    // operation.
+                    chunk = input;
+                    inputBytesInChunk = input.length;
+                } else {
+                    // Need to combine buffered data with input data into one array.
+                    chunk = new byte[mBufferedLength + inputLength];
+                    inputBytesInChunk = inputLength;
+                    System.arraycopy(mBuffered, mBufferedOffset, chunk, 0, mBufferedLength);
+                    System.arraycopy(input, inputOffset, chunk, mBufferedLength, inputLength);
+                }
+            }
+            // Update input array references to reflect that some of its bytes are now in mBuffered.
+            inputOffset += inputBytesInChunk;
+            inputLength -= inputBytesInChunk;
+
+            OperationResult opResult = mKeyStoreOperation.update(chunk);
+            if (opResult == null) {
+                throw new KeyStoreConnectException();
+            } else if (opResult.resultCode != KeyStore.NO_ERROR) {
+                throw KeymasterUtils.getExceptionForKeymasterError(opResult.resultCode);
+            }
+
+            if (opResult.inputConsumed == chunk.length) {
+                // The whole chunk was consumed
+                mBuffered = EMPTY_BYTE_ARRAY;
+                mBufferedOffset = 0;
+                mBufferedLength = 0;
+            } else if (opResult.inputConsumed == 0) {
+                // Nothing was consumed. More input needed.
+                if (inputLength > 0) {
+                    // More input is available, but it wasn't included into the previous chunk
+                    // because the chunk reached its maximum permitted size.
+                    // Shouldn't have happened.
+                    throw new CryptoOperationException("Nothing consumed from max-sized chunk: "
+                            + chunk.length + " bytes");
+                }
+                mBuffered = chunk;
+                mBufferedOffset = 0;
+                mBufferedLength = chunk.length;
+            } else if (opResult.inputConsumed < chunk.length) {
+                // The chunk was consumed only partially -- buffer the rest of the chunk
+                mBuffered = chunk;
+                mBufferedOffset = opResult.inputConsumed;
+                mBufferedLength = chunk.length - opResult.inputConsumed;
+            } else {
+                throw new CryptoOperationException("Consumed more than provided: "
+                        + opResult.inputConsumed + ", provided: " + chunk.length);
+            }
+
+            if ((opResult.output != null) && (opResult.output.length > 0)) {
+                if (inputLength > 0) {
+                    // More output might be produced in this loop -- buffer the current output
+                    if (bufferedOutput == null) {
+                        bufferedOutput = new ByteArrayOutputStream();
+                        try {
+                            bufferedOutput.write(opResult.output);
+                        } catch (IOException e) {
+                            throw new CryptoOperationException("Failed to buffer output", e);
+                        }
+                    }
+                } else {
+                    // No more output will be produced in this loop
+                    if (bufferedOutput == null) {
+                        // No previously buffered output
+                        return opResult.output;
+                    } else {
+                        // There was some previously buffered output
+                        try {
+                            bufferedOutput.write(opResult.output);
+                        } catch (IOException e) {
+                            throw new CryptoOperationException("Failed to buffer output", e);
+                        }
+                        return bufferedOutput.toByteArray();
+                    }
+                }
+            }
+        }
+
+        if (bufferedOutput == null) {
+            // No output produced
+            return EMPTY_BYTE_ARRAY;
+        } else {
+            return bufferedOutput.toByteArray();
+        }
+    }
+
+    public byte[] doFinal(byte[] input, int inputOffset, int inputLength)
+            throws KeymasterException {
+        if (inputLength == 0) {
+            // No input provided -- simplify the rest of the code
+            input = EMPTY_BYTE_ARRAY;
+            inputOffset = 0;
+        }
+
+        byte[] updateOutput = null;
+        if ((mBufferedLength + inputLength) > mMaxChunkSize) {
+            updateOutput = update(input, inputOffset, inputLength);
+            inputOffset += inputLength;
+            inputLength = 0;
+        }
+        // All of available input fits into one chunk.
+
+        byte[] finalChunk;
+        if ((mBufferedLength == 0) && (inputOffset == 0)
+                && (inputLength == input.length)) {
+            // Nothing buffered and all of input array needs to be fed into the finish operation.
+            finalChunk = input;
+        } else {
+            // Need to combine buffered data with input data into one array.
+            finalChunk = new byte[mBufferedLength + inputLength];
+            System.arraycopy(mBuffered, mBufferedOffset, finalChunk, 0, mBufferedLength);
+            System.arraycopy(input, inputOffset, finalChunk, mBufferedLength, inputLength);
+        }
+        mBuffered = EMPTY_BYTE_ARRAY;
+        mBufferedLength = 0;
+        mBufferedOffset = 0;
+
+        OperationResult opResult = mKeyStoreOperation.finish(finalChunk);
+        if (opResult == null) {
+            throw new KeyStoreConnectException();
+        } else if (opResult.resultCode != KeyStore.NO_ERROR) {
+            throw KeymasterUtils.getExceptionForKeymasterError(opResult.resultCode);
+        }
+
+        if (opResult.inputConsumed != finalChunk.length) {
+            throw new CryptoOperationException("Unexpected number of bytes consumed by finish: "
+                    + opResult.inputConsumed + " instead of " + finalChunk.length);
+        }
+
+        // Return the concatenation of the output of update and finish.
+        byte[] result;
+        byte[] finishOutput = opResult.output;
+        if ((updateOutput == null) || (updateOutput.length == 0)) {
+            result = finishOutput;
+        } else if ((finishOutput == null) || (finishOutput.length == 0)) {
+            result = updateOutput;
+        } else {
+            result = new byte[updateOutput.length + finishOutput.length];
+            System.arraycopy(updateOutput, 0, result, 0, updateOutput.length);
+            System.arraycopy(finishOutput, 0, result, updateOutput.length, finishOutput.length);
+        }
+        return (result != null) ? result : EMPTY_BYTE_ARRAY;
+    }
+}
diff --git a/keystore/java/android/security/KeyStoreHmacSpi.java b/keystore/java/android/security/KeyStoreHmacSpi.java
new file mode 100644
index 0000000..3080d7b
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreHmacSpi.java
@@ -0,0 +1,174 @@
+package android.security;
+
+import android.os.IBinder;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keymaster.OperationResult;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.MacSpi;
+
+/**
+ * {@link MacSpi} which provides HMAC implementations backed by Android KeyStore.
+ *
+ * @hide
+ */
+public abstract class KeyStoreHmacSpi extends MacSpi {
+
+    public static class HmacSHA256 extends KeyStoreHmacSpi {
+        public HmacSHA256() {
+            super(KeyStoreKeyConstraints.Digest.SHA256, 256 / 8);
+        }
+    }
+
+    private final KeyStore mKeyStore = KeyStore.getInstance();
+    private final @KeyStoreKeyConstraints.DigestEnum int mDigest;
+    private final int mMacSizeBytes;
+
+    private String mKeyAliasInKeyStore;
+
+    // The fields below are reset by the engineReset operation.
+    private KeyStoreCryptoOperationChunkedStreamer mChunkedStreamer;
+    private IBinder mOperationToken;
+
+    protected KeyStoreHmacSpi(@KeyStoreKeyConstraints.DigestEnum int digest, int macSizeBytes) {
+        mDigest = digest;
+        mMacSizeBytes = macSizeBytes;
+    }
+
+    @Override
+    protected int engineGetMacLength() {
+        return mMacSizeBytes;
+    }
+
+    @Override
+    protected void engineInit(Key key, AlgorithmParameterSpec params) throws InvalidKeyException,
+            InvalidAlgorithmParameterException {
+        if (key == null) {
+            throw new InvalidKeyException("key == null");
+        } else if (!(key instanceof KeyStoreSecretKey)) {
+            throw new InvalidKeyException(
+                    "Only Android KeyStore secret keys supported. Key: " + key);
+        }
+
+        if (params != null) {
+            throw new InvalidAlgorithmParameterException(
+                    "Unsupported algorithm parameters: " + params);
+        }
+
+        mKeyAliasInKeyStore = ((KeyStoreSecretKey) key).getAlias();
+        engineReset();
+    }
+
+    @Override
+    protected void engineReset() {
+        IBinder operationToken = mOperationToken;
+        if (operationToken != null) {
+            mOperationToken = null;
+            mKeyStore.abort(operationToken);
+        }
+        mChunkedStreamer = null;
+
+        KeymasterArguments keymasterArgs = new KeymasterArguments();
+        keymasterArgs.addInt(KeymasterDefs.KM_TAG_DIGEST, mDigest);
+
+        OperationResult opResult = mKeyStore.begin(mKeyAliasInKeyStore,
+                KeymasterDefs.KM_PURPOSE_SIGN,
+                true,
+                keymasterArgs,
+                null,
+                new KeymasterArguments());
+        if (opResult == null) {
+            throw new KeyStoreConnectException();
+        } else if (opResult.resultCode != KeyStore.NO_ERROR) {
+            throw new CryptoOperationException("Failed to start keystore operation",
+                    KeymasterUtils.getExceptionForKeymasterError(opResult.resultCode));
+        }
+        mOperationToken = opResult.token;
+        if (mOperationToken == null) {
+            throw new CryptoOperationException("Keystore returned null operation token");
+        }
+        mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer(
+                new KeyStoreStreamingConsumer(mKeyStore, mOperationToken));
+    }
+
+    @Override
+    protected void engineUpdate(byte input) {
+        engineUpdate(new byte[] {input}, 0, 1);
+    }
+
+    @Override
+    protected void engineUpdate(byte[] input, int offset, int len) {
+        if (mChunkedStreamer == null) {
+            throw new IllegalStateException("Not initialized");
+        }
+
+        byte[] output;
+        try {
+            output = mChunkedStreamer.update(input, offset, len);
+        } catch (KeymasterException e) {
+            throw new CryptoOperationException("Keystore operation failed", e);
+        }
+        if ((output != null) && (output.length != 0)) {
+            throw new CryptoOperationException("Update operation unexpectedly produced output");
+        }
+    }
+
+    @Override
+    protected byte[] engineDoFinal() {
+        if (mChunkedStreamer == null) {
+            throw new IllegalStateException("Not initialized");
+        }
+
+        byte[] result;
+        try {
+            result = mChunkedStreamer.doFinal(null, 0, 0);
+        } catch (KeymasterException e) {
+            throw new CryptoOperationException("Keystore operation failed", e);
+        }
+
+        engineReset();
+        return result;
+    }
+
+    @Override
+    public void finalize() throws Throwable {
+        try {
+            IBinder operationToken = mOperationToken;
+            if (operationToken != null) {
+                mOperationToken = null;
+                mKeyStore.abort(operationToken);
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * KeyStore-backed consumer of {@code MacSpi}'s chunked stream.
+     */
+    private static class KeyStoreStreamingConsumer
+            implements KeyStoreCryptoOperationChunkedStreamer.KeyStoreOperation {
+        private final KeyStore mKeyStore;
+        private final IBinder mOperationToken;
+
+        private KeyStoreStreamingConsumer(KeyStore keyStore, IBinder operationToken) {
+            mKeyStore = keyStore;
+            mOperationToken = operationToken;
+        }
+
+        @Override
+        public OperationResult update(byte[] input) {
+            return mKeyStore.update(mOperationToken, null, input);
+        }
+
+        @Override
+        public OperationResult finish(byte[] input) {
+            return mKeyStore.finish(mOperationToken, null, input);
+        }
+    }
+}
diff --git a/keystore/java/android/security/KeyStoreKeyConstraints.java b/keystore/java/android/security/KeyStoreKeyConstraints.java
index 47bb1cc..b5e2436 100644
--- a/keystore/java/android/security/KeyStoreKeyConstraints.java
+++ b/keystore/java/android/security/KeyStoreKeyConstraints.java
@@ -401,6 +401,20 @@
                     throw new IllegalArgumentException("Unknown digest: " + digest);
             }
         }
+
+        /**
+         * @hide
+         */
+        public static Integer getOutputSizeBytes(@DigestEnum int digest) {
+            switch (digest) {
+                case NONE:
+                    return null;
+                case SHA256:
+                    return 256 / 8;
+                default:
+                    throw new IllegalArgumentException("Unknown digest: " + digest);
+            }
+        }
     }
 
     @Retention(RetentionPolicy.SOURCE)
diff --git a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
index 86950dd..f1f9436 100644
--- a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
@@ -28,7 +28,8 @@
         public HmacSHA256() {
             super(KeyStoreKeyConstraints.Algorithm.HMAC,
                     KeyStoreKeyConstraints.Digest.SHA256,
-                    256);
+                    KeyStoreKeyConstraints.Digest.getOutputSizeBytes(
+                            KeyStoreKeyConstraints.Digest.SHA256) * 8);
         }
     }
 
@@ -76,6 +77,19 @@
             args.addInt(KeymasterDefs.KM_TAG_DIGEST,
                     KeyStoreKeyConstraints.Digest.toKeymaster(mDigest));
         }
+        if (mAlgorithm == KeyStoreKeyConstraints.Algorithm.HMAC) {
+            if (mDigest == null) {
+                throw new IllegalStateException("Digest algorithm must be specified for key"
+                        + " algorithm " + KeyStoreKeyConstraints.Algorithm.toString(mAlgorithm));
+            }
+            Integer digestOutputSizeBytes =
+                    KeyStoreKeyConstraints.Digest.getOutputSizeBytes(mDigest);
+            if (digestOutputSizeBytes != null) {
+                // TODO: Remove MAC length constraint once Keymaster API no longer requires it.
+                // TODO: Switch to bits instead of bytes, once this is fixed in Keymaster
+                args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, digestOutputSizeBytes);
+            }
+        }
         int keySizeBits = (spec.getKeySize() != null) ? spec.getKeySize() : mDefaultKeySizeBits;
         args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keySizeBits);
         @KeyStoreKeyConstraints.PurposeEnum int purposes = (spec.getPurposes() != null)
diff --git a/keystore/java/android/security/KeymasterException.java b/keystore/java/android/security/KeymasterException.java
index 4ff7115..bc8198f 100644
--- a/keystore/java/android/security/KeymasterException.java
+++ b/keystore/java/android/security/KeymasterException.java
@@ -7,7 +7,14 @@
  */
 public class KeymasterException extends Exception {
 
-    public KeymasterException(String message) {
+    private final int mErrorCode;
+
+    public KeymasterException(int errorCode, String message) {
         super(message);
+        mErrorCode = errorCode;
+    }
+
+    public int getErrorCode() {
+        return mErrorCode;
     }
 }
diff --git a/keystore/java/android/security/KeymasterUtils.java b/keystore/java/android/security/KeymasterUtils.java
index e6e88c7..4f17586 100644
--- a/keystore/java/android/security/KeymasterUtils.java
+++ b/keystore/java/android/security/KeymasterUtils.java
@@ -13,9 +13,11 @@
             case KeymasterDefs.KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT:
                 // The name of this parameter significantly differs between Keymaster and framework
                 // APIs. Use the framework wording to make life easier for developers.
-                return new KeymasterException("Invalid user authentication validity duration");
+                return new KeymasterException(keymasterErrorCode,
+                        "Invalid user authentication validity duration");
             default:
-                return new KeymasterException(KeymasterDefs.getErrorMessage(keymasterErrorCode));
+                return new KeymasterException(keymasterErrorCode,
+                        KeymasterDefs.getErrorMessage(keymasterErrorCode));
         }
     }
 }
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 7df61f27..48f5dc1 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -16,8 +16,12 @@
 #include "JankTracker.h"
 
 #include <algorithm>
+#include <cutils/ashmem.h>
+#include <cutils/log.h>
 #include <cstdio>
+#include <errno.h>
 #include <inttypes.h>
+#include <sys/mman.h>
 
 namespace android {
 namespace uirenderer {
@@ -63,11 +67,114 @@
         = FrameInfoFlags::kWindowLayoutChanged
         | FrameInfoFlags::kSurfaceCanvas;
 
+// The bucketing algorithm controls so to speak
+// If a frame is <= to this it goes in bucket 0
+static const uint32_t kBucketMinThreshold = 7;
+// If a frame is > this, start counting in increments of 2ms
+static const uint32_t kBucket2msIntervals = 32;
+// If a frame is > this, start counting in increments of 4ms
+static const uint32_t kBucket4msIntervals = 48;
+
+// This will be called every frame, performance sensitive
+// Uses bit twiddling to avoid branching while achieving the packing desired
+static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime, uint32_t max) {
+    uint32_t index = static_cast<uint32_t>(ns2ms(frameTime));
+    // If index > kBucketMinThreshold mask will be 0xFFFFFFFF as a result
+    // of negating 1 (twos compliment, yaay) else mask will be 0
+    uint32_t mask = -(index > kBucketMinThreshold);
+    // If index > threshold, this will essentially perform:
+    // amountAboveThreshold = index - threshold;
+    // index = threshold + (amountAboveThreshold / 2)
+    // However if index is <= this will do nothing. It will underflow, do
+    // a right shift by 0 (no-op), then overflow back to the original value
+    index = ((index - kBucket4msIntervals) >> (index > kBucket4msIntervals))
+            + kBucket4msIntervals;
+    index = ((index - kBucket2msIntervals) >> (index > kBucket2msIntervals))
+            + kBucket2msIntervals;
+    // If index was < minThreshold at the start of all this it's going to
+    // be a pretty garbage value right now. However, mask is 0 so we'll end
+    // up with the desired result of 0.
+    index = (index - kBucketMinThreshold) & mask;
+    return index < max ? index : max;
+}
+
+// Only called when dumping stats, less performance sensitive
+static uint32_t frameTimeForFrameCountIndex(uint32_t index) {
+    index = index + kBucketMinThreshold;
+    if (index > kBucket2msIntervals) {
+        index += (index - kBucket2msIntervals);
+    }
+    if (index > kBucket4msIntervals) {
+        // This works because it was already doubled by the above if
+        // 1 is added to shift slightly more towards the middle of the bucket
+        index += (index - kBucket4msIntervals) + 1;
+    }
+    return index;
+}
+
 JankTracker::JankTracker(nsecs_t frameIntervalNanos) {
+    // By default this will use malloc memory. It may be moved later to ashmem
+    // if there is shared space for it and a request comes in to do that.
+    mData = new ProfileData;
     reset();
     setFrameInterval(frameIntervalNanos);
 }
 
+JankTracker::~JankTracker() {
+    freeData();
+}
+
+void JankTracker::freeData() {
+    if (mIsMapped) {
+        munmap(mData, sizeof(ProfileData));
+    } else {
+        delete mData;
+    }
+    mIsMapped = false;
+    mData = nullptr;
+}
+
+void JankTracker::switchStorageToAshmem(int ashmemfd) {
+    int regionSize = ashmem_get_size_region(ashmemfd);
+    if (regionSize < static_cast<int>(sizeof(ProfileData))) {
+        ALOGW("Ashmem region is too small! Received %d, required %u",
+                regionSize, sizeof(ProfileData));
+        return;
+    }
+    ProfileData* newData = reinterpret_cast<ProfileData*>(
+            mmap(NULL, sizeof(ProfileData), PROT_READ | PROT_WRITE,
+            MAP_SHARED, ashmemfd, 0));
+    if (newData == MAP_FAILED) {
+        int err = errno;
+        ALOGW("Failed to move profile data to ashmem fd %d, error = %d",
+                ashmemfd, err);
+        return;
+    }
+
+    // The new buffer may have historical data that we want to build on top of
+    // But let's make sure we don't overflow Just In Case
+    uint32_t divider = 0;
+    if (newData->totalFrameCount > (1 << 24)) {
+        divider = 4;
+    }
+    for (size_t i = 0; i < mData->jankTypeCounts.size(); i++) {
+        newData->jankTypeCounts[i] >>= divider;
+        newData->jankTypeCounts[i] += mData->jankTypeCounts[i];
+    }
+    for (size_t i = 0; i < mData->frameCounts.size(); i++) {
+        newData->frameCounts[i] >>= divider;
+        newData->frameCounts[i] += mData->frameCounts[i];
+    }
+    newData->jankFrameCount >>= divider;
+    newData->jankFrameCount += mData->jankFrameCount;
+    newData->totalFrameCount >>= divider;
+    newData->totalFrameCount += mData->totalFrameCount;
+
+    freeData();
+    mData = newData;
+    mIsMapped = true;
+}
+
 void JankTracker::setFrameInterval(nsecs_t frameInterval) {
     mFrameInterval = frameInterval;
     mThresholds[kMissedVsync] = 1;
@@ -92,16 +199,15 @@
 }
 
 void JankTracker::addFrame(const FrameInfo& frame) {
-    mTotalFrameCount++;
+    mData->totalFrameCount++;
     // Fast-path for jank-free frames
     int64_t totalDuration =
             frame[FrameInfoIndex::kFrameCompleted] - frame[FrameInfoIndex::kIntendedVsync];
-    uint32_t framebucket = std::min(
-            static_cast<typeof mFrameCounts.size()>(ns2ms(totalDuration)),
-            mFrameCounts.size());
+    uint32_t framebucket = frameCountIndexForFrameTime(
+            totalDuration, mData->frameCounts.size());
     // Keep the fast path as fast as possible.
     if (CC_LIKELY(totalDuration < mFrameInterval)) {
-        mFrameCounts[framebucket]++;
+        mData->frameCounts[framebucket]++;
         return;
     }
 
@@ -109,47 +215,52 @@
         return;
     }
 
-    mFrameCounts[framebucket]++;
-    mJankFrameCount++;
+    mData->frameCounts[framebucket]++;
+    mData->jankFrameCount++;
 
     for (int i = 0; i < NUM_BUCKETS; i++) {
         int64_t delta = frame[COMPARISONS[i].end] - frame[COMPARISONS[i].start];
         if (delta >= mThresholds[i] && delta < IGNORE_EXCEEDING) {
-            mBuckets[i].count++;
+            mData->jankTypeCounts[i]++;
         }
     }
 }
 
-void JankTracker::dump(int fd) {
-    FILE* file = fdopen(fd, "a");
-    fprintf(file, "\nFrame stats:");
-    fprintf(file, "\n  Total frames rendered: %u", mTotalFrameCount);
-    fprintf(file, "\n  Janky frames: %u (%.2f%%)", mJankFrameCount,
-            (float) mJankFrameCount / (float) mTotalFrameCount * 100.0f);
-    fprintf(file, "\n  90th percentile: %ums", findPercentile(90));
-    fprintf(file, "\n  95th percentile: %ums", findPercentile(95));
-    fprintf(file, "\n  99th percentile: %ums", findPercentile(99));
-    for (int i = 0; i < NUM_BUCKETS; i++) {
-        fprintf(file, "\n   Number %s: %u", JANK_TYPE_NAMES[i], mBuckets[i].count);
+void JankTracker::dumpBuffer(const void* buffer, size_t bufsize, int fd) {
+    if (bufsize < sizeof(ProfileData)) {
+        return;
     }
-    fprintf(file, "\n");
-    fflush(file);
+    const ProfileData* data = reinterpret_cast<const ProfileData*>(buffer);
+    dumpData(data, fd);
+}
+
+void JankTracker::dumpData(const ProfileData* data, int fd) {
+    dprintf(fd, "\nTotal frames rendered: %u", data->totalFrameCount);
+    dprintf(fd, "\nJanky frames: %u (%.2f%%)", data->jankFrameCount,
+            (float) data->jankFrameCount / (float) data->totalFrameCount * 100.0f);
+    dprintf(fd, "\n90th percentile: %ums", findPercentile(data, 90));
+    dprintf(fd, "\n95th percentile: %ums", findPercentile(data, 95));
+    dprintf(fd, "\n99th percentile: %ums", findPercentile(data, 99));
+    for (int i = 0; i < NUM_BUCKETS; i++) {
+        dprintf(fd, "\nNumber %s: %u", JANK_TYPE_NAMES[i], data->jankTypeCounts[i]);
+    }
+    dprintf(fd, "\n");
 }
 
 void JankTracker::reset() {
-    mBuckets.fill({0});
-    mFrameCounts.fill(0);
-    mTotalFrameCount = 0;
-    mJankFrameCount = 0;
+    mData->jankTypeCounts.fill(0);
+    mData->frameCounts.fill(0);
+    mData->totalFrameCount = 0;
+    mData->jankFrameCount = 0;
 }
 
-uint32_t JankTracker::findPercentile(int percentile) {
-    int pos = percentile * mTotalFrameCount / 100;
-    int remaining = mTotalFrameCount - pos;
-    for (int i = mFrameCounts.size() - 1; i >= 0; i--) {
-        remaining -= mFrameCounts[i];
+uint32_t JankTracker::findPercentile(const ProfileData* data, int percentile) {
+    int pos = percentile * data->totalFrameCount / 100;
+    int remaining = data->totalFrameCount - pos;
+    for (int i = data->frameCounts.size() - 1; i >= 0; i--) {
+        remaining -= data->frameCounts[i];
         if (remaining <= 0) {
-            return i;
+            return frameTimeForFrameCountIndex(i);
         }
     }
     return 0;
diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h
index ae339ec..4783001 100644
--- a/libs/hwui/JankTracker.h
+++ b/libs/hwui/JankTracker.h
@@ -20,6 +20,8 @@
 #include "renderthread/TimeLord.h"
 #include "utils/RingBuffer.h"
 
+#include <cutils/compiler.h>
+
 #include <array>
 #include <memory>
 
@@ -37,33 +39,45 @@
     NUM_BUCKETS,
 };
 
-struct JankBucket {
-    // Number of frames that hit this bucket
-    uint32_t count;
+// Try to keep as small as possible, should match ASHMEM_SIZE in
+// GraphicsStatsService.java
+struct ProfileData {
+    std::array<uint32_t, NUM_BUCKETS> jankTypeCounts;
+    // See comments on kBucket* constants for what this holds
+    std::array<uint32_t, 57> frameCounts;
+
+    uint32_t totalFrameCount;
+    uint32_t jankFrameCount;
 };
 
 // TODO: Replace DrawProfiler with this
 class JankTracker {
 public:
     JankTracker(nsecs_t frameIntervalNanos);
-
-    void setFrameInterval(nsecs_t frameIntervalNanos);
+    ~JankTracker();
 
     void addFrame(const FrameInfo& frame);
 
-    void dump(int fd);
+    void dump(int fd) { dumpData(mData, fd); }
     void reset();
 
+    void switchStorageToAshmem(int ashmemfd);
+
+    uint32_t findPercentile(int p) { return findPercentile(mData, p); }
+
+    ANDROID_API static void dumpBuffer(const void* buffer, size_t bufsize, int fd);
+
 private:
-    uint32_t findPercentile(int p);
+    void freeData();
+    void setFrameInterval(nsecs_t frameIntervalNanos);
 
-    std::array<JankBucket, NUM_BUCKETS> mBuckets;
+    static uint32_t findPercentile(const ProfileData* data, int p);
+    static void dumpData(const ProfileData* data, int fd);
+
     std::array<int64_t, NUM_BUCKETS> mThresholds;
-    std::array<uint32_t, 128> mFrameCounts;
-
     int64_t mFrameInterval;
-    uint32_t mTotalFrameCount;
-    uint32_t mJankFrameCount;
+    ProfileData* mData;
+    bool mIsMapped = false;
 };
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 9456073..9237151 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -41,15 +41,10 @@
         RenderNode* rootRenderNode, IContextFactory* contextFactory)
         : mRenderThread(thread)
         , mEglManager(thread.eglManager())
-        , mEglSurface(EGL_NO_SURFACE)
-        , mBufferPreserved(false)
-        , mSwapBehavior(kSwap_default)
         , mOpaque(!translucent)
-        , mCanvas(nullptr)
-        , mHaveNewSurface(false)
         , mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord()))
         , mRootRenderNode(rootRenderNode)
-        , mCurrentFrameInfo(nullptr) {
+        , mJankTracker(thread.timeLord().frameIntervalNanos()) {
     mRenderThread.renderState().registerCanvasContext(this);
     mProfiler.setDensity(mRenderThread.mainDisplayInfo().density);
 }
@@ -258,6 +253,7 @@
 
     // TODO: Use a fence for real completion?
     mCurrentFrameInfo->markFrameCompleted();
+    mJankTracker.addFrame(*mCurrentFrameInfo);
     mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo);
     profiler().finishFrame();
 }
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index c3904c2..f5f1f54 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -127,23 +127,24 @@
     RenderThread& mRenderThread;
     EglManager& mEglManager;
     sp<ANativeWindow> mNativeWindow;
-    EGLSurface mEglSurface;
-    bool mBufferPreserved;
-    SwapBehavior mSwapBehavior;
+    EGLSurface mEglSurface = EGL_NO_SURFACE;
+    bool mBufferPreserved = false;
+    SwapBehavior mSwapBehavior = kSwap_default;
 
     bool mOpaque;
-    OpenGLRenderer* mCanvas;
-    bool mHaveNewSurface;
+    OpenGLRenderer* mCanvas = nullptr;
+    bool mHaveNewSurface = false;
     DamageAccumulator mDamageAccumulator;
     std::unique_ptr<AnimationContext> mAnimationContext;
 
     const sp<RenderNode> mRootRenderNode;
 
     DrawProfiler mProfiler;
-    FrameInfo* mCurrentFrameInfo;
+    FrameInfo* mCurrentFrameInfo = nullptr;
     // Ring buffer large enough for 1 second worth of frames
     RingBuffer<FrameInfo, 60> mFrames;
     std::string mName;
+    JankTracker mJankTracker;
 
     std::set<RenderNode*> mPrefetechedLayers;
 };
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 0091790..cc87241 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -441,6 +441,19 @@
     post(task);
 }
 
+CREATE_BRIDGE2(setProcessStatsBuffer, RenderThread* thread, int fd) {
+    args->thread->jankTracker().switchStorageToAshmem(args->fd);
+    close(args->fd);
+    return nullptr;
+}
+
+void RenderProxy::setProcessStatsBuffer(int fd) {
+    SETUP_TASK(setProcessStatsBuffer);
+    args->thread = &mRenderThread;
+    args->fd = dup(fd);
+    post(task);
+}
+
 void RenderProxy::post(RenderTask* task) {
     mRenderThread.queue(task);
 }
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 43cbe07..29c6f08 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -99,6 +99,7 @@
     ANDROID_API static void dumpGraphicsMemory(int fd);
 
     ANDROID_API void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t size);
+    ANDROID_API void setProcessStatsBuffer(int fd);
 
 private:
     RenderThread& mRenderThread;
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index d77fcd8..83954ae 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -2602,15 +2602,21 @@
                 return;
 
             case MEDIA_STOPPED:
-                if (mTimeProvider != null) {
-                    mTimeProvider.onStopped();
+                {
+                    TimeProvider timeProvider = mTimeProvider;
+                    if (timeProvider != null) {
+                        timeProvider.onStopped();
+                    }
                 }
                 break;
 
             case MEDIA_STARTED:
             case MEDIA_PAUSED:
-                if (mTimeProvider != null) {
-                    mTimeProvider.onPaused(msg.what == MEDIA_PAUSED);
+                {
+                    TimeProvider timeProvider = mTimeProvider;
+                    if (timeProvider != null) {
+                        timeProvider.onPaused(msg.what == MEDIA_PAUSED);
+                    }
                 }
                 break;
 
@@ -2620,21 +2626,26 @@
                 return;
 
             case MEDIA_SEEK_COMPLETE:
-              if (mOnSeekCompleteListener != null) {
-                  mOnSeekCompleteListener.onSeekComplete(mMediaPlayer);
-              }
-              // fall through
+                if (mOnSeekCompleteListener != null) {
+                    mOnSeekCompleteListener.onSeekComplete(mMediaPlayer);
+                }
+                // fall through
 
             case MEDIA_SKIPPED:
-              if (mTimeProvider != null) {
-                  mTimeProvider.onSeekComplete(mMediaPlayer);
-              }
-              return;
+                {
+                    TimeProvider timeProvider = mTimeProvider;
+                    if (timeProvider != null) {
+                        timeProvider.onSeekComplete(mMediaPlayer);
+                    }
+                }
+                return;
 
             case MEDIA_SET_VIDEO_SIZE:
-              if (mOnVideoSizeChangedListener != null)
-                  mOnVideoSizeChangedListener.onVideoSizeChanged(mMediaPlayer, msg.arg1, msg.arg2);
-              return;
+                if (mOnVideoSizeChangedListener != null) {
+                    mOnVideoSizeChangedListener.onVideoSizeChanged(
+                        mMediaPlayer, msg.arg1, msg.arg2);
+                }
+                return;
 
             case MEDIA_ERROR:
                 Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")");
diff --git a/packages/SystemUI/res/layout-sw600dp/recents_task_resize_dialog.xml b/packages/SystemUI/res/layout-sw600dp-land/recents_task_resize_dialog.xml
similarity index 84%
rename from packages/SystemUI/res/layout-sw600dp/recents_task_resize_dialog.xml
rename to packages/SystemUI/res/layout-sw600dp-land/recents_task_resize_dialog.xml
index 29e4bce..26c9b1a 100644
--- a/packages/SystemUI/res/layout-sw600dp/recents_task_resize_dialog.xml
+++ b/packages/SystemUI/res/layout-sw600dp-land/recents_task_resize_dialog.xml
@@ -41,20 +41,6 @@
             android:layout_margin="10dp"
             android:background="@drawable/vector_drawable_place_right" />
         <Button
-            android:id="@+id/place_top"
-            android:layout_width="36dp"
-            android:layout_height="36dp"
-            android:layout_weight="1"
-            android:layout_margin="10dp"
-            android:background="@drawable/vector_drawable_place_top" />
-        <Button
-            android:id="@+id/place_bottom"
-            android:layout_width="36dp"
-            android:layout_height="36dp"
-            android:layout_weight="1"
-            android:layout_margin="10dp"
-            android:background="@drawable/vector_drawable_place_bottom" />
-        <Button
             android:id="@+id/place_top_left"
             android:layout_width="36dp"
             android:layout_height="36dp"
diff --git a/packages/SystemUI/res/layout-sw600dp/recents_task_resize_dialog.xml b/packages/SystemUI/res/layout-sw600dp-port/recents_task_resize_dialog.xml
similarity index 84%
copy from packages/SystemUI/res/layout-sw600dp/recents_task_resize_dialog.xml
copy to packages/SystemUI/res/layout-sw600dp-port/recents_task_resize_dialog.xml
index 29e4bce..e180daa 100644
--- a/packages/SystemUI/res/layout-sw600dp/recents_task_resize_dialog.xml
+++ b/packages/SystemUI/res/layout-sw600dp-port/recents_task_resize_dialog.xml
@@ -27,20 +27,6 @@
         android:layout_height="wrap_content"
         android:orientation="horizontal">
         <Button
-            android:id="@+id/place_left"
-            android:layout_width="36dp"
-            android:layout_height="36dp"
-            android:layout_weight="1"
-            android:layout_margin="10dp"
-            android:background="@drawable/vector_drawable_place_left" />
-        <Button
-            android:id="@+id/place_right"
-            android:layout_width="36dp"
-            android:layout_height="36dp"
-            android:layout_weight="1"
-            android:layout_margin="10dp"
-            android:background="@drawable/vector_drawable_place_right" />
-        <Button
             android:id="@+id/place_top"
             android:layout_width="36dp"
             android:layout_height="36dp"
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index e81a1d0..3a97a41 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -815,7 +815,9 @@
         }
 
         // Start dozing
-        mUIDozeTrigger.startDozing();
+        if (!mConfig.multiStackEnabled) {
+            mUIDozeTrigger.startDozing();
+        }
     }
 
     /** Requests this task stacks to start it's enter-recents animation */
@@ -1219,7 +1221,7 @@
         RecentsTaskLoader.getInstance().loadTaskData(task);
 
         // If the doze trigger has already fired, then update the state for this task view
-        if (mUIDozeTrigger.hasTriggered()) {
+        if (mConfig.multiStackEnabled || mUIDozeTrigger.hasTriggered()) {
             tv.setNoUserInteractionState();
         }
 
diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java
new file mode 100644
index 0000000..c79fdfc
--- /dev/null
+++ b/services/core/java/com/android/server/GraphicsStatsService.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2015 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.server;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.MemoryFile;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.IGraphicsStats;
+import android.view.ThreadedRenderer;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * This service's job is to collect aggregate rendering profile data. It
+ * does this by allowing rendering processes to request an ashmem buffer
+ * to place their stats into. This buffer will be pre-initialized with historical
+ * data for that process if it exists (if the userId & packageName match a buffer
+ * in the historical log)
+ *
+ * This service does not itself attempt to understand the data in the buffer,
+ * its primary job is merely to manage distributing these buffers. However,
+ * it is assumed that this buffer is for ThreadedRenderer and delegates
+ * directly to ThreadedRenderer for dumping buffers.
+ *
+ * MEMORY USAGE:
+ *
+ * This class consumes UP TO:
+ * 1) [active rendering processes] * (ASHMEM_SIZE * 2)
+ * 2) ASHMEM_SIZE (for scratch space used during dumping)
+ * 3) ASHMEM_SIZE * HISTORY_SIZE
+ *
+ * Currently ASHMEM_SIZE is 256 bytes and HISTORY_SIZE is 10. Assuming
+ * the system then also has 10 active rendering processes in the worst case
+ * this would end up using under 10KiB (8KiB for the buffers, plus some overhead
+ * for userId, pid, package name, and a couple other objects)
+ *
+ *  @hide */
+public class GraphicsStatsService extends IGraphicsStats.Stub {
+    public static final String GRAPHICS_STATS_SERVICE = "graphicsstats";
+
+    private static final String TAG = "GraphicsStatsService";
+    private static final int ASHMEM_SIZE = 256;
+    private static final int HISTORY_SIZE = 10;
+
+    private final Context mContext;
+    private final Object mLock = new Object();
+    private ArrayList<ActiveBuffer> mActive = new ArrayList<>();
+    private HistoricalData[] mHistoricalLog = new HistoricalData[HISTORY_SIZE];
+    private int mNextHistoricalSlot = 0;
+    private byte[] mTempBuffer = new byte[ASHMEM_SIZE];
+
+    public GraphicsStatsService(Context context) {
+        mContext = context;
+    }
+
+    private boolean isValid(int uid, String packageName) {
+        try {
+            PackageInfo info = mContext.getPackageManager().getPackageInfo(packageName, 0);
+            return info.applicationInfo.uid == uid;
+        } catch (NameNotFoundException e) {
+        }
+        return false;
+    }
+
+    @Override
+    public ParcelFileDescriptor requestBufferForProcess(String packageName, IBinder token)
+            throws RemoteException {
+        int uid = Binder.getCallingUid();
+        int pid = Binder.getCallingPid();
+        ParcelFileDescriptor pfd = null;
+        long callingIdentity = Binder.clearCallingIdentity();
+        try {
+            if (!isValid(uid, packageName)) {
+                throw new RemoteException("Invalid package name");
+            }
+            synchronized (mLock) {
+                pfd = requestBufferForProcessLocked(token, uid, pid, packageName);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
+        return pfd;
+    }
+
+    private ParcelFileDescriptor getPfd(MemoryFile file) {
+        try {
+            return new ParcelFileDescriptor(file.getFileDescriptor());
+        } catch (IOException ex) {
+            throw new IllegalStateException("Failed to get PFD from memory file", ex);
+        }
+    }
+
+    private ParcelFileDescriptor requestBufferForProcessLocked(IBinder token,
+            int uid, int pid, String packageName) throws RemoteException {
+        ActiveBuffer buffer = fetchActiveBuffersLocked(token, uid, pid, packageName);
+        return getPfd(buffer.mProcessBuffer);
+    }
+
+    private void processDied(ActiveBuffer buffer) {
+        synchronized (mLock) {
+            mActive.remove(buffer);
+            Log.d("GraphicsStats", "Buffer count: " + mActive.size());
+        }
+        HistoricalData data = buffer.mPreviousData;
+        buffer.mPreviousData = null;
+        if (data == null) {
+            data = mHistoricalLog[mNextHistoricalSlot];
+            if (data == null) {
+                data = new HistoricalData();
+            }
+        }
+        data.update(buffer.mPackageName, buffer.mUid, buffer.mProcessBuffer);
+        buffer.closeAllBuffers();
+
+        mHistoricalLog[mNextHistoricalSlot] = data;
+        mNextHistoricalSlot = (mNextHistoricalSlot + 1) % mHistoricalLog.length;
+    }
+
+    private ActiveBuffer fetchActiveBuffersLocked(IBinder token, int uid, int pid,
+            String packageName) throws RemoteException {
+        int size = mActive.size();
+        for (int i = 0; i < size; i++) {
+            ActiveBuffer buffers = mActive.get(i);
+            if (buffers.mPid == pid
+                    && buffers.mUid == uid) {
+                return buffers;
+            }
+        }
+        // Didn't find one, need to create it
+        try {
+            ActiveBuffer buffers = new ActiveBuffer(token, uid, pid, packageName);
+            mActive.add(buffers);
+            return buffers;
+        } catch (IOException ex) {
+            throw new RemoteException("Failed to allocate space");
+        }
+    }
+
+    private HistoricalData removeHistoricalDataLocked(int uid, String packageName) {
+        for (int i = 0; i < mHistoricalLog.length; i++) {
+            final HistoricalData data = mHistoricalLog[i];
+            if (data != null && data.mUid == uid
+                    && data.mPackageName.equals(packageName)) {
+                if (i == mNextHistoricalSlot) {
+                    mHistoricalLog[i] = null;
+                } else {
+                    mHistoricalLog[i] = mHistoricalLog[mNextHistoricalSlot];
+                    mHistoricalLog[mNextHistoricalSlot] = null;
+                }
+                return data;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+        synchronized (mLock) {
+            for (int i = 0; i < mActive.size(); i++) {
+                final ActiveBuffer buffer = mActive.get(i);
+                fout.print("Package: ");
+                fout.print(buffer.mPackageName);
+                fout.flush();
+                try {
+                    buffer.mProcessBuffer.readBytes(mTempBuffer, 0, 0, ASHMEM_SIZE);
+                    ThreadedRenderer.dumpProfileData(mTempBuffer, fd);
+                } catch (IOException e) {
+                    fout.println("Failed to dump");
+                }
+                fout.println();
+            }
+            for (HistoricalData buffer : mHistoricalLog) {
+                if (buffer == null) continue;
+                fout.print("Package: ");
+                fout.print(buffer.mPackageName);
+                fout.flush();
+                ThreadedRenderer.dumpProfileData(buffer.mBuffer, fd);
+                fout.println();
+            }
+        }
+    }
+
+    private final class ActiveBuffer implements DeathRecipient {
+        final int mUid;
+        final int mPid;
+        final String mPackageName;
+        final IBinder mToken;
+        MemoryFile mProcessBuffer;
+        HistoricalData mPreviousData;
+
+        ActiveBuffer(IBinder token, int uid, int pid, String packageName)
+                throws RemoteException, IOException {
+            mUid = uid;
+            mPid = pid;
+            mPackageName = packageName;
+            mToken = token;
+            mToken.linkToDeath(this, 0);
+            mProcessBuffer = new MemoryFile("GFXStats-" + uid, ASHMEM_SIZE);
+            mPreviousData = removeHistoricalDataLocked(mUid, mPackageName);
+            if (mPreviousData != null) {
+                mProcessBuffer.writeBytes(mPreviousData.mBuffer, 0, 0, ASHMEM_SIZE);
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            mToken.unlinkToDeath(this, 0);
+            processDied(this);
+        }
+
+        void closeAllBuffers() {
+            if (mProcessBuffer != null) {
+                mProcessBuffer.close();
+                mProcessBuffer = null;
+            }
+        }
+    }
+
+    private final static class HistoricalData {
+        final byte[] mBuffer = new byte[ASHMEM_SIZE];
+        int mUid;
+        String mPackageName;
+
+        void update(String packageName, int uid, MemoryFile file) {
+            mUid = uid;
+            mPackageName = packageName;
+            try {
+                file.readBytes(mBuffer, 0, 0, ASHMEM_SIZE);
+            } catch (IOException e) {}
+        }
+    }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 8c653e2..090c0f8 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -912,6 +912,11 @@
                 }
             }
 
+            if (!disableNonCoreServices) {
+                ServiceManager.addService(GraphicsStatsService.GRAPHICS_STATS_SERVICE,
+                        new GraphicsStatsService(context));
+            }
+
             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PRINTING)) {
                 mSystemServiceManager.startService(PRINT_MANAGER_SERVICE_CLASS);
             }