Merge changes Ic573b522,I1e0bceaa into pi-preview1-androidx-dev

* changes:
  MediaSession2: Temporarily remove Javadoc references to hidden APIs
  MediaSession2: Unhide MediaSession2 and MediaController2
diff --git a/car/res/layout/car_list_item_seekbar_content.xml b/car/res/layout/car_list_item_seekbar_content.xml
index eedbe73..6e3e33a 100644
--- a/car/res/layout/car_list_item_seekbar_content.xml
+++ b/car/res/layout/car_list_item_seekbar_content.xml
@@ -33,6 +33,8 @@
         android:id="@+id/seek_bar_container"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/car_padding_1"
+        android:layout_marginBottom="@dimen/car_padding_1"
         android:orientation="vertical">
         <TextView
             android:id="@+id/text"
diff --git a/car/res/values/dimens.xml b/car/res/values/dimens.xml
index c310ec7..51810ad 100644
--- a/car/res/values/dimens.xml
+++ b/car/res/values/dimens.xml
@@ -144,7 +144,7 @@
 
     <!-- Seekbar -->
     <dimen name="car_seekbar_height">6dp</dimen>
-    <dimen name="car_seekbar_thumb_size">20dp</dimen>
+    <dimen name="car_seekbar_thumb_size">24dp</dimen>
     <dimen name="car_seekbar_thumb_stroke">1dp</dimen>
 
     <!-- Scroll Bar Thumb -->
diff --git a/jetifier/jetifier/core/src/main/resources/default.generated.config b/jetifier/jetifier/core/src/main/resources/default.generated.config
index 57590dd..33ddfb7 100644
--- a/jetifier/jetifier/core/src/main/resources/default.generated.config
+++ b/jetifier/jetifier/core/src/main/resources/default.generated.config
@@ -2110,7 +2110,7 @@
       "to": [
         {
           "groupId": "androidx.slice",
-          "artifactId": "slices-core",
+          "artifactId": "slice-core",
           "version": "1.0.0"
         }
       ]
@@ -2124,7 +2124,7 @@
       "to": [
         {
           "groupId": "androidx.slice",
-          "artifactId": "slices-builders",
+          "artifactId": "slice-builders",
           "version": "1.0.0"
         }
       ]
@@ -2138,7 +2138,7 @@
       "to": [
         {
           "groupId": "androidx.slice",
-          "artifactId": "slices-view",
+          "artifactId": "slice-view",
           "version": "1.0.0"
         }
       ]
diff --git a/media/src/androidTest/java/androidx/media/MediaPlayer2Test.java b/media/src/androidTest/java/androidx/media/MediaPlayer2Test.java
index a48af14..f565e8a 100644
--- a/media/src/androidTest/java/androidx/media/MediaPlayer2Test.java
+++ b/media/src/androidTest/java/androidx/media/MediaPlayer2Test.java
@@ -42,6 +42,8 @@
 
 import androidx.media.test.R;
 
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -53,6 +55,10 @@
 import java.util.List;
 import java.util.Vector;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
 
 @RunWith(AndroidJUnit4.class)
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
@@ -73,6 +79,7 @@
     private File mOutFile;
     private Camera mCamera;
 
+    @Before
     @Override
     public void setUp() throws Exception {
         super.setUp();
@@ -81,6 +88,7 @@
         mOutFile = new File(mRecordedFilePath);
     }
 
+    @After
     @Override
     public void tearDown() throws Exception {
         super.tearDown();
@@ -1767,7 +1775,7 @@
 
     @Test
     @LargeTest
-    public void testCallback() throws Throwable {
+    public void testMediaPlayer2Callback() throws Throwable {
         final int mp4Duration = 8484;
 
         if (!checkLoadResource(R.raw.testvideo)) {
@@ -1835,6 +1843,150 @@
         mPlayer.reset();
     }
 
+    @Test
+    @LargeTest
+    public void testPlayerStates() throws Throwable {
+        final int mp4Duration = 8484;
+
+        if (!checkLoadResource(R.raw.testvideo)) {
+            return; // skip;
+        }
+        mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
+
+        final Monitor prepareCompleted = new Monitor();
+        final Monitor playCompleted = new Monitor();
+        final Monitor pauseCompleted = new Monitor();
+
+        MediaPlayer2.MediaPlayer2EventCallback ecb = new MediaPlayer2.MediaPlayer2EventCallback() {
+            @Override
+            public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd, int what, int status) {
+                if (what == MediaPlayer2.CALL_COMPLETED_PREPARE) {
+                    prepareCompleted.signal();
+                } else if (what == MediaPlayer2.CALL_COMPLETED_PLAY) {
+                    playCompleted.signal();
+                } else if (what == MediaPlayer2.CALL_COMPLETED_PAUSE) {
+                    pauseCompleted.signal();
+                }
+            }
+        };
+        synchronized (mEventCbLock) {
+            mEventCallbacks.add(ecb);
+        }
+
+        assertEquals(MediaPlayerBase.BUFFERING_STATE_UNKNOWN, mPlayer.getBufferingState());
+        assertEquals(MediaPlayerBase.PLAYER_STATE_IDLE, mPlayer.getPlayerState());
+        prepareCompleted.reset();
+        mPlayer.prepare();
+        prepareCompleted.waitForSignal();
+        assertEquals(MediaPlayerBase.BUFFERING_STATE_BUFFERING_AND_PLAYABLE,
+                mPlayer.getBufferingState());
+        assertEquals(MediaPlayerBase.PLAYER_STATE_PAUSED, mPlayer.getPlayerState());
+
+        playCompleted.reset();
+        mPlayer.play();
+        playCompleted.waitForSignal();
+        assertEquals(MediaPlayerBase.BUFFERING_STATE_BUFFERING_AND_PLAYABLE,
+                mPlayer.getBufferingState());
+        assertEquals(MediaPlayerBase.PLAYER_STATE_PLAYING, mPlayer.getPlayerState());
+
+        pauseCompleted.reset();
+        mPlayer.pause();
+        pauseCompleted.waitForSignal();
+        assertEquals(MediaPlayerBase.BUFFERING_STATE_BUFFERING_AND_PLAYABLE,
+                mPlayer.getBufferingState());
+        assertEquals(MediaPlayerBase.PLAYER_STATE_PAUSED, mPlayer.getPlayerState());
+
+        mPlayer.reset();
+        assertEquals(MediaPlayerBase.BUFFERING_STATE_UNKNOWN, mPlayer.getBufferingState());
+        assertEquals(MediaPlayerBase.PLAYER_STATE_IDLE, mPlayer.getPlayerState());
+    }
+
+    @Test
+    @LargeTest
+    public void testPlayerEventCallback() throws Throwable {
+        final int mp4Duration = 8484;
+
+        if (!checkLoadResource(R.raw.testvideo)) {
+            return; // skip;
+        }
+
+        mPlayer.setSurface(mActivity.getSurfaceHolder().getSurface());
+
+        final Monitor onPrepareCalled = new Monitor();
+        final Monitor onSeekCompleteCalled = new Monitor();
+        final Monitor onPlayerStateChangedCalled = new Monitor();
+        final AtomicInteger playerState = new AtomicInteger();
+        final Monitor onBufferingStateChangedCalled = new Monitor();
+        final AtomicInteger bufferingState = new AtomicInteger();
+        final Monitor onPlaybackSpeedChanged = new Monitor();
+        final AtomicReference<Float> playbackSpeed = new AtomicReference<>();
+
+        MediaPlayerBase.PlayerEventCallback callback = new MediaPlayerBase.PlayerEventCallback() {
+            // TODO: implement and add test case for onCurrentDataSourceChanged() callback.
+            @Override
+            public void onMediaPrepared(MediaPlayerBase mpb, DataSourceDesc dsd) {
+                onPrepareCalled.signal();
+            }
+
+            @Override
+            public void onPlayerStateChanged(MediaPlayerBase mpb, int state) {
+                playerState.set(state);
+                onPlayerStateChangedCalled.signal();
+            }
+
+            @Override
+            public void onBufferingStateChanged(MediaPlayerBase mpb, DataSourceDesc dsd,
+                    int state) {
+                bufferingState.set(state);
+                onBufferingStateChangedCalled.signal();
+            }
+
+            @Override
+            public void onPlaybackSpeedChanged(MediaPlayerBase mpb, float speed) {
+                playbackSpeed.set(speed);
+                onPlaybackSpeedChanged.signal();
+            }
+
+            @Override
+            public void onSeekCompleted(MediaPlayerBase mpb, long position) {
+                onSeekCompleteCalled.signal();
+            }
+        };
+        ExecutorService executor = Executors.newFixedThreadPool(1);
+        mPlayer.registerPlayerEventCallback(executor, callback);
+
+        onPrepareCalled.reset();
+        onPlayerStateChangedCalled.reset();
+        onBufferingStateChangedCalled.reset();
+        mPlayer.prepare();
+        do {
+            assertTrue(onBufferingStateChangedCalled.waitForSignal(1000));
+        } while (bufferingState.get() != MediaPlayerBase.BUFFERING_STATE_BUFFERING_AND_STARVED);
+
+        assertTrue(onPrepareCalled.waitForSignal(1000));
+        do {
+            assertTrue(onPlayerStateChangedCalled.waitForSignal(1000));
+        } while (playerState.get() != MediaPlayerBase.PLAYER_STATE_PAUSED);
+        do {
+            assertTrue(onBufferingStateChangedCalled.waitForSignal(1000));
+        } while (bufferingState.get() != MediaPlayerBase.BUFFERING_STATE_BUFFERING_AND_PLAYABLE);
+
+        onSeekCompleteCalled.reset();
+        mPlayer.seekTo(mp4Duration >> 1, MediaPlayer2.SEEK_PREVIOUS_SYNC);
+        onSeekCompleteCalled.waitForSignal();
+
+        onPlaybackSpeedChanged.reset();
+        mPlayer.setPlaybackSpeed(0.5f);
+        do {
+            assertTrue(onPlaybackSpeedChanged.waitForSignal(1000));
+        } while (Math.abs(playbackSpeed.get() - 0.5f) > FLOAT_TOLERANCE);
+
+        mPlayer.reset();
+
+        mPlayer.unregisterPlayerEventCallback(callback);
+        executor.shutdown();
+    }
+
     public void testRecordAndPlay() throws Exception {
         if (!hasMicrophone()) {
             return;
diff --git a/media/src/androidTest/java/androidx/media/MediaPlayer2TestBase.java b/media/src/androidTest/java/androidx/media/MediaPlayer2TestBase.java
index 34c464b..215993a 100644
--- a/media/src/androidTest/java/androidx/media/MediaPlayer2TestBase.java
+++ b/media/src/androidTest/java/androidx/media/MediaPlayer2TestBase.java
@@ -30,6 +30,8 @@
 import android.support.test.rule.ActivityTestRule;
 import android.view.SurfaceHolder;
 
+import androidx.annotation.CallSuper;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -255,6 +257,7 @@
     }
 
     @Before
+    @CallSuper
     public void setUp() throws Exception {
         mActivity = mActivityRule.getActivity();
         try {
@@ -277,6 +280,7 @@
     }
 
     @After
+    @CallSuper
     public void tearDown() throws Exception {
         if (mPlayer != null) {
             mPlayer.close();
diff --git a/media/src/main/java/androidx/media/MediaPlayer2Impl.java b/media/src/main/java/androidx/media/MediaPlayer2Impl.java
index 59056a5..3b3e119 100644
--- a/media/src/main/java/androidx/media/MediaPlayer2Impl.java
+++ b/media/src/main/java/androidx/media/MediaPlayer2Impl.java
@@ -38,6 +38,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pair;
 import android.view.Surface;
@@ -74,10 +75,40 @@
     private static final int NEXT_SOURCE_STATE_PREPARING = 1;
     private static final int NEXT_SOURCE_STATE_PREPARED = 2;
 
-    // TODO: This class has too many locks. Use one single lock to protect internal variables.
-    private MediaPlayer mPlayer;
-    private int mPlayerState;
-    private AudioAttributesCompat mAudioAttributes;
+    private static ArrayMap<Integer, Integer> sInfoEventMap;
+    private static ArrayMap<Integer, Integer> sErrorEventMap;
+
+    static {
+        sInfoEventMap = new ArrayMap<>();
+        sInfoEventMap.put(MediaPlayer.MEDIA_INFO_UNKNOWN, MEDIA_INFO_UNKNOWN);
+        sInfoEventMap.put(2 /*MediaPlayer.MEDIA_INFO_STARTED_AS_NEXT*/, MEDIA_INFO_STARTED_AS_NEXT);
+        sInfoEventMap.put(
+                MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START, MEDIA_INFO_VIDEO_RENDERING_START);
+        sInfoEventMap.put(
+                MediaPlayer.MEDIA_INFO_VIDEO_TRACK_LAGGING, MEDIA_INFO_VIDEO_TRACK_LAGGING);
+        sInfoEventMap.put(MediaPlayer.MEDIA_INFO_BUFFERING_START, MEDIA_INFO_BUFFERING_START);
+        sInfoEventMap.put(MediaPlayer.MEDIA_INFO_BUFFERING_END, MEDIA_INFO_BUFFERING_END);
+        sInfoEventMap.put(MediaPlayer.MEDIA_INFO_BAD_INTERLEAVING, MEDIA_INFO_BAD_INTERLEAVING);
+        sInfoEventMap.put(MediaPlayer.MEDIA_INFO_NOT_SEEKABLE, MEDIA_INFO_NOT_SEEKABLE);
+        sInfoEventMap.put(MediaPlayer.MEDIA_INFO_METADATA_UPDATE, MEDIA_INFO_METADATA_UPDATE);
+        sInfoEventMap.put(MediaPlayer.MEDIA_INFO_AUDIO_NOT_PLAYING, MEDIA_INFO_AUDIO_NOT_PLAYING);
+        sInfoEventMap.put(MediaPlayer.MEDIA_INFO_VIDEO_NOT_PLAYING, MEDIA_INFO_VIDEO_NOT_PLAYING);
+        sInfoEventMap.put(
+                MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, MEDIA_INFO_UNSUPPORTED_SUBTITLE);
+        sInfoEventMap.put(MediaPlayer.MEDIA_INFO_SUBTITLE_TIMED_OUT, MEDIA_INFO_SUBTITLE_TIMED_OUT);
+
+        sErrorEventMap = new ArrayMap<>();
+        sErrorEventMap.put(MediaPlayer.MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNKNOWN);
+        sErrorEventMap.put(
+                MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK,
+                MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK);
+        sErrorEventMap.put(MediaPlayer.MEDIA_ERROR_IO, MEDIA_ERROR_IO);
+        sErrorEventMap.put(MediaPlayer.MEDIA_ERROR_MALFORMED, MEDIA_ERROR_MALFORMED);
+        sErrorEventMap.put(MediaPlayer.MEDIA_ERROR_UNSUPPORTED, MEDIA_ERROR_UNSUPPORTED);
+        sErrorEventMap.put(MediaPlayer.MEDIA_ERROR_TIMED_OUT, MEDIA_ERROR_TIMED_OUT);
+    }
+
+    private MediaPlayer mPlayer;  // MediaPlayer is thread-safe.
 
     private final Object mSrcLock = new Object();
     //--- guarded by |mSrcLock| start
@@ -102,15 +133,18 @@
     @GuardedBy("mTaskLock")
     private Task mCurrentTask;
 
-    private final Object mEventCbLock = new Object();
-    @GuardedBy("mEventCbLock")
-    private ArrayList<Pair<Executor, MediaPlayer2EventCallback>> mEventCallbackRecords =
+    private final Object mLock = new Object();
+    //--- guarded by |mLock| start
+    @PlayerState private int mPlayerState;
+    @BuffState private int mBufferingState;
+    private AudioAttributesCompat mAudioAttributes;
+    private ArrayList<Pair<Executor, MediaPlayer2EventCallback>> mMp2EventCallbackRecords =
             new ArrayList<>();
-
-    private final Object mDrmEventCbLock = new Object();
-    @GuardedBy("mDrmEventCbLock")
+    private ArrayMap<PlayerEventCallback, Executor> mPlayerEventCallbackMap =
+            new ArrayMap<>();
     private ArrayList<Pair<Executor, DrmEventCallback>> mDrmEventCallbackRecords =
             new ArrayList<>();
+    //--- guarded by |mLock| end
 
     /**
      * Default constructor.
@@ -124,7 +158,8 @@
         Looper looper = mHandlerThread.getLooper();
         mTaskHandler = new Handler(looper);
         mPlayer = new MediaPlayer();
-        mPlayerState = MediaPlayerBase.PLAYER_STATE_IDLE;
+        mPlayerState = PLAYER_STATE_IDLE;
+        mBufferingState = BUFFERING_STATE_UNKNOWN;
         setUpListeners();
     }
 
@@ -170,7 +205,7 @@
             @Override
             void process() {
                 mPlayer.start();
-                mPlayerState = PLAYER_STATE_PLAYING;
+                setPlayerState(PLAYER_STATE_PLAYING);
             }
         });
     }
@@ -191,6 +226,7 @@
             @Override
             void process() throws IOException {
                 mPlayer.prepareAsync();
+                setBufferingState(BUFFERING_STATE_BUFFERING_AND_STARVED);
             }
         });
     }
@@ -198,8 +234,7 @@
     /**
      * Pauses playback. Call play() to resume.
      *
-     * @throws IllegalStateException if the internal player engine has not been
-     * initialized.
+     * @throws IllegalStateException if the internal player engine has not been initialized.
      */
     @Override
     public void pause() {
@@ -207,7 +242,7 @@
             @Override
             void process() {
                 mPlayer.pause();
-                mPlayerState = PLAYER_STATE_PAUSED;
+                setPlayerState(PLAYER_STATE_PAUSED);
             }
         });
     }
@@ -241,7 +276,7 @@
      * Gets the duration of the file.
      *
      * @return the duration in milliseconds, if no duration is available
-     *         (for example, if streaming live content), -1 is returned.
+     * (for example, if streaming live content), -1 is returned.
      */
     @Override
     public long getDuration() {
@@ -265,7 +300,9 @@
 
     @Override
     public @PlayerState int getPlayerState() {
-        return mPlayerState;
+        synchronized (mLock) {
+            return mPlayerState;
+        }
     }
 
     /**
@@ -275,8 +312,9 @@
      */
     @Override
     public @BuffState int getBufferingState() {
-        // TODO: use cached state or call native function.
-        return BUFFERING_STATE_UNKNOWN;
+        synchronized (mLock) {
+            return mBufferingState;
+        }
     }
 
     /**
@@ -292,15 +330,21 @@
         addTask(new Task(CALL_COMPLETED_SET_AUDIO_ATTRIBUTES, false) {
             @Override
             void process() {
-                mAudioAttributes = attributes;
-                mPlayer.setAudioAttributes((AudioAttributes) mAudioAttributes.unwrap());
+                AudioAttributes attr;
+                synchronized (mLock) {
+                    mAudioAttributes = attributes;
+                    attr = (AudioAttributes) mAudioAttributes.unwrap();
+                }
+                mPlayer.setAudioAttributes(attr);
             }
         });
     }
 
     @Override
     public @NonNull AudioAttributesCompat getAudioAttributes() {
-        return mAudioAttributes;
+        synchronized (mLock) {
+            return mAudioAttributes;
+        }
     }
 
     /**
@@ -437,7 +481,7 @@
         addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_SPEED, false) {
             @Override
             void process() {
-                mPlayer.setPlaybackParams(getPlaybackParams().setSpeed(speed));
+                setPlaybackParamsInternal(getPlaybackParams().setSpeed(speed));
             }
         });
     }
@@ -508,8 +552,17 @@
      */
     @Override
     public void registerPlayerEventCallback(@NonNull Executor e,
-                                            @NonNull PlayerEventCallback cb) {
-        // TODO: implement this.
+            @NonNull PlayerEventCallback cb) {
+        if (cb == null) {
+            throw new IllegalArgumentException("Illegal null PlayerEventCallback");
+        }
+        if (e == null) {
+            throw new IllegalArgumentException(
+                    "Illegal null Executor for the PlayerEventCallback");
+        }
+        synchronized (mLock) {
+            mPlayerEventCallbackMap.put(cb, e);
+        }
     }
 
     /**
@@ -518,7 +571,12 @@
      */
     @Override
     public void unregisterPlayerEventCallback(@NonNull PlayerEventCallback cb) {
-        // TODO: implement this.
+        if (cb == null) {
+            throw new IllegalArgumentException("Illegal null PlayerEventCallback");
+        }
+        synchronized (mLock) {
+            mPlayerEventCallbackMap.remove(cb);
+        }
     }
 
     @Override
@@ -526,18 +584,12 @@
         addTask(new Task(CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED, false) {
             @Override
             void process() {
-                synchronized (mEventCbLock) {
-                    for (final Pair<Executor, MediaPlayer2EventCallback> cb
-                            : mEventCallbackRecords) {
-                        cb.first.execute(new Runnable() {
-                            @Override
-                            public void run() {
-                                cb.second.onCommandLabelReached(
-                                        MediaPlayer2Impl.this, label);
-                            }
-                        });
+                notifyMediaPlayer2Event(new Mp2EventNotifier() {
+                    @Override
+                    public void notify(MediaPlayer2EventCallback cb) {
+                        cb.onCommandLabelReached(MediaPlayer2Impl.this, label);
                     }
-                }
+                });
             }
         });
     }
@@ -690,7 +742,6 @@
      * non-zero speed is equivalent to calling play().
      *
      * @param params the playback params.
-     *
      * @throws IllegalStateException if the internal player engine has not been
      * initialized or has been released.
      * @throws IllegalArgumentException if params is not supported.
@@ -700,7 +751,7 @@
         addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_PARAMS, false) {
             @Override
             void process() {
-                mPlayer.setPlaybackParams(params);
+                setPlaybackParamsInternal(params);
             }
         });
     }
@@ -722,7 +773,6 @@
      * Sets A/V sync mode.
      *
      * @param params the A/V sync params to apply
-     *
      * @throws IllegalStateException if the internal player engine has not been
      * initialized.
      * @throws IllegalArgumentException if params are not supported.
@@ -741,9 +791,8 @@
      * Gets the A/V sync mode.
      *
      * @return the A/V sync params
-     *
      * @throws IllegalStateException if the internal player engine has not been
-     * initialized.
+     *                               initialized.
      */
     @Override
     @NonNull
@@ -805,8 +854,7 @@
      * {@link MediaTimestamp#getAnchorMediaTimeUs} can be used as current playback position.
      *
      * @return a MediaTimestamp object if a timestamp is available, or {@code null} if no timestamp
-     *         is available, e.g. because the media player has not been initialized.
-     *
+     * is available, e.g. because the media player has not been initialized.
      * @see MediaTimestamp
      */
     @Override
@@ -823,6 +871,8 @@
     @Override
     public void reset() {
         mPlayer.reset();
+        setPlayerState(PLAYER_STATE_IDLE);
+        setBufferingState(BUFFERING_STATE_UNKNOWN);
         /* FIXME: reset other internal variables. */
     }
 
@@ -1092,11 +1142,11 @@
      * In addition, the support for selecting an audio track at runtime is pretty limited
      * in that an audio track can only be selected in the <em>Prepared</em> state.
      * </p>
+     *
      * @param index the index of the track to be selected. The valid range of the index
      * is 0..total number of track - 1. The total number of tracks as well as the type of
      * each individual track can be found by calling {@link #getTrackInfo()} method.
      * @throws IllegalStateException if called in an invalid state.
-     *
      * @see MediaPlayer2#getTrackInfo
      */
     @Override
@@ -1116,11 +1166,11 @@
      * deselected. If the timed text track identified by index has not been
      * selected before, it throws an exception.
      * </p>
+     *
      * @param index the index of the track to be deselected. The valid range of the index
      * is 0..total number of tracks - 1. The total number of tracks as well as the type of
      * each individual track can be found by calling {@link #getTrackInfo()} method.
      * @throws IllegalStateException if called in an invalid state.
-     *
      * @see MediaPlayer2#getTrackInfo
      */
     @Override
@@ -1142,7 +1192,7 @@
      */
     @Override
     public void setMediaPlayer2EventCallback(@NonNull Executor executor,
-                                             @NonNull MediaPlayer2EventCallback eventCallback) {
+            @NonNull MediaPlayer2EventCallback eventCallback) {
         if (eventCallback == null) {
             throw new IllegalArgumentException("Illegal null MediaPlayer2EventCallback");
         }
@@ -1150,8 +1200,8 @@
             throw new IllegalArgumentException(
                     "Illegal null Executor for the MediaPlayer2EventCallback");
         }
-        synchronized (mEventCbLock) {
-            mEventCallbackRecords.add(new Pair(executor, eventCallback));
+        synchronized (mLock) {
+            mMp2EventCallbackRecords.add(new Pair(executor, eventCallback));
         }
     }
 
@@ -1160,8 +1210,8 @@
      */
     @Override
     public void clearMediaPlayer2EventCallback() {
-        synchronized (mEventCbLock) {
-            mEventCallbackRecords.clear();
+        synchronized (mLock) {
+            mMp2EventCallbackRecords.clear();
         }
     }
 
@@ -1203,7 +1253,7 @@
             throw new IllegalArgumentException(
                     "Illegal null Executor for the MediaPlayer2EventCallback");
         }
-        synchronized (mDrmEventCbLock) {
+        synchronized (mLock) {
             mDrmEventCallbackRecords.add(new Pair(executor, eventCallback));
         }
     }
@@ -1213,7 +1263,7 @@
      */
     @Override
     public void clearDrmEventCallback() {
-        synchronized (mDrmEventCbLock) {
+        synchronized (mLock) {
             mDrmEventCallbackRecords.clear();
         }
     }
@@ -1257,15 +1307,14 @@
      *
      * @param uuid The UUID of the crypto scheme. If not known beforehand, it can be retrieved
      * from the source through {@code getDrmInfo} or registering a {@code onDrmInfoListener}.
-     *
-     * @throws IllegalStateException              if called before prepare(), or the DRM was
-     *                                            prepared already
-     * @throws UnsupportedSchemeException         if the crypto scheme is not supported
-     * @throws ResourceBusyException              if required DRM resources are in use
-     * @throws ProvisioningNetworkErrorException  if provisioning is required but failed due to a
-     *                                            network error
-     * @throws ProvisioningServerErrorException   if provisioning is required but failed due to
-     *                                            the request denied by the provisioning server
+     * @throws IllegalStateException             if called before prepare(), or the DRM was
+     *                                           prepared already
+     * @throws UnsupportedSchemeException        if the crypto scheme is not supported
+     * @throws ResourceBusyException             if required DRM resources are in use
+     * @throws ProvisioningNetworkErrorException if provisioning is required but failed due to a
+     *                                           network error
+     * @throws ProvisioningServerErrorException  if provisioning is required but failed due to
+     *                                           the request denied by the provisioning server
      */
     @Override
     public void prepareDrm(@NonNull UUID uuid)
@@ -1410,6 +1459,8 @@
     /**
      * Read a DRM engine plugin String property value, given the property name string.
      * <p>
+     *
+
      * @param propertyName the property name
      *
      * Standard fields names are:
@@ -1431,6 +1482,7 @@
     /**
      * Set a DRM engine plugin String property value.
      * <p>
+     *
      * @param propertyName the property name
      * @param value the property value
      *
@@ -1449,23 +1501,108 @@
         }
     }
 
+    private void setPlaybackParamsInternal(final PlaybackParams params) {
+        PlaybackParams current = mPlayer.getPlaybackParams();
+        mPlayer.setPlaybackParams(params);
+        if (Math.abs(current.getSpeed() - params.getSpeed()) > 0.0001f) {
+            notifyPlayerEvent(new PlayerEventNotifier() {
+                @Override
+                public void notify(PlayerEventCallback cb) {
+                    cb.onPlaybackSpeedChanged(MediaPlayer2Impl.this, params.getSpeed());
+                }
+            });
+        }
+    }
+
+    private void setPlayerState(@PlayerState final int state) {
+        synchronized (mLock) {
+            if (mPlayerState == state) {
+                return;
+            }
+            mPlayerState = state;
+        }
+        notifyPlayerEvent(new PlayerEventNotifier() {
+            @Override
+            public void notify(PlayerEventCallback cb) {
+                cb.onPlayerStateChanged(MediaPlayer2Impl.this, state);
+            }
+        });
+    }
+
+    private void setBufferingState(@BuffState final int state) {
+        synchronized (mLock) {
+            if (mBufferingState == state) {
+                return;
+            }
+            mBufferingState = state;
+        }
+        notifyPlayerEvent(new PlayerEventNotifier() {
+            @Override
+            public void notify(PlayerEventCallback cb) {
+                cb.onBufferingStateChanged(MediaPlayer2Impl.this, mCurrentDSD, state);
+            }
+        });
+    }
+
+    private void notifyMediaPlayer2Event(final Mp2EventNotifier notifier) {
+        List<Pair<Executor, MediaPlayer2EventCallback>> records;
+        synchronized (mLock) {
+            records = new ArrayList<>(mMp2EventCallbackRecords);
+        }
+        for (final Pair<Executor, MediaPlayer2EventCallback> record : records) {
+            record.first.execute(new Runnable() {
+                @Override
+                public void run() {
+                    notifier.notify(record.second);
+                }
+            });
+        }
+    }
+
+    private void notifyPlayerEvent(final PlayerEventNotifier notifier) {
+        ArrayMap<PlayerEventCallback, Executor> map;
+        synchronized (mLock) {
+            map = new ArrayMap<>(mPlayerEventCallbackMap);
+        }
+        final int callbackCount = map.size();
+        for (int i = 0; i < callbackCount; i++) {
+            final Executor executor = map.valueAt(i);
+            final PlayerEventCallback cb = map.keyAt(i);
+            executor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    notifier.notify(cb);
+                }
+            });
+        }
+    }
+
+    private interface Mp2EventNotifier {
+        void notify(MediaPlayer2EventCallback callback);
+    }
+
+    private interface PlayerEventNotifier {
+        void notify(PlayerEventCallback callback);
+    }
+
     private void setUpListeners() {
         mPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
             @Override
             public void onPrepared(MediaPlayer mp) {
-                mPlayerState = PLAYER_STATE_PAUSED;
-                synchronized (mEventCbLock) {
-                    for (final Pair<Executor, MediaPlayer2EventCallback> cb :
-                            mEventCallbackRecords) {
-                        cb.first.execute(new Runnable() {
-                            @Override
-                            public void run() {
-                                cb.second.onInfo(MediaPlayer2Impl.this, mCurrentDSD,
-                                        MEDIA_INFO_PREPARED, 0);
-                            }
-                        });
+                setPlayerState(PLAYER_STATE_PAUSED);
+                setBufferingState(BUFFERING_STATE_BUFFERING_AND_PLAYABLE);
+                notifyMediaPlayer2Event(new Mp2EventNotifier() {
+                    @Override
+                    public void notify(MediaPlayer2EventCallback callback) {
+                        callback.onInfo(MediaPlayer2Impl.this, mCurrentDSD, MEDIA_INFO_PREPARED, 0);
                     }
-                }
+                });
+                notifyPlayerEvent(new PlayerEventNotifier() {
+                    @Override
+                    public void notify(PlayerEventCallback cb) {
+                        cb.onMediaPrepared(MediaPlayer2Impl.this, mCurrentDSD);
+                    }
+                });
                 synchronized (mTaskLock) {
                     if (mCurrentTask != null
                             && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE
@@ -1481,37 +1618,32 @@
         mPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
             @Override
             public void onVideoSizeChanged(MediaPlayer mp, final int width, final int height) {
-                synchronized (mEventCbLock) {
-                    for (final Pair<Executor, MediaPlayer2EventCallback> cb :
-                            mEventCallbackRecords) {
-                        cb.first.execute(new Runnable() {
-                            @Override
-                            public void run() {
-                                cb.second.onVideoSizeChanged(MediaPlayer2Impl.this, mCurrentDSD,
-                                        width, height);
-                            }
-                        });
+                notifyMediaPlayer2Event(new Mp2EventNotifier() {
+                    @Override
+                    public void notify(MediaPlayer2EventCallback cb) {
+                        cb.onVideoSizeChanged(MediaPlayer2Impl.this, mCurrentDSD, width, height);
                     }
-                }
+                });
             }
         });
         mPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() {
             @Override
             public boolean onInfo(MediaPlayer mp, int what, int extra) {
-                switch(what) {
+                switch (what) {
                     case MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START:
-                        synchronized (mEventCbLock) {
-                            for (final Pair<Executor, MediaPlayer2EventCallback> cb :
-                                    mEventCallbackRecords) {
-                                cb.first.execute(new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        cb.second.onInfo(MediaPlayer2Impl.this, mCurrentDSD,
-                                                MEDIA_INFO_VIDEO_RENDERING_START, 0);
-                                    }
-                                });
+                        notifyMediaPlayer2Event(new Mp2EventNotifier() {
+                            @Override
+                            public void notify(MediaPlayer2EventCallback cb) {
+                                cb.onInfo(MediaPlayer2Impl.this, mCurrentDSD,
+                                        MEDIA_INFO_VIDEO_RENDERING_START, 0);
                             }
-                        }
+                        });
+                        break;
+                    case MediaPlayer.MEDIA_INFO_BUFFERING_START:
+                        setBufferingState(BUFFERING_STATE_BUFFERING_AND_STARVED);
+                        break;
+                    case MediaPlayer.MEDIA_INFO_BUFFERING_END:
+                        setBufferingState(BUFFERING_STATE_BUFFERING_AND_PLAYABLE);
                         break;
                 }
                 return false;
@@ -1520,38 +1652,28 @@
         mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
             @Override
             public void onCompletion(MediaPlayer mp) {
-                mPlayerState = PLAYER_STATE_PAUSED;
-                synchronized (mEventCbLock) {
-                    for (final Pair<Executor, MediaPlayer2EventCallback> cb :
-                            mEventCallbackRecords) {
-                        cb.first.execute(new Runnable() {
-                            @Override
-                            public void run() {
-                                cb.second.onInfo(MediaPlayer2Impl.this, mCurrentDSD,
-                                        MEDIA_INFO_PLAYBACK_COMPLETE, 0);
-                            }
-                        });
+                setPlayerState(PLAYER_STATE_PAUSED);
+                notifyMediaPlayer2Event(new Mp2EventNotifier() {
+                    @Override
+                    public void notify(MediaPlayer2EventCallback cb) {
+                        cb.onInfo(MediaPlayer2Impl.this, mCurrentDSD, MEDIA_INFO_PLAYBACK_COMPLETE,
+                                0);
                     }
-                }
+                });
             }
         });
         mPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
             @Override
             public boolean onError(MediaPlayer mp, final int what, final int extra) {
-                mPlayerState = PLAYER_STATE_ERROR;
-                synchronized (mEventCbLock) {
-                    for (final Pair<Executor, MediaPlayer2EventCallback> cb :
-                            mEventCallbackRecords) {
-                        cb.first.execute(new Runnable() {
-                            @Override
-                            public void run() {
-                                // TODO: translate what value to MP2's definition.
-                                cb.second.onError(MediaPlayer2Impl.this, mCurrentDSD,
-                                        what, extra);
-                            }
-                        });
+                setPlayerState(PLAYER_STATE_ERROR);
+                setBufferingState(BUFFERING_STATE_UNKNOWN);
+                notifyMediaPlayer2Event(new Mp2EventNotifier() {
+                    @Override
+                    public void notify(MediaPlayer2EventCallback cb) {
+                        int w = sErrorEventMap.getOrDefault(what, MEDIA_ERROR_UNKNOWN);
+                        cb.onError(MediaPlayer2Impl.this, mCurrentDSD, w, extra);
                     }
-                }
+                });
                 return true;
             }
         });
@@ -1567,60 +1689,57 @@
                         processPendingTask_l();
                     }
                 }
+                final long seekPos = getCurrentPosition();
+                notifyPlayerEvent(new PlayerEventNotifier() {
+                    @Override
+                    public void notify(PlayerEventCallback cb) {
+                        // TODO: The actual seeked position might be different from the
+                        // requested position. Clarify which one is expected here.
+                        cb.onSeekCompleted(MediaPlayer2Impl.this, seekPos);
+                    }
+                });
             }
         });
         mPlayer.setOnTimedMetaDataAvailableListener(
                 new MediaPlayer.OnTimedMetaDataAvailableListener() {
                     @Override
                     public void onTimedMetaDataAvailable(MediaPlayer mp, final TimedMetaData data) {
-                        synchronized (mEventCbLock) {
-                            for (final Pair<Executor, MediaPlayer2EventCallback> cb :
-                                    mEventCallbackRecords) {
-                                cb.first.execute(new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        cb.second.onTimedMetaDataAvailable(MediaPlayer2Impl.this,
-                                                mCurrentDSD, data);
-                                    }
-                                });
+                        notifyMediaPlayer2Event(new Mp2EventNotifier() {
+                            @Override
+                            public void notify(MediaPlayer2EventCallback cb) {
+                                cb.onTimedMetaDataAvailable(
+                                        MediaPlayer2Impl.this, mCurrentDSD, data);
                             }
-                        }
+                        });
                     }
                 });
         mPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() {
             @Override
             public boolean onInfo(MediaPlayer mp, final int what, final int extra) {
-                synchronized (mEventCbLock) {
-                    for (final Pair<Executor, MediaPlayer2EventCallback> cb :
-                            mEventCallbackRecords) {
-                        cb.first.execute(new Runnable() {
-                            @Override
-                            public void run() {
-                                // TODO: translate what value to MP2's definition.
-                                cb.second.onInfo(MediaPlayer2Impl.this, mCurrentDSD, what, extra);
-                            }
-                        });
+                notifyMediaPlayer2Event(new Mp2EventNotifier() {
+                    @Override
+                    public void notify(MediaPlayer2EventCallback cb) {
+                        int w = sInfoEventMap.getOrDefault(what, MEDIA_INFO_UNKNOWN);
+                        cb.onInfo(MediaPlayer2Impl.this, mCurrentDSD, w, extra);
                     }
-                }
+                });
                 return true;
             }
         });
         mPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {
             @Override
             public void onBufferingUpdate(MediaPlayer mp, final int percent) {
-                synchronized (mEventCbLock) {
-                    mBufferedPercentageCurrent.set(percent);
-                    for (final Pair<Executor, MediaPlayer2EventCallback> cb :
-                            mEventCallbackRecords) {
-                        cb.first.execute(new Runnable() {
-                            @Override
-                            public void run() {
-                                cb.second.onInfo(MediaPlayer2Impl.this, mCurrentDSD,
-                                        MEDIA_INFO_BUFFERING_UPDATE, percent);
-                            }
-                        });
-                    }
+                if (percent >= 100) {
+                    setBufferingState(BUFFERING_STATE_BUFFERING_COMPLETE);
                 }
+                mBufferedPercentageCurrent.set(percent);
+                notifyMediaPlayer2Event(new Mp2EventNotifier() {
+                    @Override
+                    public void notify(MediaPlayer2EventCallback cb) {
+                        cb.onInfo(MediaPlayer2Impl.this, mCurrentDSD,
+                                MEDIA_INFO_BUFFERING_UPDATE, percent);
+                    }
+                });
             }
         });
     }
@@ -1737,9 +1856,9 @@
                 subset = Arrays.copyOfRange(pssh, i, i + dataLenSize);
                 int datalen = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN)
                         ? ((subset[3] & 0xff) << 24) | ((subset[2] & 0xff) << 16)
-                                | ((subset[1] & 0xff) <<  8) |  (subset[0] & 0xff)
+                        | ((subset[1] & 0xff) << 8) | (subset[0] & 0xff)
                         : ((subset[0] & 0xff) << 24) | ((subset[1] & 0xff) << 16)
-                                | ((subset[2] & 0xff) <<  8) |  (subset[3] & 0xff);
+                                | ((subset[2] & 0xff) << 8) | (subset[3] & 0xff);
                 i += dataLenSize;
                 len -= dataLenSize;
 
@@ -1851,17 +1970,13 @@
             if (mMediaCallType == CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED) {
                 return;
             }
-            synchronized (mEventCbLock) {
-                for (final Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) {
-                    cb.first.execute(new Runnable() {
-                        @Override
-                        public void run() {
-                            cb.second.onCallCompleted(
-                                    MediaPlayer2Impl.this, mDSD, mMediaCallType, status);
-                        }
-                    });
+            notifyMediaPlayer2Event(new Mp2EventNotifier() {
+                @Override
+                public void notify(MediaPlayer2EventCallback cb) {
+                    cb.onCallCompleted(
+                            MediaPlayer2Impl.this, mDSD, mMediaCallType, status);
                 }
-            }
+            });
         }
     };
 }
diff --git a/recyclerview-selection/api/current.txt b/recyclerview-selection/api/current.txt
index 3d9bdf5..72e22ef 100644
--- a/recyclerview-selection/api/current.txt
+++ b/recyclerview-selection/api/current.txt
@@ -97,7 +97,7 @@
     method public abstract boolean clearSelection();
     method public abstract void copySelection(androidx.recyclerview.selection.MutableSelection<K>);
     method public abstract boolean deselect(K);
-    method public abstract androidx.recyclerview.selection.Selection getSelection();
+    method public abstract androidx.recyclerview.selection.Selection<K> getSelection();
     method public abstract boolean hasSelection();
     method public abstract boolean isSelected(K);
     method public abstract void onRestoreInstanceState(android.os.Bundle);
diff --git a/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/DefaultSelectionTrackerTest.java b/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/DefaultSelectionTrackerTest.java
index 525c264..f7512ca 100644
--- a/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/DefaultSelectionTrackerTest.java
+++ b/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/DefaultSelectionTrackerTest.java
@@ -303,7 +303,7 @@
 
     @Test
     public void testProvisionalSelection() {
-        Selection s = mTracker.getSelection();
+        Selection<String> s = mTracker.getSelection();
         mSelection.assertNoSelection();
 
         // Mimicking band selection case -- BandController notifies item callback by itself.
@@ -319,7 +319,7 @@
 
     @Test
     public void testProvisionalSelection_Replace() {
-        Selection s = mTracker.getSelection();
+        Selection<String> s = mTracker.getSelection();
 
         // Mimicking band selection case -- BandController notifies item callback by itself.
         mListener.onItemStateChanged(mItems.get(1), true);
@@ -343,7 +343,7 @@
 
     @Test
     public void testProvisionalSelection_IntersectsExistingProvisionalSelection() {
-        Selection s = mTracker.getSelection();
+        Selection<String> s = mTracker.getSelection();
 
         // Mimicking band selection case -- BandController notifies item callback by itself.
         mListener.onItemStateChanged(mItems.get(1), true);
@@ -365,7 +365,7 @@
 
     @Test
     public void testProvisionalSelection_Apply() {
-        Selection s = mTracker.getSelection();
+        Selection<String> s = mTracker.getSelection();
 
         // Mimicking band selection case -- BandController notifies item callback by itself.
         mListener.onItemStateChanged(mItems.get(1), true);
@@ -383,7 +383,7 @@
     public void testProvisionalSelection_Cancel() {
         mTracker.select(mItems.get(1));
         mTracker.select(mItems.get(2));
-        Selection s = mTracker.getSelection();
+        Selection<String> s = mTracker.getSelection();
 
         SparseBooleanArray provisional = new SparseBooleanArray();
         provisional.append(3, true);
@@ -399,7 +399,7 @@
     public void testProvisionalSelection_IntersectsAppliedSelection() {
         mTracker.select(mItems.get(1));
         mTracker.select(mItems.get(2));
-        Selection s = mTracker.getSelection();
+        Selection<String> s = mTracker.getSelection();
 
         // Mimicking band selection case -- BandController notifies item callback by itself.
         mListener.onItemStateChanged(mItems.get(3), true);
diff --git a/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/SelectionTest.java b/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/SelectionTest.java
index eb2402b..b8c6583 100644
--- a/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/SelectionTest.java
+++ b/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/SelectionTest.java
@@ -41,11 +41,11 @@
             "auth|id=@53di*/f3#d"
     };
 
-    private Selection mSelection;
+    private Selection<String> mSelection;
 
     @Before
     public void setUp() throws Exception {
-        mSelection = new Selection();
+        mSelection = new Selection<>();
         mSelection.add(mIds[0]);
         mSelection.add(mIds[1]);
         mSelection.add(mIds[2]);
@@ -96,14 +96,14 @@
 
     @Test
     public void testIsEmpty() {
-        assertTrue(new Selection().isEmpty());
+        assertTrue(new Selection<>().isEmpty());
         mSelection.clear();
         assertTrue(mSelection.isEmpty());
     }
 
     @Test
     public void testSize() {
-        Selection other = new Selection();
+        Selection<String> other = new Selection<>();
         for (int i = 0; i < mSelection.size(); i++) {
             other.add(mIds[i]);
         }
@@ -117,7 +117,7 @@
 
     @Test
     public void testEqualsOther() {
-        Selection other = new Selection();
+        Selection<String> other = new Selection<>();
         other.add(mIds[0]);
         other.add(mIds[1]);
         other.add(mIds[2]);
@@ -127,7 +127,7 @@
 
     @Test
     public void testEqualsCopy() {
-        Selection other = new Selection();
+        Selection<String> other = new Selection<>();
         other.copyFrom(mSelection);
         assertEquals(mSelection, other);
         assertEquals(mSelection.hashCode(), other.hashCode());
@@ -135,7 +135,7 @@
 
     @Test
     public void testNotEquals() {
-        Selection other = new Selection();
+        Selection<String> other = new Selection<>();
         other.add("foobar");
         assertFalse(mSelection.equals(other));
     }
diff --git a/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/testing/SelectionProbe.java b/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/testing/SelectionProbe.java
index e8ae607..8ac4baf 100644
--- a/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/testing/SelectionProbe.java
+++ b/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/testing/SelectionProbe.java
@@ -64,7 +64,7 @@
     }
 
     public void assertSelectionSize(int expected) {
-        Selection selection = mMgr.getSelection();
+        Selection<String> selection = mMgr.getSelection();
         assertEquals(selection.toString(), expected, selection.size());
 
         mSelectionListener.assertSelectionSize(expected);
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionTracker.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionTracker.java
index 5491f99..283426f 100644
--- a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionTracker.java
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionTracker.java
@@ -121,7 +121,7 @@
      * of the selection that will not reflect future changes
      * to selection.
      */
-    public abstract Selection getSelection();
+    public abstract Selection<K> getSelection();
 
     /**
      * Updates {@code dest} to reflect the current selection.
diff --git a/samples/SupportSliceDemos/build.gradle b/samples/SupportSliceDemos/build.gradle
index 4d518ff..8675869 100644
--- a/samples/SupportSliceDemos/build.gradle
+++ b/samples/SupportSliceDemos/build.gradle
@@ -21,9 +21,9 @@
 }
 
 dependencies {
-    implementation(project(":slices-view"))
-    implementation(project(":slices-builders"))
-    implementation(project(":slices-core"))
+    implementation(project(":slice-view"))
+    implementation(project(":slice-builders"))
+    implementation(project(":slice-core"))
     implementation("com.android.support:design:28.0.0-SNAPSHOT", { transitive = false })
     implementation(project(":transition"))
     implementation(project(":recyclerview"))
diff --git a/settings.gradle b/settings.gradle
index 61676b0..7ec91e5 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -74,9 +74,9 @@
 includeProject(":recommendation", "recommendation")
 includeProject(":recyclerview", "v7/recyclerview")
 includeProject(":recyclerview-selection", "recyclerview-selection")
-includeProject(":slices-core", "slices/core")
-includeProject(":slices-view", "slices/view")
-includeProject(":slices-builders", "slices/builders")
+includeProject(":slice-core", "slices/core")
+includeProject(":slice-view", "slices/view")
+includeProject(":slice-builders", "slices/builders")
 includeProject(":slidingpanelayout", "slidingpanelayout")
 includeProject(":swiperefreshlayout", "swiperefreshlayout")
 includeProject(":textclassifier", "textclassifier")
diff --git a/slices/builders/build.gradle b/slices/builders/build.gradle
index 907a0b4..73d0568 100644
--- a/slices/builders/build.gradle
+++ b/slices/builders/build.gradle
@@ -23,7 +23,7 @@
 }
 
 dependencies {
-    implementation(project(":slices-core"))
+    implementation(project(":slice-core"))
     implementation project(":annotation")
     implementation project(path: ':core')
 }
diff --git a/slices/view/build.gradle b/slices/view/build.gradle
index 71de3a7..38576c3 100644
--- a/slices/view/build.gradle
+++ b/slices/view/build.gradle
@@ -23,8 +23,8 @@
 }
 
 dependencies {
-    implementation(project(":slices-core"))
-    implementation(project(":slices-builders"))
+    implementation(project(":slice-core"))
+    implementation(project(":slice-builders"))
     implementation(project(":recyclerview"))
     api(ARCH_LIFECYCLE_LIVEDATA_CORE, libs.exclude_annotations_transitive)
 
diff --git a/slices/view/src/main/java/androidx/slice/widget/ListContent.java b/slices/view/src/main/java/androidx/slice/widget/ListContent.java
index ee3c2e4..cde20da 100644
--- a/slices/view/src/main/java/androidx/slice/widget/ListContent.java
+++ b/slices/view/src/main/java/androidx/slice/widget/ListContent.java
@@ -306,9 +306,12 @@
         return null;
     }
 
-    private static boolean isValidHeader(SliceItem sliceItem) {
+    /**
+     * @return whether the provided slice item is a valid header.
+     */
+    public static boolean isValidHeader(SliceItem sliceItem) {
         if (FORMAT_SLICE.equals(sliceItem.getFormat()) && !sliceItem.hasAnyHints(HINT_LIST_ITEM,
-                HINT_ACTIONS, HINT_KEYWORDS)) {
+                HINT_ACTIONS, HINT_KEYWORDS, HINT_SEE_MORE)) {
              // Minimum valid header is a slice with text
             SliceItem item = SliceQuery.find(sliceItem, FORMAT_TEXT, (String) null, null);
             return item != null;
diff --git a/slices/view/src/main/java/androidx/slice/widget/RowView.java b/slices/view/src/main/java/androidx/slice/widget/RowView.java
index 6940cb0..a4a589a 100644
--- a/slices/view/src/main/java/androidx/slice/widget/RowView.java
+++ b/slices/view/src/main/java/androidx/slice/widget/RowView.java
@@ -17,7 +17,6 @@
 package androidx.slice.widget;
 
 import static android.app.slice.Slice.EXTRA_RANGE_VALUE;
-import static android.app.slice.Slice.HINT_LIST_ITEM;
 import static android.app.slice.Slice.HINT_NO_TINT;
 import static android.app.slice.Slice.HINT_PARTIAL;
 import static android.app.slice.Slice.HINT_SHORTCUT;
@@ -220,7 +219,7 @@
             SliceView.OnSliceActionListener observer) {
         setSliceActionListener(observer);
         mRowIndex = index;
-        mIsHeader = !slice.hasHint(HINT_LIST_ITEM);
+        mIsHeader = ListContent.isValidHeader(slice);
         mHeaderActions = null;
         mRowContent = new RowContent(getContext(), slice, mIsHeader);
         populateViews();
@@ -531,7 +530,7 @@
             b.setTextColor(mTintColor);
         }
         mSeeMoreView = b;
-        addView(mSeeMoreView);
+        mRootView.addView(mSeeMoreView);
     }
 
     @Override
@@ -578,7 +577,7 @@
             removeView(mRangeBar);
         }
         if (mSeeMoreView != null) {
-            removeView(mSeeMoreView);
+            mRootView.removeView(mSeeMoreView);
         }
     }
 }
diff --git a/v7/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerTest.java b/v7/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerTest.java
index 25aa00d..60eb6d2 100644
--- a/v7/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerTest.java
+++ b/v7/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerTest.java
@@ -1180,7 +1180,16 @@
             adapter.addAndNotify(5 + (i % 3) * 3, 1);
             Thread.sleep(25);
         }
-        smoothScrollToPosition(mLayoutManager.findLastVisibleItemPosition() + 20);
+
+        final AtomicInteger lastVisiblePosition = new AtomicInteger();
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                lastVisiblePosition.set(mLayoutManager.findLastVisibleItemPosition());
+            }
+        });
+
+        smoothScrollToPosition(lastVisiblePosition.get() + 20);
         waitForAnimations(2);
         getInstrumentation().waitForIdleSync();
         assertEquals("Children count should add up", childCount.get(),