Merge "Fix up DisconnectCause description."
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index 31e6e25..0cf8df1 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -65,6 +65,12 @@
 public abstract class CameraCaptureSession implements AutoCloseable {
 
     /**
+     * Used to identify invalid session ID.
+     * @hide
+     */
+    public static final int SESSION_ID_NONE = -1;
+
+    /**
      * Get the camera device that this session is created for.
      */
     public abstract CameraDevice getDevice();
@@ -168,10 +174,11 @@
      * @throws IllegalArgumentException if the request targets no Surfaces or Surfaces that are not
      *                                  configured as outputs for this session; or a reprocess
      *                                  capture request is submitted in a non-reprocessible capture
-     *                                  session; or the capture targets a Surface in the middle
-     *                                  of being {@link #prepare prepared}; or the handler is
-     *                                  null, the listener is not null, and the calling thread has
-     *                                  no looper.
+     *                                  session; or the reprocess capture request was created with
+     *                                  a {@link TotalCaptureResult} from a different session; or
+     *                                  the capture targets a Surface in the middle of being
+     *                                  {@link #prepare prepared}; or the handler is null, the
+     *                                  listener is not null, and the calling thread has no looper.
      *
      * @see #captureBurst
      * @see #setRepeatingRequest
@@ -226,7 +233,9 @@
      *                                  capture request is submitted in a non-reprocessible capture
      *                                  session; or the list of requests contains both requests to
      *                                  capture images from the camera and reprocess capture
-     *                                  requests; or one of the captures targets a Surface in the
+     *                                  requests; or one of the reprocess capture requests was
+     *                                  created with a {@link TotalCaptureResult} from a different
+     *                                  session; or one of the captures targets a Surface in the
      *                                  middle of being {@link #prepare prepared}; or if the handler
      *                                  is null, the listener is not null, and the calling thread
      *                                  has no looper.
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 35727e8..19d17b1 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -158,6 +158,9 @@
     private final HashSet<Surface> mSurfaceSet;
     private final CameraMetadataNative mSettings;
     private boolean mIsReprocess;
+    // Each reprocess request must be tied to a reprocessible session ID.
+    // Valid only for reprocess requests (mIsReprocess == true).
+    private int mReprocessibleSessionId;
 
     private Object mUserTag;
 
@@ -170,6 +173,7 @@
         mSettings = new CameraMetadataNative();
         mSurfaceSet = new HashSet<Surface>();
         mIsReprocess = false;
+        mReprocessibleSessionId = CameraCaptureSession.SESSION_ID_NONE;
     }
 
     /**
@@ -182,6 +186,7 @@
         mSettings = new CameraMetadataNative(source.mSettings);
         mSurfaceSet = (HashSet<Surface>) source.mSurfaceSet.clone();
         mIsReprocess = source.mIsReprocess;
+        mReprocessibleSessionId = source.mReprocessibleSessionId;
         mUserTag = source.mUserTag;
     }
 
@@ -189,11 +194,36 @@
      * Take ownership of passed-in settings.
      *
      * Used by the Builder to create a mutable CaptureRequest.
+     *
+     * @param settings Settings for this capture request.
+     * @param isReprocess Indicates whether to create a reprocess capture request. {@code true}
+     *                    to create a reprocess capture request. {@code false} to create a regular
+     *                    capture request.
+     * @param reprocessibleSessionId The ID of the camera capture session this capture is created
+     *                               for. This is used to validate if the application submits a
+     *                               reprocess capture request to the same session where
+     *                               the {@link TotalCaptureResult}, used to create the reprocess
+     *                               capture, came from.
+     *
+     * @throws IllegalArgumentException If creating a reprocess capture request with an invalid
+     *                                  reprocessibleSessionId.
+     *
+     * @see CameraDevice#createReprocessCaptureRequest
      */
-    private CaptureRequest(CameraMetadataNative settings, boolean isReprocess) {
+    private CaptureRequest(CameraMetadataNative settings, boolean isReprocess,
+            int reprocessibleSessionId) {
         mSettings = CameraMetadataNative.move(settings);
         mSurfaceSet = new HashSet<Surface>();
         mIsReprocess = isReprocess;
+        if (isReprocess) {
+            if (reprocessibleSessionId == CameraCaptureSession.SESSION_ID_NONE) {
+                throw new IllegalArgumentException("Create a reprocess capture request with an " +
+                        "invalid session ID: " + reprocessibleSessionId);
+            }
+            mReprocessibleSessionId = reprocessibleSessionId;
+        } else {
+            mReprocessibleSessionId = CameraCaptureSession.SESSION_ID_NONE;
+        }
     }
 
     /**
@@ -277,6 +307,23 @@
     }
 
     /**
+     * Get the reprocessible session ID this reprocess capture request is associated with.
+     *
+     * @return the reprocessible session ID this reprocess capture request is associated with
+     *
+     * @throws IllegalStateException if this capture request is not a reprocess capture request.
+     * @hide
+     */
+    public int getReprocessibleSessionId() {
+        if (mIsReprocess == false ||
+                mReprocessibleSessionId == CameraCaptureSession.SESSION_ID_NONE) {
+            throw new IllegalStateException("Getting the reprocessible session ID for a "+
+                    "non-reprocess capture request is illegal.");
+        }
+        return mReprocessibleSessionId;
+    }
+
+    /**
      * Determine whether this CaptureRequest is equal to another CaptureRequest.
      *
      * <p>A request is considered equal to another is if it's set of key/values is equal, it's
@@ -298,7 +345,8 @@
                 && Objects.equals(mUserTag, other.mUserTag)
                 && mSurfaceSet.equals(other.mSurfaceSet)
                 && mSettings.equals(other.mSettings)
-                && mIsReprocess == other.mIsReprocess;
+                && mIsReprocess == other.mIsReprocess
+                && mReprocessibleSessionId == other.mReprocessibleSessionId;
     }
 
     @Override
@@ -347,6 +395,7 @@
         }
 
         mIsReprocess = (in.readInt() == 0) ? false : true;
+        mReprocessibleSessionId = CameraCaptureSession.SESSION_ID_NONE;
     }
 
     @Override
@@ -397,10 +446,23 @@
          * Initialize the builder using the template; the request takes
          * ownership of the template.
          *
+         * @param template Template settings for this capture request.
+         * @param reprocess Indicates whether to create a reprocess capture request. {@code true}
+         *                  to create a reprocess capture request. {@code false} to create a regular
+         *                  capture request.
+         * @param reprocessibleSessionId The ID of the camera capture session this capture is
+         *                               created for. This is used to validate if the application
+         *                               submits a reprocess capture request to the same session
+         *                               where the {@link TotalCaptureResult}, used to create the
+         *                               reprocess capture, came from.
+         *
+         * @throws IllegalArgumentException If creating a reprocess capture request with an invalid
+         *                                  reprocessibleSessionId.
          * @hide
          */
-        public Builder(CameraMetadataNative template, boolean reprocess) {
-            mRequest = new CaptureRequest(template, reprocess);
+        public Builder(CameraMetadataNative template, boolean reprocess,
+                int reprocessibleSessionId) {
+            mRequest = new CaptureRequest(template, reprocess, reprocessibleSessionId);
         }
 
         /**
diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java
index 6f7dd78..fb3c098 100644
--- a/core/java/android/hardware/camera2/TotalCaptureResult.java
+++ b/core/java/android/hardware/camera2/TotalCaptureResult.java
@@ -50,6 +50,7 @@
 public final class TotalCaptureResult extends CaptureResult {
 
     private final List<CaptureResult> mPartialResults;
+    private final int mSessionId;
 
     /**
      * Takes ownership of the passed-in camera metadata and the partial results
@@ -58,7 +59,7 @@
      * @hide
      */
     public TotalCaptureResult(CameraMetadataNative results, CaptureRequest parent,
-            CaptureResultExtras extras, List<CaptureResult> partials) {
+            CaptureResultExtras extras, List<CaptureResult> partials, int sessionId) {
         super(results, parent, extras);
 
         if (partials == null) {
@@ -66,6 +67,8 @@
         } else {
             mPartialResults = partials;
         }
+
+        mSessionId = sessionId;
     }
 
     /**
@@ -78,6 +81,7 @@
         super(results, sequenceId);
 
         mPartialResults = new ArrayList<>();
+        mSessionId = CameraCaptureSession.SESSION_ID_NONE;
     }
 
     /**
@@ -95,4 +99,14 @@
     public List<CaptureResult> getPartialResults() {
         return Collections.unmodifiableList(mPartialResults);
     }
+
+    /**
+     * Get the ID of the session where the capture request of this result was submitted.
+     *
+     * @return The session ID
+     * @hide
+     */
+    public int getSessionId() {
+        return mSessionId;
+    }
 }
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index c74204d..3c19529 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -156,9 +156,10 @@
         } else if (request.isReprocess() && !isReprocessible()) {
             throw new IllegalArgumentException("this capture session cannot handle reprocess " +
                     "requests");
+        } else if (request.isReprocess() && request.getReprocessibleSessionId() != mId) {
+            throw new IllegalArgumentException("capture request was created for another session");
         }
 
-
         checkNotClosed();
 
         handler = checkHandler(handler, callback);
@@ -185,12 +186,17 @@
         if (reprocess && !isReprocessible()) {
             throw new IllegalArgumentException("this capture session cannot handle reprocess " +
                     "requests");
+        } else if (reprocess && requests.get(0).getReprocessibleSessionId() != mId) {
+            throw new IllegalArgumentException("capture request was created for another session");
         }
 
         for (int i = 1; i < requests.size(); i++) {
             if (requests.get(i).isReprocess() != reprocess) {
                 throw new IllegalArgumentException("cannot mix regular and reprocess capture " +
                         " requests");
+            } else if (reprocess && requests.get(i).getReprocessibleSessionId() != mId) {
+                throw new IllegalArgumentException("capture request was created for another " +
+                    "session");
             }
         }
 
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 1e680dfd..ff4ad79 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -585,8 +585,8 @@
                 return null;
             }
 
-            CaptureRequest.Builder builder =
-                    new CaptureRequest.Builder(templatedRequest, /*reprocess*/false);
+            CaptureRequest.Builder builder = new CaptureRequest.Builder(
+                    templatedRequest, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE);
 
             return builder;
         }
@@ -601,7 +601,8 @@
             CameraMetadataNative resultMetadata = new
                     CameraMetadataNative(inputResult.getNativeCopy());
 
-            return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true);
+            return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true,
+                    inputResult.getSessionId());
         }
     }
 
@@ -763,7 +764,7 @@
 
             if (callback != null) {
                 mCaptureCallbackMap.put(requestId, new CaptureCallbackHolder(callback,
-                        requestList, handler, repeating));
+                        requestList, handler, repeating, mNextSessionId - 1));
             } else {
                 if (DEBUG) {
                     Log.d(TAG, "Listen for request " + requestId + " is null");
@@ -1095,9 +1096,10 @@
         private final CaptureCallback mCallback;
         private final List<CaptureRequest> mRequestList;
         private final Handler mHandler;
+        private final int mSessionId;
 
         CaptureCallbackHolder(CaptureCallback callback, List<CaptureRequest> requestList,
-                Handler handler, boolean repeating) {
+                Handler handler, boolean repeating, int sessionId) {
             if (callback == null || handler == null) {
                 throw new UnsupportedOperationException(
                     "Must have a valid handler and a valid callback");
@@ -1106,6 +1108,7 @@
             mHandler = handler;
             mRequestList = new ArrayList<CaptureRequest>(requestList);
             mCallback = callback;
+            mSessionId = sessionId;
         }
 
         public boolean isRepeating() {
@@ -1140,6 +1143,10 @@
             return mHandler;
         }
 
+        public int getSessionId() {
+            return mSessionId;
+        }
+
     }
 
     /**
@@ -1643,8 +1650,8 @@
                     List<CaptureResult> partialResults =
                             mFrameNumberTracker.popPartialResults(frameNumber);
 
-                    final TotalCaptureResult resultAsCapture =
-                            new TotalCaptureResult(result, request, resultExtras, partialResults);
+                    final TotalCaptureResult resultAsCapture = new TotalCaptureResult(result,
+                            request, resultExtras, partialResults, holder.getSessionId());
 
                     // Final capture result
                     resultDispatch = new Runnable() {
@@ -1665,7 +1672,8 @@
                 holder.getHandler().post(resultDispatch);
 
                 // Collect the partials for a total result; or mark the frame as totally completed
-                mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult, isReprocess);
+                mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult,
+                        isReprocess);
 
                 // Fire onCaptureSequenceCompleted
                 if (!isPartialResult) {
diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java
index 69a05c4..bae06b8 100644
--- a/core/java/android/provider/Browser.java
+++ b/core/java/android/provider/Browser.java
@@ -16,6 +16,7 @@
 
 package android.provider;
 
+import android.annotation.RequiresPermission;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
@@ -32,6 +33,9 @@
 import android.util.Log;
 import android.webkit.WebIconDatabase;
 
+import static android.Manifest.permission.READ_HISTORY_BOOKMARKS;
+import static android.Manifest.permission.WRITE_HISTORY_BOOKMARKS;
+
 public class Browser {
     private static final String LOGTAG = "browser";
 
@@ -41,6 +45,8 @@
      * {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS} permission and writing to it
      * requires the {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS} permission.
      */
+    @RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))
+    @RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))
     public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
 
     /**
@@ -122,6 +128,8 @@
      * {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS} permission and writing to it
      * requires the {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS} permission.
      */
+    @RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))
+    @RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))
     public static final Uri SEARCHES_URI = Uri.parse("content://browser/searches");
 
     /**
@@ -233,6 +241,7 @@
      *
      *  @param cr   The ContentResolver used to access the database.
      */
+    @RequiresPermission(READ_HISTORY_BOOKMARKS)
     public static final Cursor getAllBookmarks(ContentResolver cr) throws
             IllegalStateException {
         return cr.query(Bookmarks.CONTENT_URI,
@@ -248,6 +257,7 @@
      *
      *  @param cr   The ContentResolver used to access the database.
      */
+    @RequiresPermission(READ_HISTORY_BOOKMARKS)
     public static final Cursor getAllVisitedUrls(ContentResolver cr) throws
             IllegalStateException {
         return cr.query(Combined.CONTENT_URI,
@@ -308,6 +318,7 @@
      *  @param real If true, this is an actual visit, and should add to the
      *              number of visits.  If false, the user entered it manually.
      */
+    @RequiresPermission(allOf = {READ_HISTORY_BOOKMARKS, WRITE_HISTORY_BOOKMARKS})
     public static final void updateVisitedHistory(ContentResolver cr,
                                                   String url, boolean real) {
         long now = System.currentTimeMillis();
@@ -358,6 +369,7 @@
      *  @param cr   The ContentResolver used to access the database.
      *  @hide pending API council approval
      */
+    @RequiresPermission(READ_HISTORY_BOOKMARKS)
     public static final String[] getVisitedHistory(ContentResolver cr) {
         Cursor c = null;
         String[] str = null;
@@ -393,6 +405,7 @@
      *
      * @param cr The ContentResolver used to access the database.
      */
+    @RequiresPermission(allOf = {READ_HISTORY_BOOKMARKS, WRITE_HISTORY_BOOKMARKS})
     public static final void truncateHistory(ContentResolver cr) {
         // TODO make a single request to the provider to do this in a single transaction
         Cursor cursor = null;
@@ -424,6 +437,7 @@
      * @param cr   The ContentResolver used to access the database.
      * @return boolean  True if the history can be cleared.
      */
+    @RequiresPermission(READ_HISTORY_BOOKMARKS)
     public static final boolean canClearHistory(ContentResolver cr) {
         Cursor cursor = null;
         boolean ret = false;
@@ -446,6 +460,7 @@
      *  Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
      *  @param cr   The ContentResolver used to access the database.
      */
+    @RequiresPermission(WRITE_HISTORY_BOOKMARKS)
     public static final void clearHistory(ContentResolver cr) {
         deleteHistoryWhere(cr, null);
     }
@@ -461,6 +476,7 @@
      * @param whereClause   String to limit the items affected.
      *                      null means all items.
      */
+    @RequiresPermission(allOf = {READ_HISTORY_BOOKMARKS, WRITE_HISTORY_BOOKMARKS})
     private static final void deleteHistoryWhere(ContentResolver cr, String whereClause) {
         Cursor cursor = null;
         try {
@@ -486,6 +502,7 @@
      * @param end   Last date to remove. If -1, all dates after begin.
      *              Non-inclusive.
      */
+    @RequiresPermission(WRITE_HISTORY_BOOKMARKS)
     public static final void deleteHistoryTimeFrame(ContentResolver cr,
             long begin, long end) {
         String whereClause;
@@ -511,6 +528,7 @@
      * @param cr    The ContentResolver used to access the database.
      * @param url   url to remove.
      */
+    @RequiresPermission(WRITE_HISTORY_BOOKMARKS)
     public static final void deleteFromHistory(ContentResolver cr,
                                                String url) {
         cr.delete(History.CONTENT_URI, History.URL + "=?", new String[] { url });
@@ -523,6 +541,7 @@
      * @param cr   The ContentResolver used to access the database.
      * @param search    The string to add to the searches database.
      */
+    @RequiresPermission(allOf = {READ_HISTORY_BOOKMARKS, WRITE_HISTORY_BOOKMARKS})
     public static final void addSearchUrl(ContentResolver cr, String search) {
         // The content provider will take care of updating existing searches instead of duplicating
         ContentValues values = new ContentValues();
@@ -536,6 +555,7 @@
      *  Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
      * @param cr   The ContentResolver used to access the database.
      */
+    @RequiresPermission(WRITE_HISTORY_BOOKMARKS)
     public static final void clearSearches(ContentResolver cr) {
         // FIXME: Should this clear the urls to which these searches lead?
         // (i.e. remove google.com/query= blah blah blah)
@@ -557,6 +577,7 @@
      *  @param  listener IconListener that gets the icons once they are
      *          retrieved.
      */
+    @RequiresPermission(READ_HISTORY_BOOKMARKS)
     public static final void requestAllIcons(ContentResolver cr, String where,
             WebIconDatabase.IconListener listener) {
         // Do nothing: this is no longer used.
diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java
index c6b4d7e..113e597 100644
--- a/core/java/android/widget/DayPickerView.java
+++ b/core/java/android/widget/DayPickerView.java
@@ -196,9 +196,23 @@
     }
 
     @Override
+    public void onRtlPropertiesChanged(@ResolvedLayoutDir int layoutDirection) {
+        super.onRtlPropertiesChanged(layoutDirection);
+
+        requestLayout();
+    }
+
+    @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        final ImageButton leftButton = mPrevButton;
-        final ImageButton rightButton = mNextButton;
+        final ImageButton leftButton;
+        final ImageButton rightButton;
+        if (isLayoutRtl()) {
+            leftButton = mNextButton;
+            rightButton = mPrevButton;
+        } else {
+            leftButton = mPrevButton;
+            rightButton = mNextButton;
+        }
 
         final int width = right - left;
         final int height = bottom - top;
diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java
index 0249c22..2778f0f 100644
--- a/core/java/android/widget/SimpleMonthView.java
+++ b/core/java/android/widget/SimpleMonthView.java
@@ -162,7 +162,6 @@
         mTitleFormatter = new SimpleDateFormat(titleFormat, locale);
         mDayOfWeekFormatter = new SimpleDateFormat(DAY_OF_WEEK_FORMAT, locale);
 
-        setClickable(true);
         initPaints(res);
     }
 
@@ -318,7 +317,8 @@
         final int x = (int) (event.getX() + 0.5f);
         final int y = (int) (event.getY() + 0.5f);
 
-        switch (event.getAction()) {
+        final int action = event.getAction();
+        switch (action) {
             case MotionEvent.ACTION_DOWN:
             case MotionEvent.ACTION_MOVE:
                 final int touchedItem = getDayAtLocation(x, y);
@@ -326,6 +326,10 @@
                     mTouchedItem = touchedItem;
                     invalidate();
                 }
+                if (action == MotionEvent.ACTION_DOWN && touchedItem < 0) {
+                    // Touch something that's not an item, reject event.
+                    return false;
+                }
                 break;
 
             case MotionEvent.ACTION_UP:
@@ -376,9 +380,16 @@
 
         for (int col = 0; col < DAYS_IN_WEEK; col++) {
             final int colCenter = colWidth * col + colWidth / 2;
+            final int colCenterRtl;
+            if (isLayoutRtl()) {
+                colCenterRtl = mPaddedWidth - colCenter;
+            } else {
+                colCenterRtl = colCenter;
+            }
+
             final int dayOfWeek = (col + mWeekStart) % DAYS_IN_WEEK;
             final String label = getDayOfWeekLabel(dayOfWeek);
-            canvas.drawText(label, colCenter, rowCenter - halfLineHeight, p);
+            canvas.drawText(label, colCenterRtl, rowCenter - halfLineHeight, p);
         }
     }
 
@@ -402,6 +413,13 @@
 
         for (int day = 1, col = findDayOffset(); day <= mDaysInMonth; day++) {
             final int colCenter = colWidth * col + colWidth / 2;
+            final int colCenterRtl;
+            if (isLayoutRtl()) {
+                colCenterRtl = mPaddedWidth - colCenter;
+            } else {
+                colCenterRtl = colCenter;
+            }
+
             int stateMask = 0;
 
             if (day >= mEnabledDayStart && day <= mEnabledDayEnd) {
@@ -413,12 +431,12 @@
                 stateMask |= StateSet.VIEW_STATE_ACTIVATED;
 
                 // Adjust the circle to be centered on the row.
-                canvas.drawCircle(colCenter, rowCenter, mDaySelectorRadius, mDaySelectorPaint);
+                canvas.drawCircle(colCenterRtl, rowCenter, mDaySelectorRadius, mDaySelectorPaint);
             } else if (mTouchedItem == day) {
                 stateMask |= StateSet.VIEW_STATE_PRESSED;
 
                 // Adjust the circle to be centered on the row.
-                canvas.drawCircle(colCenter, rowCenter, mDaySelectorRadius, mDayHighlightPaint);
+                canvas.drawCircle(colCenterRtl, rowCenter, mDaySelectorRadius, mDayHighlightPaint);
             }
 
             final boolean isDayToday = mToday == day;
@@ -431,7 +449,7 @@
             }
             p.setColor(dayTextColor);
 
-            canvas.drawText(Integer.toString(day), colCenter, rowCenter - halfLineHeight, p);
+            canvas.drawText(Integer.toString(day), colCenterRtl, rowCenter - halfLineHeight, p);
 
             col++;
 
@@ -583,6 +601,13 @@
     }
 
     @Override
+    public void onRtlPropertiesChanged(@ResolvedLayoutDir int layoutDirection) {
+        super.onRtlPropertiesChanged(layoutDirection);
+
+        requestLayout();
+    }
+
+    @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         if (!changed) {
             return;
@@ -657,8 +682,16 @@
             return -1;
         }
 
+        // Adjust for RTL after applying padding.
+        final int paddedXRtl;
+        if (isLayoutRtl()) {
+            paddedXRtl = mPaddedWidth - paddedX;
+        } else {
+            paddedXRtl = paddedX;
+        }
+
         final int row = (paddedY - headerHeight) / mDayHeight;
-        final int col = (paddedX * DAYS_IN_WEEK) / mPaddedWidth;
+        final int col = (paddedXRtl * DAYS_IN_WEEK) / mPaddedWidth;
         final int index = col + row * DAYS_IN_WEEK;
         final int day = index + 1 - findDayOffset();
         if (day < 1 || day > mDaysInMonth) {
@@ -681,10 +714,15 @@
 
         final int index = id - 1 + findDayOffset();
 
-        // Compute left edge.
+        // Compute left edge, taking into account RTL.
         final int col = index % DAYS_IN_WEEK;
         final int colWidth = mCellWidth;
-        final int left = getPaddingLeft() + col * colWidth;
+        final int left;
+        if (isLayoutRtl()) {
+            left = getWidth() - getPaddingRight() - (col + 1) * colWidth;
+        } else {
+            left = getPaddingLeft() + col * colWidth;
+        }
 
         // Compute top edge.
         final int row = index / DAYS_IN_WEEK;
diff --git a/core/java/com/android/internal/widget/SwipeDismissLayout.java b/core/java/com/android/internal/widget/SwipeDismissLayout.java
index 89990c2..191662c 100644
--- a/core/java/com/android/internal/widget/SwipeDismissLayout.java
+++ b/core/java/com/android/internal/widget/SwipeDismissLayout.java
@@ -111,7 +111,7 @@
     }
 
     private void init(Context context) {
-        ViewConfiguration vc = ViewConfiguration.get(getContext());
+        ViewConfiguration vc = ViewConfiguration.get(context);
         mSlop = vc.getScaledTouchSlop();
         mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
         mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
@@ -290,7 +290,7 @@
             float deltaX = ev.getRawX() - mDownX;
             float deltaY = ev.getRawY() - mDownY;
             if ((deltaX * deltaX) + (deltaY * deltaY) > mSlop * mSlop) {
-                mSwiping = deltaX > mSlop * 2 && Math.abs(deltaY) < mSlop * 2;
+                mSwiping = deltaX > mSlop * 2 && Math.abs(deltaY) < Math.abs(deltaX);
             } else {
                 mSwiping = false;
             }
@@ -299,9 +299,9 @@
 
     private void updateDismiss(MotionEvent ev) {
         float deltaX = ev.getRawX() - mDownX;
+        mVelocityTracker.addMovement(ev);
+        mVelocityTracker.computeCurrentVelocity(1000);
         if (!mDismissed) {
-            mVelocityTracker.addMovement(ev);
-            mVelocityTracker.computeCurrentVelocity(1000);
 
             if (deltaX > (getWidth() * DISMISS_MIN_DRAG_WIDTH_RATIO) &&
                     ev.getRawX() >= mLastX) {
@@ -311,7 +311,9 @@
         // Check if the user tried to undo this.
         if (mDismissed && mSwiping) {
             // Check if the user's finger is actually back
-            if (deltaX < (getWidth() * DISMISS_MIN_DRAG_WIDTH_RATIO)) {
+            if (deltaX < (getWidth() * DISMISS_MIN_DRAG_WIDTH_RATIO) ||
+                    // or user is flinging back left
+                    mVelocityTracker.getXVelocity() < -mMinFlingVelocity) {
                 mDismissed = false;
             }
         }
diff --git a/core/java/com/android/internal/widget/ViewPager.java b/core/java/com/android/internal/widget/ViewPager.java
index 5c08daf..441e640 100644
--- a/core/java/com/android/internal/widget/ViewPager.java
+++ b/core/java/com/android/internal/widget/ViewPager.java
@@ -27,9 +27,9 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.MathUtils;
 import android.view.FocusFinder;
 import android.view.Gravity;
 import android.view.KeyEvent;
@@ -43,7 +43,6 @@
 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;
 import android.widget.Scroller;
@@ -84,8 +83,9 @@
  */
 public class ViewPager extends ViewGroup {
     private static final String TAG = "ViewPager";
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = true;
 
+    private static final int MAX_SCROLL_X = 2 << 23;
     private static final boolean USE_CACHE = false;
 
     private static final int DEFAULT_OFFSCREEN_PAGES = 1;
@@ -108,9 +108,13 @@
 
     static class ItemInfo {
         Object object;
-        int position;
         boolean scrolling;
         float widthFactor;
+
+        /** Logical position of the item within the pager adapter. */
+        int position;
+
+        /** Offset between the starting edges of the item and its container. */
         float offset;
     }
 
@@ -146,6 +150,12 @@
     private int mTopPageBounds;
     private int mBottomPageBounds;
 
+    /**
+     * The increment used to move in the "left" direction. Dependent on layout
+     * direction.
+     */
+    private int mLeftIncr = -1;
+
     // Offsets of the first and last items, if known.
     // Set during population, used to determine if we are at the beginning
     // or end of the pager data set during touch scrolling.
@@ -198,14 +208,10 @@
     // "catching" the flinging pager.
     private static final int CLOSE_ENOUGH = 2; // dp
 
-    private boolean mFakeDragging;
-    private long mFakeDragBeginTime;
-
     private final EdgeEffect mLeftEdge;
     private final EdgeEffect mRightEdge;
 
     private boolean mFirstLayout = true;
-    private boolean mNeedCalculatePageOffsets = false;
     private boolean mCalledSuper;
     private int mDecorChildCount;
 
@@ -473,7 +479,7 @@
         mAdapterChangeListener = listener;
     }
 
-    private int getClientWidth() {
+    private int getPaddedWidth() {
         return getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
     }
 
@@ -504,36 +510,33 @@
         return mCurItem;
     }
 
-    void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
-        setCurrentItemInternal(item, smoothScroll, always, 0);
+    boolean setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
+        return setCurrentItemInternal(item, smoothScroll, always, 0);
     }
 
-    void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
+    boolean setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
         if (mAdapter == null || mAdapter.getCount() <= 0) {
             setScrollingCacheEnabled(false);
-            return;
-        }
-        if (!always && mCurItem == item && mItems.size() != 0) {
-            setScrollingCacheEnabled(false);
-            return;
+            return false;
         }
 
-        if (item < 0) {
-            item = 0;
-        } else if (item >= mAdapter.getCount()) {
-            item = mAdapter.getCount() - 1;
+        item = MathUtils.constrain(item, 0, mAdapter.getCount() - 1);
+        if (!always && mCurItem == item && mItems.size() != 0) {
+            setScrollingCacheEnabled(false);
+            return false;
         }
+
         final int pageLimit = mOffscreenPageLimit;
         if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
             // We are doing a jump by more than one page.  To avoid
             // glitches, we want to keep all current pages in the view
             // until the scroll ends.
-            for (int i=0; i<mItems.size(); i++) {
+            for (int i = 0; i < mItems.size(); i++) {
                 mItems.get(i).scrolling = true;
             }
         }
-        final boolean dispatchSelected = mCurItem != item;
 
+        final boolean dispatchSelected = mCurItem != item;
         if (mFirstLayout) {
             // We don't have any idea how big we are yet and shouldn't have any pages either.
             // Just set things up and let the pending layout handle things.
@@ -549,38 +552,55 @@
             populate(item);
             scrollToItem(item, smoothScroll, velocity, dispatchSelected);
         }
+
+        return true;
     }
 
-    private void scrollToItem(int item, boolean smoothScroll, int velocity,
+    private void scrollToItem(int position, boolean smoothScroll, int velocity,
             boolean dispatchSelected) {
-        final ItemInfo curInfo = infoForPosition(item);
-        int destX = 0;
-        if (curInfo != null) {
-            final int width = getClientWidth();
-            destX = (int) (width * Math.max(mFirstOffset,
-                    Math.min(curInfo.offset, mLastOffset)));
-        }
+        final int destX = getLeftEdgeForItem(position);
+
         if (smoothScroll) {
             smoothScrollTo(destX, 0, velocity);
+
             if (dispatchSelected && mOnPageChangeListener != null) {
-                mOnPageChangeListener.onPageSelected(item);
+                mOnPageChangeListener.onPageSelected(position);
             }
             if (dispatchSelected && mInternalPageChangeListener != null) {
-                mInternalPageChangeListener.onPageSelected(item);
+                mInternalPageChangeListener.onPageSelected(position);
             }
         } else {
             if (dispatchSelected && mOnPageChangeListener != null) {
-                mOnPageChangeListener.onPageSelected(item);
+                mOnPageChangeListener.onPageSelected(position);
             }
             if (dispatchSelected && mInternalPageChangeListener != null) {
-                mInternalPageChangeListener.onPageSelected(item);
+                mInternalPageChangeListener.onPageSelected(position);
             }
+
             completeScroll(false);
             scrollTo(destX, 0);
             pageScrolled(destX);
         }
     }
 
+    private int getLeftEdgeForItem(int position) {
+        final ItemInfo info = infoForPosition(position);
+        if (info == null) {
+            return 0;
+        }
+
+        final int width = getPaddedWidth();
+        final int scaledOffset = (int) (width * MathUtils.constrain(
+                info.offset, mFirstOffset, mLastOffset));
+
+        if (isLayoutRtl()) {
+            final int itemWidth = (int) (width * info.widthFactor + 0.5f);
+            return MAX_SCROLL_X - itemWidth - scaledOffset;
+        } else {
+            return scaledOffset;
+        }
+    }
+
     /**
      * Set a listener that will be invoked whenever the page changes or is incrementally
      * scrolled. See {@link OnPageChangeListener}.
@@ -784,7 +804,7 @@
         setScrollingCacheEnabled(true);
         setScrollState(SCROLL_STATE_SETTLING);
 
-        final int width = getClientWidth();
+        final int width = getPaddedWidth();
         final int halfWidth = width / 2;
         final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);
         final float distance = halfWidth + halfWidth *
@@ -968,7 +988,7 @@
             float extraWidthLeft = 0.f;
             int itemIndex = curIndex - 1;
             ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
-            final int clientWidth = getClientWidth();
+            final int clientWidth = getPaddedWidth();
             final float leftWidthNeeded = clientWidth <= 0 ? 0 :
                     2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;
             for (int pos = mCurItem - 1; pos >= 0; pos--) {
@@ -981,7 +1001,7 @@
                         mAdapter.destroyItem(this, pos, ii.object);
                         if (DEBUG) {
                             Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
-                                    " view: " + ((View) ii.object));
+                                    " view: " + ii.object);
                         }
                         itemIndex--;
                         curIndex--;
@@ -1015,7 +1035,7 @@
                             mAdapter.destroyItem(this, pos, ii.object);
                             if (DEBUG) {
                                 Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
-                                        " view: " + ((View) ii.object));
+                                        " view: " + ii.object);
                             }
                             ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                         }
@@ -1099,49 +1119,51 @@
 
     private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) {
         final int N = mAdapter.getCount();
-        final int width = getClientWidth();
+        final int width = getPaddedWidth();
         final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
+
         // Fix up offsets for later layout.
         if (oldCurInfo != null) {
             final int oldCurPosition = oldCurInfo.position;
+
             // Base offsets off of oldCurInfo.
             if (oldCurPosition < curItem.position) {
                 int itemIndex = 0;
-                ItemInfo ii = null;
                 float offset = oldCurInfo.offset + oldCurInfo.widthFactor + marginOffset;
-                for (int pos = oldCurPosition + 1;
-                        pos <= curItem.position && itemIndex < mItems.size(); pos++) {
-                    ii = mItems.get(itemIndex);
+                for (int pos = oldCurPosition + 1; pos <= curItem.position && itemIndex < mItems.size(); pos++) {
+                    ItemInfo ii = mItems.get(itemIndex);
                     while (pos > ii.position && itemIndex < mItems.size() - 1) {
                         itemIndex++;
                         ii = mItems.get(itemIndex);
                     }
+
                     while (pos < ii.position) {
                         // We don't have an item populated for this,
                         // ask the adapter for an offset.
                         offset += mAdapter.getPageWidth(pos) + marginOffset;
                         pos++;
                     }
+
                     ii.offset = offset;
                     offset += ii.widthFactor + marginOffset;
                 }
             } else if (oldCurPosition > curItem.position) {
                 int itemIndex = mItems.size() - 1;
-                ItemInfo ii = null;
                 float offset = oldCurInfo.offset;
-                for (int pos = oldCurPosition - 1;
-                        pos >= curItem.position && itemIndex >= 0; pos--) {
-                    ii = mItems.get(itemIndex);
+                for (int pos = oldCurPosition - 1; pos >= curItem.position && itemIndex >= 0; pos--) {
+                    ItemInfo ii = mItems.get(itemIndex);
                     while (pos < ii.position && itemIndex > 0) {
                         itemIndex--;
                         ii = mItems.get(itemIndex);
                     }
+
                     while (pos > ii.position) {
                         // We don't have an item populated for this,
                         // ask the adapter for an offset.
                         offset -= mAdapter.getPageWidth(pos) + marginOffset;
                         pos--;
                     }
+
                     offset -= ii.widthFactor + marginOffset;
                     ii.offset = offset;
                 }
@@ -1155,6 +1177,7 @@
         mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;
         mLastOffset = curItem.position == N - 1 ?
                 curItem.offset + curItem.widthFactor - 1 : Float.MAX_VALUE;
+
         // Previous pages
         for (int i = curIndex - 1; i >= 0; i--, pos--) {
             final ItemInfo ii = mItems.get(i);
@@ -1165,8 +1188,10 @@
             ii.offset = offset;
             if (ii.position == 0) mFirstOffset = offset;
         }
+
         offset = curItem.offset + curItem.widthFactor + marginOffset;
         pos = curItem.position + 1;
+
         // Next pages
         for (int i = curIndex + 1; i < itemCount; i++, pos++) {
             final ItemInfo ii = mItems.get(i);
@@ -1179,8 +1204,6 @@
             ii.offset = offset;
             offset += ii.widthFactor + marginOffset;
         }
-
-        mNeedCalculatePageOffsets = false;
     }
 
     /**
@@ -1546,34 +1569,47 @@
         // Page views. Do this once we have the right padding offsets from above.
         for (int i = 0; i < count; i++) {
             final View child = getChildAt(i);
-            if (child.getVisibility() != GONE) {
-                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                ItemInfo ii;
-                if (!lp.isDecor && (ii = infoForChild(child)) != null) {
-                    int loff = (int) (childWidth * ii.offset);
-                    int childLeft = paddingLeft + loff;
-                    int childTop = paddingTop;
-                    if (lp.needsMeasure) {
-                        // This was added during layout and needs measurement.
-                        // Do it now that we know what we're working with.
-                        lp.needsMeasure = false;
-                        final int widthSpec = MeasureSpec.makeMeasureSpec(
-                                (int) (childWidth * lp.widthFactor),
-                                MeasureSpec.EXACTLY);
-                        final int heightSpec = MeasureSpec.makeMeasureSpec(
-                                (int) (height - paddingTop - paddingBottom),
-                                MeasureSpec.EXACTLY);
-                        child.measure(widthSpec, heightSpec);
-                    }
-                    if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
-                            + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
-                            + "x" + child.getMeasuredHeight());
-                    child.layout(childLeft, childTop,
-                            childLeft + child.getMeasuredWidth(),
-                            childTop + child.getMeasuredHeight());
-                }
+            if (child.getVisibility() == GONE) {
+                continue;
             }
+
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            if (lp.isDecor) {
+                continue;
+            }
+
+            final ItemInfo ii = infoForChild(child);
+            if (ii == null) {
+                continue;
+            }
+
+            if (lp.needsMeasure) {
+                // This was added during layout and needs measurement.
+                // Do it now that we know what we're working with.
+                lp.needsMeasure = false;
+                final int widthSpec = MeasureSpec.makeMeasureSpec(
+                        (int) (childWidth * lp.widthFactor),
+                        MeasureSpec.EXACTLY);
+                final int heightSpec = MeasureSpec.makeMeasureSpec(
+                        (int) (height - paddingTop - paddingBottom),
+                        MeasureSpec.EXACTLY);
+                child.measure(widthSpec, heightSpec);
+            }
+
+            final int childMeasuredWidth = child.getMeasuredWidth();
+            final int startOffset = (int) (childWidth * ii.offset);
+            final int childLeft;
+            if (isLayoutRtl()) {
+                childLeft = MAX_SCROLL_X - paddingRight - startOffset - childMeasuredWidth;
+            } else {
+                childLeft = paddingLeft + startOffset;
+            }
+
+            final int childTop = paddingTop;
+            child.layout(childLeft, childTop, childLeft + childMeasuredWidth,
+                    childTop + child.getMeasuredHeight());
         }
+
         mTopPageBounds = paddingTop;
         mBottomPageBounds = height - paddingBottom;
         mDecorChildCount = decorCount;
@@ -1587,13 +1623,14 @@
     @Override
     public void computeScroll() {
         if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
-            int oldX = getScrollX();
-            int oldY = getScrollY();
-            int x = mScroller.getCurrX();
-            int y = mScroller.getCurrY();
+            final int oldX = getScrollX();
+            final int oldY = getScrollY();
+            final int x = mScroller.getCurrX();
+            final int y = mScroller.getCurrY();
 
             if (oldX != x || oldY != y) {
                 scrollTo(x, y);
+
                 if (!pageScrolled(x)) {
                     mScroller.abortAnimation();
                     scrollTo(0, y);
@@ -1609,7 +1646,7 @@
         completeScroll(true);
     }
 
-    private boolean pageScrolled(int xpos) {
+    private boolean pageScrolled(int scrollX) {
         if (mItems.size() == 0) {
             mCalledSuper = false;
             onPageScrolled(0, 0, 0);
@@ -1619,12 +1656,21 @@
             }
             return false;
         }
-        final ItemInfo ii = infoForCurrentScrollPosition();
-        final int width = getClientWidth();
+
+        // Translate to scrollX to scrollStart for RTL.
+        final int scrollStart;
+        if (isLayoutRtl()) {
+            scrollStart = MAX_SCROLL_X - scrollX;
+        } else {
+            scrollStart = scrollX;
+        }
+
+        final ItemInfo ii = infoForFirstVisiblePage();
+        final int width = getPaddedWidth();
         final int widthWithMargin = width + mPageMargin;
         final float marginOffset = (float) mPageMargin / width;
         final int currentPage = ii.position;
-        final float pageOffset = (((float) xpos / width) - ii.offset) /
+        final float pageOffset = (((float) scrollStart / width) - ii.offset) /
                 (ii.widthFactor + marginOffset);
         final int offsetPixels = (int) (pageOffset * widthWithMargin);
 
@@ -1706,7 +1752,7 @@
 
                 if (lp.isDecor) continue;
 
-                final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth();
+                final float transformPos = (float) (child.getLeft() - scrollX) / getPaddedWidth();
                 mPageTransformer.transformPage(child, transformPos);
             }
         }
@@ -1785,11 +1831,11 @@
         // are dragging.
         if (action != MotionEvent.ACTION_DOWN) {
             if (mIsBeingDragged) {
-                if (DEBUG) Log.v(TAG, "Intercept returning true!");
+                if (DEBUG) Log.v(TAG, "Being dragged, intercept returning true!");
                 return true;
             }
             if (mIsUnableToDrag) {
-                if (DEBUG) Log.v(TAG, "Intercept returning false!");
+                if (DEBUG) Log.v(TAG, "Unable to drag, intercept returning false!");
                 return false;
             }
         }
@@ -1903,13 +1949,6 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
-        if (mFakeDragging) {
-            // A fake drag is in progress already, ignore this real one
-            // but still eat the touch events.
-            // (It is likely that the user is multi-touching the screen.)
-            return true;
-        }
-
         if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
             // Don't handle edge touches immediately -- they may actually belong to one of our
             // descendants.
@@ -1978,19 +2017,26 @@
                 if (mIsBeingDragged) {
                     final VelocityTracker velocityTracker = mVelocityTracker;
                     velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
-                    int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
+                    final int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
+
                     mPopulatePending = true;
-                    final int width = getClientWidth();
-                    final int scrollX = getScrollX();
-                    final ItemInfo ii = infoForCurrentScrollPosition();
+
+                    final float scrollStart = getScrollStart();
+                    final float scrolledPages = scrollStart / getPaddedWidth();
+                    final ItemInfo ii = infoForFirstVisiblePage();
                     final int currentPage = ii.position;
-                    final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
-                    final int activePointerIndex =
-                            ev.findPointerIndex(mActivePointerId);
+                    final float nextPageOffset;
+                    if (isLayoutRtl()) {
+                        nextPageOffset = (ii.offset - scrolledPages) / ii.widthFactor;
+                    }  else {
+                        nextPageOffset = (scrolledPages - ii.offset) / ii.widthFactor;
+                    }
+
+                    final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
                     final float x = ev.getX(activePointerIndex);
                     final int totalDelta = (int) (x - mInitialMotionX);
-                    int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
-                            totalDelta);
+                    final int nextPage = determineTargetPage(
+                            currentPage, nextPageOffset, initialVelocity, totalDelta);
                     setCurrentItemInternal(nextPage, true, true, initialVelocity);
 
                     mActivePointerId = INVALID_POINTER;
@@ -2038,48 +2084,79 @@
     private boolean performDrag(float x) {
         boolean needsInvalidate = false;
 
+        final int width = getPaddedWidth();
         final float deltaX = mLastMotionX - x;
         mLastMotionX = x;
 
-        float oldScrollX = getScrollX();
-        float scrollX = oldScrollX + deltaX;
-        final int width = getClientWidth();
-
-        float leftBound = width * mFirstOffset;
-        float rightBound = width * mLastOffset;
-        boolean leftAbsolute = true;
-        boolean rightAbsolute = true;
-
-        final ItemInfo firstItem = mItems.get(0);
-        final ItemInfo lastItem = mItems.get(mItems.size() - 1);
-        if (firstItem.position != 0) {
-            leftAbsolute = false;
-            leftBound = firstItem.offset * width;
-        }
-        if (lastItem.position != mAdapter.getCount() - 1) {
-            rightAbsolute = false;
-            rightBound = lastItem.offset * width;
+        final EdgeEffect startEdge;
+        final EdgeEffect endEdge;
+        if (isLayoutRtl()) {
+            startEdge = mRightEdge;
+            endEdge = mLeftEdge;
+        } else {
+            startEdge = mLeftEdge;
+            endEdge = mRightEdge;
         }
 
-        if (scrollX < leftBound) {
-            if (leftAbsolute) {
-                float over = leftBound - scrollX;
-                mLeftEdge.onPull(Math.abs(over) / width);
+        // Translate scroll to relative coordinates.
+        final float nextScrollX = getScrollX() + deltaX;
+        final float scrollStart;
+        if (isLayoutRtl()) {
+            scrollStart = MAX_SCROLL_X - nextScrollX;
+        } else {
+            scrollStart = nextScrollX;
+        }
+
+        final float startBound;
+        final ItemInfo startItem = mItems.get(0);
+        final boolean startAbsolute = startItem.position == 0;
+        if (startAbsolute) {
+            startBound = startItem.offset * width;
+        } else {
+            startBound = width * mFirstOffset;
+        }
+
+        final float endBound;
+        final ItemInfo endItem = mItems.get(mItems.size() - 1);
+        final boolean endAbsolute = endItem.position == mAdapter.getCount() - 1;
+        if (endAbsolute) {
+            endBound = endItem.offset * width;
+        } else {
+            endBound = width * mLastOffset;
+        }
+
+        final float clampedScrollStart;
+        if (scrollStart < startBound) {
+            if (startAbsolute) {
+                final float over = startBound - scrollStart;
+                startEdge.onPull(Math.abs(over) / width);
                 needsInvalidate = true;
             }
-            scrollX = leftBound;
-        } else if (scrollX > rightBound) {
-            if (rightAbsolute) {
-                float over = scrollX - rightBound;
-                mRightEdge.onPull(Math.abs(over) / width);
+            clampedScrollStart = startBound;
+        } else if (scrollStart > endBound) {
+            if (endAbsolute) {
+                final float over = scrollStart - endBound;
+                endEdge.onPull(Math.abs(over) / width);
                 needsInvalidate = true;
             }
-            scrollX = rightBound;
+            clampedScrollStart = endBound;
+        } else {
+            clampedScrollStart = scrollStart;
         }
-        // Don't lose the rounded component
-        mLastMotionX += scrollX - (int) scrollX;
-        scrollTo((int) scrollX, getScrollY());
-        pageScrolled((int) scrollX);
+
+        // Translate back to absolute coordinates.
+        final float targetScrollX;
+        if (isLayoutRtl()) {
+            targetScrollX = MAX_SCROLL_X - clampedScrollStart;
+        } else {
+            targetScrollX = clampedScrollStart;
+        }
+
+        // Don't lose the rounded component.
+        mLastMotionX += targetScrollX - (int) targetScrollX;
+
+        scrollTo((int) targetScrollX, getScrollY());
+        pageScrolled((int) targetScrollX);
 
         return needsInvalidate;
     }
@@ -2088,19 +2165,23 @@
      * @return Info about the page at the current scroll position.
      *         This can be synthetic for a missing middle page; the 'object' field can be null.
      */
-    private ItemInfo infoForCurrentScrollPosition() {
-        final int width = getClientWidth();
-        final float scrollOffset = width > 0 ? (float) getScrollX() / width : 0;
+    private ItemInfo infoForFirstVisiblePage() {
+        final int startOffset = getScrollStart();
+        final int width = getPaddedWidth();
+        final float scrollOffset = width > 0 ? (float) startOffset / width : 0;
         final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
+
         int lastPos = -1;
         float lastOffset = 0.f;
         float lastWidth = 0.f;
         boolean first = true;
-
         ItemInfo lastItem = null;
-        for (int i = 0; i < mItems.size(); i++) {
+
+        final int N = mItems.size();
+        for (int i = 0; i < N; i++) {
             ItemInfo ii = mItems.get(i);
-            float offset;
+
+            // Seek to position.
             if (!first && ii.position != lastPos + 1) {
                 // Create a synthetic item for a missing page.
                 ii = mTempItem;
@@ -2109,17 +2190,18 @@
                 ii.widthFactor = mAdapter.getPageWidth(ii.position);
                 i--;
             }
-            offset = ii.offset;
 
-            final float leftBound = offset;
-            final float rightBound = offset + ii.widthFactor + marginOffset;
-            if (first || scrollOffset >= leftBound) {
-                if (scrollOffset < rightBound || i == mItems.size() - 1) {
+            final float offset = ii.offset;
+            final float startBound = offset;
+            if (first || scrollOffset >= startBound) {
+                final float endBound = offset + ii.widthFactor + marginOffset;
+                if (scrollOffset < endBound || i == mItems.size() - 1) {
                     return ii;
                 }
             } else {
                 return lastItem;
             }
+
             first = false;
             lastPos = ii.position;
             lastOffset = offset;
@@ -2130,13 +2212,28 @@
         return lastItem;
     }
 
+    private int getScrollStart() {
+        if (isLayoutRtl()) {
+            return MAX_SCROLL_X - getScrollX();
+        } else {
+            return getScrollX();
+        }
+    }
+
+    /**
+     * @param currentPage the position of the page with the first visible starting edge
+     * @param pageOffset the fraction of the right-hand page that's visible
+     * @param velocity the velocity of the touch event stream
+     * @param deltaX the distance of the touch event stream
+     * @return the position of the target page
+     */
     private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) {
         int targetPage;
         if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
-            targetPage = velocity > 0 ? currentPage : currentPage + 1;
+            targetPage = currentPage - (velocity < 0 ? mLeftIncr : 0);
         } else {
             final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
-            targetPage = (int) (currentPage + pageOffset + truncator);
+            targetPage = (int) (currentPage - mLeftIncr * (pageOffset + truncator));
         }
 
         if (mItems.size() > 0) {
@@ -2144,7 +2241,7 @@
             final ItemInfo lastItem = mItems.get(mItems.size() - 1);
 
             // Only let the user target pages we have items for
-            targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
+            targetPage = MathUtils.constrain(targetPage, firstItem.position, lastItem.position);
         }
 
         return targetPage;
@@ -2205,6 +2302,7 @@
             int itemIndex = 0;
             ItemInfo ii = mItems.get(0);
             float offset = ii.offset;
+
             final int itemCount = mItems.size();
             final int firstPos = ii.position;
             final int lastPos = mItems.get(itemCount - 1).position;
@@ -2213,156 +2311,39 @@
                     ii = mItems.get(++itemIndex);
                 }
 
-                float drawAt;
+                final float itemOffset;
+                final float widthFactor;
                 if (pos == ii.position) {
-                    drawAt = (ii.offset + ii.widthFactor) * width;
-                    offset = ii.offset + ii.widthFactor + marginOffset;
+                    itemOffset = ii.offset;
+                    widthFactor = ii.widthFactor;
                 } else {
-                    float widthFactor = mAdapter.getPageWidth(pos);
-                    drawAt = (offset + widthFactor) * width;
-                    offset += widthFactor + marginOffset;
+                    itemOffset = offset;
+                    widthFactor = mAdapter.getPageWidth(pos);
                 }
 
-                if (drawAt + mPageMargin > scrollX) {
-                    mMarginDrawable.setBounds((int) drawAt, mTopPageBounds,
-                            (int) (drawAt + mPageMargin + 0.5f), mBottomPageBounds);
+                final float left;
+                final float scaledOffset = itemOffset * width;
+                if (isLayoutRtl()) {
+                    left = MAX_SCROLL_X - scaledOffset;
+                } else {
+                    left = scaledOffset + widthFactor * width;
+                }
+
+                offset = itemOffset + widthFactor + marginOffset;
+
+                if (left + mPageMargin > scrollX) {
+                    mMarginDrawable.setBounds((int) left, mTopPageBounds,
+                            (int) (left + mPageMargin + 0.5f), mBottomPageBounds);
                     mMarginDrawable.draw(canvas);
                 }
 
-                if (drawAt > scrollX + width) {
+                if (left > scrollX + width) {
                     break; // No more visible, no sense in continuing
                 }
             }
         }
     }
 
-    /**
-     * Start a fake drag of the pager.
-     *
-     * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager
-     * with the touch scrolling of another view, while still letting the ViewPager
-     * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
-     * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
-     * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
-     *
-     * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag
-     * is already in progress, this method will return false.
-     *
-     * @return true if the fake drag began successfully, false if it could not be started.
-     *
-     * @see #fakeDragBy(float)
-     * @see #endFakeDrag()
-     */
-    public boolean beginFakeDrag() {
-        if (mIsBeingDragged) {
-            return false;
-        }
-        mFakeDragging = true;
-        setScrollState(SCROLL_STATE_DRAGGING);
-        mInitialMotionX = mLastMotionX = 0;
-        if (mVelocityTracker == null) {
-            mVelocityTracker = VelocityTracker.obtain();
-        } else {
-            mVelocityTracker.clear();
-        }
-        final long time = SystemClock.uptimeMillis();
-        final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
-        mVelocityTracker.addMovement(ev);
-        ev.recycle();
-        mFakeDragBeginTime = time;
-        return true;
-    }
-
-    /**
-     * End a fake drag of the pager.
-     *
-     * @see #beginFakeDrag()
-     * @see #fakeDragBy(float)
-     */
-    public void endFakeDrag() {
-        if (!mFakeDragging) {
-            throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
-        }
-
-        final VelocityTracker velocityTracker = mVelocityTracker;
-        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
-        int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
-        mPopulatePending = true;
-        final int width = getClientWidth();
-        final int scrollX = getScrollX();
-        final ItemInfo ii = infoForCurrentScrollPosition();
-        final int currentPage = ii.position;
-        final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
-        final int totalDelta = (int) (mLastMotionX - mInitialMotionX);
-        int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
-                totalDelta);
-        setCurrentItemInternal(nextPage, true, true, initialVelocity);
-        endDrag();
-
-        mFakeDragging = false;
-    }
-
-    /**
-     * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
-     *
-     * @param xOffset Offset in pixels to drag by.
-     * @see #beginFakeDrag()
-     * @see #endFakeDrag()
-     */
-    public void fakeDragBy(float xOffset) {
-        if (!mFakeDragging) {
-            throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
-        }
-
-        mLastMotionX += xOffset;
-
-        float oldScrollX = getScrollX();
-        float scrollX = oldScrollX - xOffset;
-        final int width = getClientWidth();
-
-        float leftBound = width * mFirstOffset;
-        float rightBound = width * mLastOffset;
-
-        final ItemInfo firstItem = mItems.get(0);
-        final ItemInfo lastItem = mItems.get(mItems.size() - 1);
-        if (firstItem.position != 0) {
-            leftBound = firstItem.offset * width;
-        }
-        if (lastItem.position != mAdapter.getCount() - 1) {
-            rightBound = lastItem.offset * width;
-        }
-
-        if (scrollX < leftBound) {
-            scrollX = leftBound;
-        } else if (scrollX > rightBound) {
-            scrollX = rightBound;
-        }
-        // Don't lose the rounded component
-        mLastMotionX += scrollX - (int) scrollX;
-        scrollTo((int) scrollX, getScrollY());
-        pageScrolled((int) scrollX);
-
-        // Synthesize an event for the VelocityTracker.
-        final long time = SystemClock.uptimeMillis();
-        final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
-                mLastMotionX, 0, 0);
-        mVelocityTracker.addMovement(ev);
-        ev.recycle();
-    }
-
-    /**
-     * Returns true if a fake drag is in progress.
-     *
-     * @return true if currently in a fake drag, false otherwise.
-     *
-     * @see #beginFakeDrag()
-     * @see #fakeDragBy(float)
-     * @see #endFakeDrag()
-     */
-    public boolean isFakeDragging() {
-        return mFakeDragging;
-    }
-
     private void onSecondaryPointerUp(MotionEvent ev) {
         final int pointerIndex = ev.getActionIndex();
         final int pointerId = ev.getPointerId(pointerIndex);
@@ -2408,7 +2389,7 @@
             return false;
         }
 
-        final int width = getClientWidth();
+        final int width = getPaddedWidth();
         final int scrollX = getScrollX();
         if (direction < 0) {
             return (scrollX > (int) (width * mFirstOffset));
@@ -2438,12 +2419,11 @@
             final int count = group.getChildCount();
             // Count backwards - let topmost views consume scroll distance first.
             for (int i = count - 1; i >= 0; i--) {
-                // TODO: Add versioned support here for transformed views.
-                // This will not work for transformed views in Honeycomb+
+                // TODO: Add support for transformed views.
                 final View child = group.getChildAt(i);
-                if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
-                        y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
-                        canScroll(child, true, dx, x + scrollX - child.getLeft(),
+                if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight()
+                        && y + scrollY >= child.getTop() && y + scrollY < child.getBottom()
+                        && canScroll(child, true, dx, x + scrollX - child.getLeft(),
                                 y + scrollY - child.getTop())) {
                     return true;
                 }
@@ -2582,19 +2562,22 @@
     }
 
     boolean pageLeft() {
-        if (mCurItem > 0) {
-            setCurrentItem(mCurItem-1, true);
-            return true;
-        }
-        return false;
+        return setCurrentItemInternal(mCurItem + mLeftIncr, true, false);
     }
 
     boolean pageRight() {
-        if (mAdapter != null && mCurItem < (mAdapter.getCount()-1)) {
-            setCurrentItem(mCurItem+1, true);
-            return true;
+        return setCurrentItemInternal(mCurItem - mLeftIncr, true, false);
+    }
+
+    @Override
+    public void onRtlPropertiesChanged(@ResolvedLayoutDir int layoutDirection) {
+        super.onRtlPropertiesChanged(layoutDirection);
+
+        if (layoutDirection == LAYOUT_DIRECTION_LTR) {
+            mLeftIncr = -1;
+        } else {
+            mLeftIncr = 1;
         }
-        return false;
     }
 
     /**
diff --git a/core/res/res/drawable/ic_chevron_right.xml b/core/res/res/drawable/ic_chevron_end.xml
similarity index 91%
rename from core/res/res/drawable/ic_chevron_right.xml
rename to core/res/res/drawable/ic_chevron_end.xml
index 4e6d8e3..8570d26 100644
--- a/core/res/res/drawable/ic_chevron_right.xml
+++ b/core/res/res/drawable/ic_chevron_end.xml
@@ -18,7 +18,8 @@
         android:height="24dp"
         android:viewportWidth="24"
         android:viewportHeight="24"
-        android:tint="?attr/colorControlNormal">
+        android:tint="?attr/colorControlNormal"
+        android:autoMirrored="true">
     <path
         android:fillColor="#FF000000"
         android:pathData="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6,-6z"/>
diff --git a/core/res/res/drawable/ic_chevron_left.xml b/core/res/res/drawable/ic_chevron_start.xml
similarity index 91%
rename from core/res/res/drawable/ic_chevron_left.xml
rename to core/res/res/drawable/ic_chevron_start.xml
index dc24706..d412ce0 100644
--- a/core/res/res/drawable/ic_chevron_left.xml
+++ b/core/res/res/drawable/ic_chevron_start.xml
@@ -18,7 +18,8 @@
         android:height="24dp"
         android:viewportWidth="24"
         android:viewportHeight="24"
-        android:tint="?attr/colorControlNormal">
+        android:tint="?attr/colorControlNormal"
+        android:autoMirrored="true">
     <path
         android:fillColor="#FF000000"
         android:pathData="M15.41 7.41L14 6l-6 6 6 6 1.41,-1.41L10.83 12z"/>
diff --git a/core/res/res/layout/date_picker_header_material.xml b/core/res/res/layout/date_picker_header_material.xml
index 8125544..2150341 100644
--- a/core/res/res/layout/date_picker_header_material.xml
+++ b/core/res/res/layout/date_picker_header_material.xml
@@ -42,7 +42,7 @@
 
     <TextView
         android:id="@+id/date_picker_header_date"
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:textAppearance="@style/TextAppearance.Material.DatePicker.DateLabel"
         android:gravity="start"
diff --git a/core/res/res/layout/day_picker_content_material.xml b/core/res/res/layout/day_picker_content_material.xml
index 1852bfa..b582d74 100644
--- a/core/res/res/layout/day_picker_content_material.xml
+++ b/core/res/res/layout/day_picker_content_material.xml
@@ -30,7 +30,7 @@
         android:layout_height="wrap_content"
         android:minWidth="48dp"
         android:minHeight="48dp"
-        android:src="@drawable/ic_chevron_left"
+        android:src="@drawable/ic_chevron_start"
         android:background="?attr/selectableItemBackgroundBorderless"
         android:contentDescription="@string/date_picker_prev_month_button"
         android:visibility="invisible" />
@@ -41,7 +41,7 @@
         android:layout_height="wrap_content"
         android:minWidth="48dp"
         android:minHeight="48dp"
-        android:src="@drawable/ic_chevron_right"
+        android:src="@drawable/ic_chevron_end"
         android:background="?attr/selectableItemBackgroundBorderless"
         android:contentDescription="@string/date_picker_next_month_button"
         android:visibility="invisible" />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7e24150..5b13325 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2235,8 +2235,6 @@
   <java-symbol type="dimen" name="floating_toolbar_horizontal_margin" />
   <java-symbol type="dimen" name="floating_toolbar_vertical_margin" />
 
-  <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" />
diff --git a/docs/html/guide/appendix/media-formats.jd b/docs/html/guide/appendix/media-formats.jd
index 19f510a..2a908ba 100644
--- a/docs/html/guide/appendix/media-formats.jd
+++ b/docs/html/guide/appendix/media-formats.jd
@@ -71,7 +71,7 @@
 </tr>
 
 <tr>
-<td rowspan="11">Audio</td>
+<td rowspan="12">Audio</td>
 <td>AAC LC</td>
 <td style="text-align: center;"><big>&bull;</big></td>
 <td style="text-align: center;"><big>&bull;</big></td>
@@ -180,6 +180,15 @@
 </tr>
 
 <tr>
+<td>Opus</td>
+<td style="text-align: center;"></td>
+<td style="text-align: center;"><big>&bull;</big><br><small>(Android 5.0+)</small></td>
+<td></td>
+<td>
+  Matroska (.mkv)</td>
+</tr>
+
+<tr>
 <td rowspan="5">Image</td>
 <td>JPEG</td>
 <td style="text-align: center;"><big>&bull;</big></td>
@@ -235,7 +244,7 @@
 
 
 <tr>
-<td rowspan="4">Video</td>
+<td rowspan="6">Video</td>
 <td>H.263</td>
 <td style="text-align: center;"><big>&bull;</big></td>
 <td style="text-align: center;"><big>&bull;</big></td>
@@ -257,6 +266,15 @@
 </tr>
 
 <tr>
+<td>H.265 HEVC</td>
+<td style="text-align: center;" nowrap></td>
+<td style="text-align: center;" nowrap><big>&bull;</big><br><small>(Android 5.0+)</small></td>
+<td>Main Profile Level 3 for mobile devices and Main Profile Level 4.1 for Android TV</td>
+<td>
+  &bull; MPEG-4 (.mp4)<br>
+</tr>
+
+<tr>
 <td>MPEG-4 SP</td>
 <td>&nbsp;</td>
 <td style="text-align: center;"><big>&bull;</big></td>
@@ -275,6 +293,16 @@
   &bull; Matroska (.mkv, Android 4.0+)</td>
 </tr>
 
+<tr>
+<td>VP9</td>
+<td style="text-align: center;" nowrap></td>
+<td style="text-align: center;" nowrap><big>&bull;</big><br><small>(Android 4.4+)</small></td>
+<td></td>
+<td>
+  &bull; <a href="http://www.webmproject.org/">WebM</a> (.webm)<br>
+  &bull; Matroska (.mkv, Android 4.0+)</td>
+</tr>
+
 </tbody></table>
 
 
diff --git a/docs/html/training/tv/start/start.jd b/docs/html/training/tv/start/start.jd
index 2766e90..0f5871f 100644
--- a/docs/html/training/tv/start/start.jd
+++ b/docs/html/training/tv/start/start.jd
@@ -1,4 +1,4 @@
-page.title=Get Started with TV Apps
+page.title=Getting Started with TV Apps
 page.tags="leanback","recyclerview","launcher"
 
 trainingnavtop=true
@@ -10,6 +10,7 @@
 <div id="tb">
   <h2>This lesson teaches you how to</h2>
   <ol>
+    <li><a href="#media">Determine Media Format Support</a></li>
     <li><a href="#dev-project">Setup a TV Project</a></li>
     <li><a href="#tv-libraries">Add TV Support Libraries</a></li>
     <li><a href="#build-it">Build TV Apps</a></li>
@@ -42,6 +43,18 @@
   minimum required changes to enable an app to run on TV devices.
 </p>
 
+<h2 id="media">Determine Media Format Support</h2>
+
+<p>See the following documentation for information about the codecs, protocols, and formats
+supported by Android TV.</p>
+
+<ul>
+  <li><a href="{@docRoot}guide/appendix/media-formats.html">Supported Media Formats</a></li>
+  <li><a class="external-link" href="https://source.android.com/devices/drm.html">DRM</a></li>
+  <li><code><a href="{@docRoot}reference/android/drm/package-summary.html">android.drm</a></code></li>
+  <li><a href="{@docRoot}guide/topics/media/exoplayer.html">ExoPlayer</a></li>
+  <li>{@link android.media.MediaPlayer android.media.MediaPlayer}</li>
+</ul>
 
 <h2 id="dev-project">Set up a TV Project</h2>
 
@@ -284,9 +297,15 @@
     TV devices.
   </li>
   <li>
-    <a href="{@docRoot}training/tv/games/index.html">Games for TV</a> - TV devices are a great
+    <a href="{@docRoot}training/tv/games/index.html">Building TV Games</a> - TV devices are a great
     platform for games. See this topic for information on building great game experiences for TV.
   </li>
+    <li>
+    <a href="{@docRoot}training/tv/tif/index.html">Building Live TV Apps</a> - Present your video
+    content in a linear, "broadcast TV" style with channels and programs that your users can access
+    through a program guide as well as the channel up/down buttons.
+  </li>
+
 </ul>
 
 
diff --git a/keystore/java/android/security/KeyStoreCipherSpi.java b/keystore/java/android/security/KeyStoreCipherSpi.java
index 37e00b2..1f8d8ec 100644
--- a/keystore/java/android/security/KeyStoreCipherSpi.java
+++ b/keystore/java/android/security/KeyStoreCipherSpi.java
@@ -152,29 +152,58 @@
 
     @Override
     protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
-        init(opmode, key, random);
-        initAlgorithmSpecificParameters();
-        ensureKeystoreOperationInitialized();
+        resetAll();
+
+        boolean success = false;
+        try {
+            init(opmode, key, random);
+            initAlgorithmSpecificParameters();
+            ensureKeystoreOperationInitialized();
+            success = true;
+        } finally {
+            if (!success) {
+                resetAll();
+            }
+        }
     }
 
     @Override
     protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random)
             throws InvalidKeyException, InvalidAlgorithmParameterException {
-        init(opmode, key, random);
-        initAlgorithmSpecificParameters(params);
-        ensureKeystoreOperationInitialized();
+        resetAll();
+
+        boolean success = false;
+        try {
+            init(opmode, key, random);
+            initAlgorithmSpecificParameters(params);
+            ensureKeystoreOperationInitialized();
+            success = true;
+        } finally {
+            if (!success) {
+                resetAll();
+            }
+        }
     }
 
     @Override
     protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
             SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
-        init(opmode, key, random);
-        initAlgorithmSpecificParameters(params);
-        ensureKeystoreOperationInitialized();
+        resetAll();
+
+        boolean success = false;
+        try {
+            init(opmode, key, random);
+            initAlgorithmSpecificParameters(params);
+            ensureKeystoreOperationInitialized();
+            success = true;
+        } finally {
+            if (!success) {
+                resetAll();
+            }
+        }
     }
 
     private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
-        resetAll();
         if (!(key instanceof KeyStoreSecretKey)) {
             throw new InvalidKeyException(
                     "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
diff --git a/keystore/java/android/security/KeyStoreHmacSpi.java b/keystore/java/android/security/KeyStoreHmacSpi.java
index a19bbda..f8b6fef 100644
--- a/keystore/java/android/security/KeyStoreHmacSpi.java
+++ b/keystore/java/android/security/KeyStoreHmacSpi.java
@@ -69,9 +69,10 @@
     private final int mKeymasterDigest;
     private final int mMacSizeBytes;
 
-    private String mKeyAliasInKeyStore;
+    // Fields below are populated by engineInit and should be preserved after engineDoFinal.
+    private KeyStoreSecretKey mKey;
 
-    // The fields below are reset by the engineReset operation.
+    // Fields below are reset when engineDoFinal succeeds.
     private KeyStoreCryptoOperationChunkedStreamer mChunkedStreamer;
     private IBinder mOperationToken;
     private Long mOperationHandle;
@@ -89,28 +90,39 @@
     @Override
     protected void engineInit(Key key, AlgorithmParameterSpec params) throws InvalidKeyException,
             InvalidAlgorithmParameterException {
+        resetAll();
+
+        boolean success = false;
+        try {
+            init(key, params);
+            ensureKeystoreOperationInitialized();
+            success = true;
+        } finally {
+            if (!success) {
+                resetAll();
+            }
+        }
+    }
+
+    private void init(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);
         }
+        mKey = (KeyStoreSecretKey) key;
 
         if (params != null) {
             throw new InvalidAlgorithmParameterException(
                     "Unsupported algorithm parameters: " + params);
         }
 
-        mKeyAliasInKeyStore = ((KeyStoreSecretKey) key).getAlias();
-        if (mKeyAliasInKeyStore == null) {
-            throw new InvalidKeyException("Key's KeyStore alias not known");
-        }
-        engineReset();
-        ensureKeystoreOperationInitialized();
     }
 
-    @Override
-    protected void engineReset() {
+    private void resetAll() {
+        mKey = null;
         IBinder operationToken = mOperationToken;
         if (operationToken != null) {
             mOperationToken = null;
@@ -120,11 +132,26 @@
         mChunkedStreamer = null;
     }
 
+    private void resetWhilePreservingInitState() {
+        IBinder operationToken = mOperationToken;
+        if (operationToken != null) {
+            mOperationToken = null;
+            mKeyStore.abort(operationToken);
+        }
+        mOperationHandle = null;
+        mChunkedStreamer = null;
+    }
+
+    @Override
+    protected void engineReset() {
+        resetWhilePreservingInitState();
+    }
+
     private void ensureKeystoreOperationInitialized() {
         if (mChunkedStreamer != null) {
             return;
         }
-        if (mKeyAliasInKeyStore == null) {
+        if (mKey == null) {
             throw new IllegalStateException("Not initialized");
         }
 
@@ -132,7 +159,8 @@
         keymasterArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_HMAC);
         keymasterArgs.addInt(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest);
 
-        OperationResult opResult = mKeyStore.begin(mKeyAliasInKeyStore,
+        OperationResult opResult = mKeyStore.begin(
+                mKey.getAlias(),
                 KeymasterDefs.KM_PURPOSE_SIGN,
                 true,
                 keymasterArgs,
@@ -184,7 +212,7 @@
             throw KeyStore.getCryptoOperationException(e);
         }
 
-        engineReset();
+        resetWhilePreservingInitState();
         return result;
     }
 
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index f0e2f5c5..46d33b4 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -16,6 +16,7 @@
 
 package android.media.tv;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.content.Context;
@@ -424,7 +425,7 @@
      * @return a CharSequence containing the TV input's label. If the TV input does not have
      *         a label, its name is returned.
      */
-    public CharSequence loadLabel(Context context) {
+    public CharSequence loadLabel(@NonNull Context context) {
         if (TextUtils.isEmpty(mLabel)) {
             return mService.loadLabel(context.getPackageManager());
         } else {
@@ -452,7 +453,7 @@
      * @return a Drawable containing the TV input's icon. If the TV input does not have an icon,
      *         application's icon is returned. If it's unavailable too, {@code null} is returned.
      */
-    public Drawable loadIcon(Context context) {
+    public Drawable loadIcon(@NonNull Context context) {
         if (mIconUri == null) {
             return loadServiceIcon(context);
         }
@@ -506,7 +507,7 @@
      * @param flags The flags used for parceling.
      */
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString(mId);
         dest.writeString(mParentId);
         mService.writeToParcel(dest, flags);
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 105084e..0f265de 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -16,6 +16,7 @@
 
 package android.media.tv;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.graphics.Rect;
@@ -932,7 +933,7 @@
      * @return the {@link TvInputInfo} for a given TV input. {@code null} if not found.
      */
     @Nullable
-    public TvInputInfo getTvInputInfo(String inputId) {
+    public TvInputInfo getTvInputInfo(@NonNull String inputId) {
         if (inputId == null) {
             throw new IllegalArgumentException("inputId cannot be null");
         }
@@ -956,7 +957,7 @@
      * @param inputId The id of the TV input.
      * @throws IllegalArgumentException if the argument is {@code null}.
      */
-    public int getInputState(String inputId) {
+    public int getInputState(@NonNull String inputId) {
         if (inputId == null) {
             throw new IllegalArgumentException("inputId cannot be null");
         }
@@ -977,7 +978,7 @@
      * @param handler A {@link Handler} that the status change will be delivered to.
      * @throws IllegalArgumentException if any of the arguments is {@code null}.
      */
-    public void registerCallback(TvInputCallback callback, Handler handler) {
+    public void registerCallback(@NonNull TvInputCallback callback, @NonNull Handler handler) {
         if (callback == null) {
             throw new IllegalArgumentException("callback cannot be null");
         }
@@ -995,7 +996,7 @@
      * @param callback The existing callback to remove.
      * @throws IllegalArgumentException if any of the arguments is {@code null}.
      */
-    public void unregisterCallback(final TvInputCallback callback) {
+    public void unregisterCallback(@NonNull final TvInputCallback callback) {
         if (callback == null) {
             throw new IllegalArgumentException("callback cannot be null");
         }
@@ -1047,7 +1048,7 @@
      * @param rating The TV content rating to check.
      * @return {@code true} if the given TV content rating is blocked, {@code false} otherwise.
      */
-    public boolean isRatingBlocked(TvContentRating rating) {
+    public boolean isRatingBlocked(@NonNull TvContentRating rating) {
         if (rating == null) {
             throw new IllegalArgumentException("rating cannot be null");
         }
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 382ec91..4258534 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -16,6 +16,7 @@
 
 package android.media.tv;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
@@ -314,7 +315,7 @@
         @SystemApi
         public void notifySessionEvent(final String eventType, final Bundle eventArgs) {
             if (eventType == null) {
-                throw new IllegalArgumentException("eventType should not be null.");
+                throw new IllegalArgumentException("eventType cannot be null");
             }
             executeOrPostRunnable(new Runnable() {
                 @Override
@@ -544,7 +545,10 @@
          * @see #notifyContentAllowed
          * @see TvInputManager
          */
-        public void notifyContentBlocked(final TvContentRating rating) {
+        public void notifyContentBlocked(@NonNull final TvContentRating rating) {
+            if (rating == null) {
+                throw new IllegalArgumentException("rating cannot be null");
+            }
             executeOrPostRunnable(new Runnable() {
                 @Override
                 public void run() {
@@ -828,7 +832,7 @@
          * @hide
          */
         @SystemApi
-        public void onAppPrivateCommand(String action, Bundle data) {
+        public void onAppPrivateCommand(@NonNull String action, Bundle data) {
         }
 
         /**
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 6456049..d248b12 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -16,6 +16,7 @@
 
 package android.media.tv;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.Context;
@@ -273,7 +274,7 @@
      * @param inputId The ID of TV input which will play the given channel.
      * @param channelUri The URI of a channel.
      */
-    public void tune(String inputId, Uri channelUri) {
+    public void tune(@NonNull String inputId, Uri channelUri) {
         tune(inputId, channelUri, null);
     }
 
@@ -494,7 +495,7 @@
      * @hide
      */
     @SystemApi
-    public void sendAppPrivateCommand(String action, Bundle data) {
+    public void sendAppPrivateCommand(@NonNull String action, Bundle data) {
         if (TextUtils.isEmpty(action)) {
             throw new IllegalArgumentException("action cannot be null or an empty string");
         }
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index 6f33672..d71b44b 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -19,6 +19,7 @@
 import android.graphics.ImageFormat;
 import android.graphics.SurfaceTexture;
 import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.ICameraDeviceCallbacks;
@@ -170,7 +171,8 @@
         assertEquals(CameraBinderTestUtils.NO_ERROR, status);
         assertFalse(metadata.isEmpty());
 
-        CaptureRequest.Builder request = new CaptureRequest.Builder(metadata, /*reprocess*/false);
+        CaptureRequest.Builder request = new CaptureRequest.Builder(metadata, /*reprocess*/false,
+                CameraCaptureSession.SESSION_ID_NONE);
         assertFalse(request.isEmpty());
         assertFalse(metadata.isEmpty());
         if (needStream) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a48a4d9..4970e0f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5508,17 +5508,20 @@
             if (app.isolated) {
                 mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);
             }
-            app.kill(reason, true);
-            handleAppDiedLocked(app, true, allowRestart);
-            removeLruProcessLocked(app);
-
+            boolean willRestart = false;
             if (app.persistent && !app.isolated) {
                 if (!callerWillRestart) {
-                    addAppLocked(app.info, false, null /* ABI override */);
+                    willRestart = true;
                 } else {
                     needRestart = true;
                 }
             }
+            app.kill(reason, true);
+            handleAppDiedLocked(app, willRestart, allowRestart);
+            if (willRestart) {
+                removeLruProcessLocked(app);
+                addAppLocked(app.info, false, null /* ABI override */);
+            }
         } else {
             mRemovedProcesses.add(app);
         }
diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java
index db3ae91..c8e5c3a 100644
--- a/services/core/java/com/android/server/pm/KeySetManagerService.java
+++ b/services/core/java/com/android/server/pm/KeySetManagerService.java
@@ -488,7 +488,7 @@
     }
 
     public String encodePublicKey(PublicKey k) throws IOException {
-        return new String(Base64.encode(k.getEncoded(), 0));
+        return new String(Base64.encode(k.getEncoded(), Base64.NO_WRAP));
     }
 
     public void dumpLPr(PrintWriter pw, String packageName,
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index e914cd4..a210223 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -281,7 +281,7 @@
                 Slog.w(WindowManagerService.TAG, "removeAllWindows: removing win=" + win);
             }
 
-            service.removeWindowLocked(win.mSession, win);
+            service.removeWindowLocked(win);
         }
         allAppWindows.clear();
         windows.clear();
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index bb53534..c24fcb3 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -78,7 +78,7 @@
             WindowState windowState = (WindowState) inputWindowHandle.windowState;
             if (windowState != null) {
                 Slog.i(WindowManagerService.TAG, "WINDOW DIED " + windowState);
-                mService.removeWindowLocked(windowState.mSession, windowState);
+                mService.removeWindowLocked(windowState);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 38fc959..c5bdbb0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2660,11 +2660,11 @@
             if (win == null) {
                 return;
             }
-            removeWindowLocked(session, win);
+            removeWindowLocked(win);
         }
     }
 
-    public void removeWindowLocked(Session session, WindowState win) {
+    void removeWindowLocked(WindowState win) {
         if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
             if (DEBUG_STARTING_WINDOW) Slog.d(TAG, "Starting window removed " + win);
         }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index cd9689e..ec70879 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1169,10 +1169,10 @@
                     WindowState win = mService.windowForClientLocked(mSession, mClient, false);
                     Slog.i(TAG, "WIN DEATH: " + win);
                     if (win != null) {
-                        mService.removeWindowLocked(mSession, win);
+                        mService.removeWindowLocked(win);
                     } else if (mHasSurface) {
                         Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid.");
-                        mService.removeWindowLocked(mSession, WindowState.this);
+                        mService.removeWindowLocked(WindowState.this);
                     }
                 }
             } catch (IllegalArgumentException ex) {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index bbeee8c..b303505 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -83,7 +83,7 @@
             WindowState win = windows.get(winNdx);
             if (WindowManagerService.DEBUG_WINDOW_MOVEMENT) Slog.w(WindowManagerService.TAG,
                     "removeAllWindows: removing win=" + win);
-            win.mService.removeWindowLocked(win.mSession, win);
+            win.mService.removeWindowLocked(win);
         }
         windows.clear();
     }