Merge "Fix ConcurrentModificationException"
diff --git a/api/current.txt b/api/current.txt
index 573e635..fff502a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -22842,6 +22842,27 @@
     field public static final int STOP_VIDEO_RECORDING = 3; // 0x3
   }
 
+  public class MediaBrowser2 extends android.media.MediaController2 {
+    ctor public MediaBrowser2(android.content.Context, android.media.SessionToken2, java.util.concurrent.Executor, android.media.MediaBrowser2.BrowserCallback);
+    method public void getChildren(java.lang.String, int, int, android.os.Bundle);
+    method public void getItem(java.lang.String);
+    method public void getLibraryRoot(android.os.Bundle);
+    method public void getSearchResult(java.lang.String, int, int, android.os.Bundle);
+    method public void search(java.lang.String, android.os.Bundle);
+    method public void subscribe(java.lang.String, android.os.Bundle);
+    method public void unsubscribe(java.lang.String);
+  }
+
+  public static class MediaBrowser2.BrowserCallback extends android.media.MediaController2.ControllerCallback {
+    ctor public MediaBrowser2.BrowserCallback();
+    method public void onChildrenChanged(java.lang.String, int, android.os.Bundle);
+    method public void onGetChildrenDone(java.lang.String, int, int, java.util.List<android.media.MediaItem2>, android.os.Bundle);
+    method public void onGetItemDone(java.lang.String, android.media.MediaItem2);
+    method public void onGetLibraryRootDone(android.os.Bundle, java.lang.String, android.os.Bundle);
+    method public void onGetSearchResultDone(java.lang.String, int, int, java.util.List<android.media.MediaItem2>, android.os.Bundle);
+    method public void onSearchResultChanged(java.lang.String, int, android.os.Bundle);
+  }
+
   public final class MediaCas implements java.lang.AutoCloseable {
     ctor public MediaCas(int) throws android.media.MediaCasException.UnsupportedCasException;
     method public void close();
@@ -23318,6 +23339,73 @@
     field public static final int REGULAR_CODECS = 0; // 0x0
   }
 
+  public class MediaController2 implements java.lang.AutoCloseable {
+    ctor public MediaController2(android.content.Context, android.media.SessionToken2, java.util.concurrent.Executor, android.media.MediaController2.ControllerCallback);
+    method public void addPlaylistItem(int, android.media.MediaItem2);
+    method public void adjustVolume(int, int);
+    method public void close();
+    method public void fastForward();
+    method public long getBufferedPosition();
+    method public android.media.MediaItem2 getCurrentPlaylistItem();
+    method public android.media.MediaController2.PlaybackInfo getPlaybackInfo();
+    method public float getPlaybackSpeed();
+    method public int getPlayerState();
+    method public java.util.List<android.media.MediaItem2> getPlaylist();
+    method public android.media.MediaSession2.PlaylistParams getPlaylistParams();
+    method public long getPosition();
+    method public android.app.PendingIntent getSessionActivity();
+    method public android.media.SessionToken2 getSessionToken();
+    method public boolean isConnected();
+    method public void pause();
+    method public void play();
+    method public void playFromMediaId(java.lang.String, android.os.Bundle);
+    method public void playFromSearch(java.lang.String, android.os.Bundle);
+    method public void playFromUri(android.net.Uri, android.os.Bundle);
+    method public void prepare();
+    method public void prepareFromMediaId(java.lang.String, android.os.Bundle);
+    method public void prepareFromSearch(java.lang.String, android.os.Bundle);
+    method public void prepareFromUri(android.net.Uri, android.os.Bundle);
+    method public void removePlaylistItem(android.media.MediaItem2);
+    method public void rewind();
+    method public void seekTo(long);
+    method public void sendCustomCommand(android.media.MediaSession2.Command, android.os.Bundle, android.os.ResultReceiver);
+    method public void setPlaylistParams(android.media.MediaSession2.PlaylistParams);
+    method public void setRating(java.lang.String, android.media.Rating2);
+    method public void setVolumeTo(int, int);
+    method public void skipToNext();
+    method public void skipToPlaylistItem(android.media.MediaItem2);
+    method public void skipToPrevious();
+    method public void stop();
+  }
+
+  public static abstract class MediaController2.ControllerCallback {
+    ctor public MediaController2.ControllerCallback();
+    method public void onAllowedCommandsChanged(android.media.MediaSession2.CommandGroup);
+    method public void onBufferedPositionChanged(long);
+    method public void onConnected(android.media.MediaSession2.CommandGroup);
+    method public void onCurrentPlaylistItemChanged(android.media.MediaItem2);
+    method public void onCustomCommand(android.media.MediaSession2.Command, android.os.Bundle, android.os.ResultReceiver);
+    method public void onCustomLayoutChanged(java.util.List<android.media.MediaSession2.CommandButton>);
+    method public void onDisconnected();
+    method public void onError(int, int);
+    method public void onPlaybackInfoChanged(android.media.MediaController2.PlaybackInfo);
+    method public void onPlaybackSpeedChanged(float);
+    method public void onPlayerStateChanged(int);
+    method public void onPlaylistChanged(java.util.List<android.media.MediaItem2>);
+    method public void onPlaylistParamsChanged(android.media.MediaSession2.PlaylistParams);
+    method public void onPositionUpdated(long, long);
+  }
+
+  public static final class MediaController2.PlaybackInfo {
+    method public android.media.AudioAttributes getAudioAttributes();
+    method public int getControlType();
+    method public int getCurrentVolume();
+    method public int getMaxVolume();
+    method public int getPlaybackType();
+    field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
+    field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
+  }
+
   public final class MediaCrypto {
     ctor public MediaCrypto(java.util.UUID, byte[]) throws android.media.MediaCryptoException;
     method protected void finalize();
@@ -23713,6 +23801,69 @@
     field public static final java.lang.String MIMETYPE_VIDEO_VP9 = "video/x-vnd.on2.vp9";
   }
 
+  public class MediaItem2 {
+    method public static android.media.MediaItem2 fromBundle(android.content.Context, android.os.Bundle);
+    method public android.media.DataSourceDesc getDataSourceDesc();
+    method public int getFlags();
+    method public java.lang.String getMediaId();
+    method public android.media.MediaMetadata2 getMetadata();
+    method public boolean isBrowsable();
+    method public boolean isPlayable();
+    method public void setMetadata(android.media.MediaMetadata2);
+    method public android.os.Bundle toBundle();
+    field public static final int FLAG_BROWSABLE = 1; // 0x1
+    field public static final int FLAG_PLAYABLE = 2; // 0x2
+  }
+
+  public static final class MediaItem2.Builder {
+    ctor public MediaItem2.Builder(android.content.Context, int);
+    method public android.media.MediaItem2 build();
+    method public android.media.MediaItem2.Builder setDataSourceDesc(android.media.DataSourceDesc);
+    method public android.media.MediaItem2.Builder setMediaId(java.lang.String);
+    method public android.media.MediaItem2.Builder setMetadata(android.media.MediaMetadata2);
+  }
+
+  public abstract class MediaLibraryService2 extends android.media.MediaSessionService2 {
+    ctor public MediaLibraryService2();
+    method public abstract android.media.MediaLibraryService2.MediaLibrarySession onCreateSession(java.lang.String);
+    field public static final java.lang.String SERVICE_INTERFACE = "android.media.MediaLibraryService2";
+  }
+
+  public static final class MediaLibraryService2.LibraryRoot {
+    ctor public MediaLibraryService2.LibraryRoot(android.content.Context, java.lang.String, android.os.Bundle);
+    method public android.os.Bundle getExtras();
+    method public java.lang.String getRootId();
+    field public static final java.lang.String EXTRA_OFFLINE = "android.media.extra.OFFLINE";
+    field public static final java.lang.String EXTRA_RECENT = "android.media.extra.RECENT";
+    field public static final java.lang.String EXTRA_SUGGESTED = "android.media.extra.SUGGESTED";
+  }
+
+  public static final class MediaLibraryService2.MediaLibrarySession extends android.media.MediaSession2 {
+    method public void notifyChildrenChanged(android.media.MediaSession2.ControllerInfo, java.lang.String, int, android.os.Bundle);
+    method public void notifyChildrenChanged(java.lang.String, int, android.os.Bundle);
+    method public void notifySearchResultChanged(android.media.MediaSession2.ControllerInfo, java.lang.String, int, android.os.Bundle);
+  }
+
+  public static final class MediaLibraryService2.MediaLibrarySession.Builder {
+    ctor public MediaLibraryService2.MediaLibrarySession.Builder(android.media.MediaLibraryService2, android.media.MediaPlayerBase, java.util.concurrent.Executor, android.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback);
+    method public android.media.MediaLibraryService2.MediaLibrarySession build();
+    method public android.media.MediaLibraryService2.MediaLibrarySession.Builder setId(java.lang.String);
+    method public android.media.MediaLibraryService2.MediaLibrarySession.Builder setSessionActivity(android.app.PendingIntent);
+    method public android.media.MediaLibraryService2.MediaLibrarySession.Builder setSessionCallback(java.util.concurrent.Executor, android.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback);
+    method public android.media.MediaLibraryService2.MediaLibrarySession.Builder setVolumeProvider(android.media.VolumeProvider2);
+  }
+
+  public static class MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback extends android.media.MediaSession2.SessionCallback {
+    ctor public MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback(android.content.Context);
+    method public java.util.List<android.media.MediaItem2> onGetChildren(android.media.MediaSession2.ControllerInfo, java.lang.String, int, int, android.os.Bundle);
+    method public android.media.MediaItem2 onGetItem(android.media.MediaSession2.ControllerInfo, java.lang.String);
+    method public android.media.MediaLibraryService2.LibraryRoot onGetLibraryRoot(android.media.MediaSession2.ControllerInfo, android.os.Bundle);
+    method public java.util.List<android.media.MediaItem2> onGetSearchResult(android.media.MediaSession2.ControllerInfo, java.lang.String, int, int, android.os.Bundle);
+    method public void onSearch(android.media.MediaSession2.ControllerInfo, java.lang.String, android.os.Bundle);
+    method public void onSubscribe(android.media.MediaSession2.ControllerInfo, java.lang.String, android.os.Bundle);
+    method public void onUnsubscribe(android.media.MediaSession2.ControllerInfo, java.lang.String);
+  }
+
   public final class MediaMetadata implements android.os.Parcelable {
     method public boolean containsKey(java.lang.String);
     method public int describeContents();
@@ -23768,6 +23919,79 @@
     method public android.media.MediaMetadata.Builder putText(java.lang.String, java.lang.CharSequence);
   }
 
+  public final class MediaMetadata2 {
+    method public boolean containsKey(java.lang.String);
+    method public static android.media.MediaMetadata2 fromBundle(android.content.Context, android.os.Bundle);
+    method public android.graphics.Bitmap getBitmap(java.lang.String);
+    method public android.os.Bundle getExtras();
+    method public float getFloat(java.lang.String);
+    method public long getLong(java.lang.String);
+    method public java.lang.String getMediaId();
+    method public android.media.Rating2 getRating(java.lang.String);
+    method public java.lang.String getString(java.lang.String);
+    method public java.lang.CharSequence getText(java.lang.String);
+    method public java.util.Set<java.lang.String> keySet();
+    method public int size();
+    method public android.os.Bundle toBundle();
+    field public static final long BT_FOLDER_TYPE_ALBUMS = 2L; // 0x2L
+    field public static final long BT_FOLDER_TYPE_ARTISTS = 3L; // 0x3L
+    field public static final long BT_FOLDER_TYPE_GENRES = 4L; // 0x4L
+    field public static final long BT_FOLDER_TYPE_MIXED = 0L; // 0x0L
+    field public static final long BT_FOLDER_TYPE_PLAYLISTS = 5L; // 0x5L
+    field public static final long BT_FOLDER_TYPE_TITLES = 1L; // 0x1L
+    field public static final long BT_FOLDER_TYPE_YEARS = 6L; // 0x6L
+    field public static final java.lang.String METADATA_KEY_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
+    field public static final java.lang.String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
+    field public static final java.lang.String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
+    field public static final java.lang.String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+    field public static final java.lang.String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
+    field public static final java.lang.String METADATA_KEY_ART = "android.media.metadata.ART";
+    field public static final java.lang.String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
+    field public static final java.lang.String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
+    field public static final java.lang.String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
+    field public static final java.lang.String METADATA_KEY_BT_FOLDER_TYPE = "android.media.metadata.BT_FOLDER_TYPE";
+    field public static final java.lang.String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
+    field public static final java.lang.String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
+    field public static final java.lang.String METADATA_KEY_DATE = "android.media.metadata.DATE";
+    field public static final java.lang.String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+    field public static final java.lang.String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
+    field public static final java.lang.String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
+    field public static final java.lang.String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
+    field public static final java.lang.String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
+    field public static final java.lang.String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
+    field public static final java.lang.String METADATA_KEY_DOWNLOAD_STATUS = "android.media.metadata.DOWNLOAD_STATUS";
+    field public static final java.lang.String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
+    field public static final java.lang.String METADATA_KEY_EXTRAS = "android.media.metadata.EXTRAS";
+    field public static final java.lang.String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
+    field public static final java.lang.String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
+    field public static final java.lang.String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
+    field public static final java.lang.String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
+    field public static final java.lang.String METADATA_KEY_RADIO_CALLSIGN = "android.media.metadata.RADIO_CALLSIGN";
+    field public static final java.lang.String METADATA_KEY_RADIO_FREQUENCY = "android.media.metadata.RADIO_FREQUENCY";
+    field public static final java.lang.String METADATA_KEY_RATING = "android.media.metadata.RATING";
+    field public static final java.lang.String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
+    field public static final java.lang.String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+    field public static final java.lang.String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
+    field public static final java.lang.String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
+    field public static final java.lang.String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
+    field public static final long STATUS_DOWNLOADED = 2L; // 0x2L
+    field public static final long STATUS_DOWNLOADING = 1L; // 0x1L
+    field public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
+  }
+
+  public static final class MediaMetadata2.Builder {
+    ctor public MediaMetadata2.Builder(android.content.Context);
+    ctor public MediaMetadata2.Builder(android.content.Context, android.media.MediaMetadata2);
+    method public android.media.MediaMetadata2 build();
+    method public android.media.MediaMetadata2.Builder putBitmap(java.lang.String, android.graphics.Bitmap);
+    method public android.media.MediaMetadata2.Builder putFloat(java.lang.String, float);
+    method public android.media.MediaMetadata2.Builder putLong(java.lang.String, long);
+    method public android.media.MediaMetadata2.Builder putRating(java.lang.String, android.media.Rating2);
+    method public android.media.MediaMetadata2.Builder putString(java.lang.String, java.lang.String);
+    method public android.media.MediaMetadata2.Builder putText(java.lang.String, java.lang.CharSequence);
+    method public android.media.MediaMetadata2.Builder setExtras(android.os.Bundle);
+  }
+
   public abstract deprecated class MediaMetadataEditor {
     method public synchronized void addEditableKey(int);
     method public abstract void apply();
@@ -24239,6 +24463,19 @@
     field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
   }
 
+  public abstract class MediaPlayerBase implements java.lang.AutoCloseable {
+    ctor public MediaPlayerBase();
+    method public abstract android.media.AudioAttributes getAudioAttributes();
+    method public abstract int getPlayerState();
+    method public abstract void pause();
+    method public abstract void play();
+    method public abstract void setAudioAttributes(android.media.AudioAttributes);
+    field public static final int STATE_ERROR = 0; // 0x0
+    field public static final int STATE_IDLE = 0; // 0x0
+    field public static final int STATE_PAUSED = 0; // 0x0
+    field public static final int STATE_PLAYING = 0; // 0x0
+  }
+
   public class MediaRecorder implements android.media.AudioRouting {
     ctor public MediaRecorder();
     method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
@@ -24515,6 +24752,172 @@
     method public abstract void onScanCompleted(java.lang.String, android.net.Uri);
   }
 
+  public class MediaSession2 implements java.lang.AutoCloseable {
+    method public void addPlaylistItem(int, android.media.MediaItem2);
+    method public void close();
+    method public void editPlaylistItem(android.media.MediaItem2);
+    method public void fastForward();
+    method public java.util.List<android.media.MediaSession2.ControllerInfo> getConnectedControllers();
+    method public android.media.MediaItem2 getCurrentPlaylistItem();
+    method public android.media.MediaPlayerBase getPlayer();
+    method public java.util.List<android.media.MediaItem2> getPlaylist();
+    method public android.media.MediaSession2.PlaylistParams getPlaylistParams();
+    method public android.media.SessionToken2 getToken();
+    method public void notifyError(int, int);
+    method public void pause();
+    method public void play();
+    method public void prepare();
+    method public void removePlaylistItem(android.media.MediaItem2);
+    method public void rewind();
+    method public void seekTo(long);
+    method public void sendCustomCommand(android.media.MediaSession2.Command, android.os.Bundle);
+    method public void sendCustomCommand(android.media.MediaSession2.ControllerInfo, android.media.MediaSession2.Command, android.os.Bundle, android.os.ResultReceiver);
+    method public void setAllowedCommands(android.media.MediaSession2.ControllerInfo, android.media.MediaSession2.CommandGroup);
+    method public void setCustomLayout(android.media.MediaSession2.ControllerInfo, java.util.List<android.media.MediaSession2.CommandButton>);
+    method public void setPlayer(android.media.MediaPlayerBase);
+    method public void setPlayer(android.media.MediaPlayerBase, android.media.VolumeProvider2);
+    method public void setPlaylist(java.util.List<android.media.MediaItem2>);
+    method public void setPlaylistParams(android.media.MediaSession2.PlaylistParams);
+    method public void skipToNext();
+    method public void skipToPlaylistItem(android.media.MediaItem2);
+    method public void skipToPrevious();
+    method public void stop();
+    field public static final int COMMAND_CODE_BROWSER = 22; // 0x16
+    field public static final int COMMAND_CODE_CUSTOM = 0; // 0x0
+    field public static final int COMMAND_CODE_PLAYBACK_FAST_FORWARD = 7; // 0x7
+    field public static final int COMMAND_CODE_PLAYBACK_PAUSE = 2; // 0x2
+    field public static final int COMMAND_CODE_PLAYBACK_PLAY = 1; // 0x1
+    field public static final int COMMAND_CODE_PLAYBACK_PREPARE = 6; // 0x6
+    field public static final int COMMAND_CODE_PLAYBACK_REWIND = 8; // 0x8
+    field public static final int COMMAND_CODE_PLAYBACK_SEEK_TO = 9; // 0x9
+    field public static final int COMMAND_CODE_PLAYBACK_SET_CURRENT_PLAYLIST_ITEM = 10; // 0xa
+    field public static final int COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS = 11; // 0xb
+    field public static final int COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM = 4; // 0x4
+    field public static final int COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM = 5; // 0x5
+    field public static final int COMMAND_CODE_PLAYBACK_STOP = 3; // 0x3
+    field public static final int COMMAND_CODE_PLAYLIST_ADD = 12; // 0xc
+    field public static final int COMMAND_CODE_PLAYLIST_GET = 14; // 0xe
+    field public static final int COMMAND_CODE_PLAYLIST_REMOVE = 13; // 0xd
+    field public static final int COMMAND_CODE_PLAY_FROM_MEDIA_ID = 16; // 0x10
+    field public static final int COMMAND_CODE_PLAY_FROM_SEARCH = 18; // 0x12
+    field public static final int COMMAND_CODE_PLAY_FROM_URI = 17; // 0x11
+    field public static final int COMMAND_CODE_PREPARE_FROM_MEDIA_ID = 19; // 0x13
+    field public static final int COMMAND_CODE_PREPARE_FROM_SEARCH = 21; // 0x15
+    field public static final int COMMAND_CODE_PREPARE_FROM_URI = 20; // 0x14
+    field public static final int COMMAND_CODE_SET_VOLUME = 15; // 0xf
+    field public static final int ERROR_CODE_ACTION_ABORTED = 10; // 0xa
+    field public static final int ERROR_CODE_APP_ERROR = 1; // 0x1
+    field public static final int ERROR_CODE_AUTHENTICATION_EXPIRED = 3; // 0x3
+    field public static final int ERROR_CODE_CONCURRENT_STREAM_LIMIT = 5; // 0x5
+    field public static final int ERROR_CODE_CONTENT_ALREADY_PLAYING = 8; // 0x8
+    field public static final int ERROR_CODE_END_OF_QUEUE = 11; // 0xb
+    field public static final int ERROR_CODE_NOT_AVAILABLE_IN_REGION = 7; // 0x7
+    field public static final int ERROR_CODE_NOT_SUPPORTED = 2; // 0x2
+    field public static final int ERROR_CODE_PARENTAL_CONTROL_RESTRICTED = 6; // 0x6
+    field public static final int ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED = 4; // 0x4
+    field public static final int ERROR_CODE_SETUP_REQUIRED = 12; // 0xc
+    field public static final int ERROR_CODE_SKIP_LIMIT_REACHED = 9; // 0x9
+    field public static final int ERROR_CODE_UNKNOWN_ERROR = 0; // 0x0
+  }
+
+  public static final class MediaSession2.Builder {
+    ctor public MediaSession2.Builder(android.content.Context, android.media.MediaPlayerBase);
+    method public android.media.MediaSession2 build();
+    method public android.media.MediaSession2.Builder setId(java.lang.String);
+    method public android.media.MediaSession2.Builder setSessionActivity(android.app.PendingIntent);
+    method public android.media.MediaSession2.Builder setSessionCallback(java.util.concurrent.Executor, android.media.MediaSession2.SessionCallback);
+    method public android.media.MediaSession2.Builder setVolumeProvider(android.media.VolumeProvider2);
+  }
+
+  public static final class MediaSession2.Command {
+    ctor public MediaSession2.Command(android.content.Context, int);
+    ctor public MediaSession2.Command(android.content.Context, java.lang.String, android.os.Bundle);
+    method public int getCommandCode();
+    method public java.lang.String getCustomCommand();
+    method public android.os.Bundle getExtra();
+  }
+
+  public static final class MediaSession2.CommandButton {
+    method public android.media.MediaSession2.Command getCommand();
+    method public java.lang.String getDisplayName();
+    method public android.os.Bundle getExtra();
+    method public int getIconResId();
+    method public boolean isEnabled();
+  }
+
+  public static final class MediaSession2.CommandButton.Builder {
+    ctor public MediaSession2.CommandButton.Builder(android.content.Context);
+    method public android.media.MediaSession2.CommandButton build();
+    method public android.media.MediaSession2.CommandButton.Builder setCommand(android.media.MediaSession2.Command);
+    method public android.media.MediaSession2.CommandButton.Builder setDisplayName(java.lang.String);
+    method public android.media.MediaSession2.CommandButton.Builder setEnabled(boolean);
+    method public android.media.MediaSession2.CommandButton.Builder setExtra(android.os.Bundle);
+    method public android.media.MediaSession2.CommandButton.Builder setIconResId(int);
+  }
+
+  public static final class MediaSession2.CommandGroup {
+    ctor public MediaSession2.CommandGroup(android.content.Context);
+    ctor public MediaSession2.CommandGroup(android.content.Context, android.media.MediaSession2.CommandGroup);
+    method public void addAllPredefinedCommands();
+    method public void addCommand(android.media.MediaSession2.Command);
+    method public boolean hasCommand(android.media.MediaSession2.Command);
+    method public boolean hasCommand(int);
+    method public void removeCommand(android.media.MediaSession2.Command);
+  }
+
+  public static final class MediaSession2.ControllerInfo {
+    method public java.lang.String getPackageName();
+    method public int getUid();
+    method public boolean isTrusted();
+  }
+
+  public static final class MediaSession2.PlaylistParams {
+    ctor public MediaSession2.PlaylistParams(android.content.Context, int, int, android.media.MediaMetadata2);
+    method public static android.media.MediaSession2.PlaylistParams fromBundle(android.content.Context, android.os.Bundle);
+    method public android.media.MediaMetadata2 getPlaylistMetadata();
+    method public int getRepeatMode();
+    method public int getShuffleMode();
+    method public android.os.Bundle toBundle();
+    field public static final int REPEAT_MODE_ALL = 2; // 0x2
+    field public static final int REPEAT_MODE_GROUP = 3; // 0x3
+    field public static final int REPEAT_MODE_NONE = 0; // 0x0
+    field public static final int REPEAT_MODE_ONE = 1; // 0x1
+    field public static final int SHUFFLE_MODE_ALL = 1; // 0x1
+    field public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
+    field public static final int SHUFFLE_MODE_NONE = 0; // 0x0
+  }
+
+  public static abstract class MediaSession2.SessionCallback {
+    ctor public MediaSession2.SessionCallback(android.content.Context);
+    method public boolean onCommandRequest(android.media.MediaSession2.ControllerInfo, android.media.MediaSession2.Command);
+    method public android.media.MediaSession2.CommandGroup onConnect(android.media.MediaSession2.ControllerInfo);
+    method public void onCustomCommand(android.media.MediaSession2.ControllerInfo, android.media.MediaSession2.Command, android.os.Bundle, android.os.ResultReceiver);
+    method public void onDisconnected(android.media.MediaSession2.ControllerInfo);
+    method public void onPlayFromMediaId(android.media.MediaSession2.ControllerInfo, java.lang.String, android.os.Bundle);
+    method public void onPlayFromSearch(android.media.MediaSession2.ControllerInfo, java.lang.String, android.os.Bundle);
+    method public void onPlayFromUri(android.media.MediaSession2.ControllerInfo, android.net.Uri, android.os.Bundle);
+    method public void onPrepareFromMediaId(android.media.MediaSession2.ControllerInfo, java.lang.String, android.os.Bundle);
+    method public void onPrepareFromSearch(android.media.MediaSession2.ControllerInfo, java.lang.String, android.os.Bundle);
+    method public void onPrepareFromUri(android.media.MediaSession2.ControllerInfo, android.net.Uri, android.os.Bundle);
+    method public void onSetRating(android.media.MediaSession2.ControllerInfo, java.lang.String, android.media.Rating2);
+  }
+
+  public abstract class MediaSessionService2 extends android.app.Service {
+    ctor public MediaSessionService2();
+    method public final android.media.MediaSession2 getSession();
+    method public android.os.IBinder onBind(android.content.Intent);
+    method public abstract android.media.MediaSession2 onCreateSession(java.lang.String);
+    method public android.media.MediaSessionService2.MediaNotification onUpdateNotification();
+    field public static final java.lang.String SERVICE_INTERFACE = "android.media.MediaSessionService2";
+    field public static final java.lang.String SERVICE_META_DATA = "android.media.session";
+  }
+
+  public static class MediaSessionService2.MediaNotification {
+    ctor public MediaSessionService2.MediaNotification(android.content.Context, int, android.app.Notification);
+    method public android.app.Notification getNotification();
+    method public int getNotificationId();
+  }
+
   public final class MediaSync {
     ctor public MediaSync();
     method public android.view.Surface createInputSurface();
@@ -24645,6 +25048,29 @@
     field public static final int RATING_THUMB_UP_DOWN = 2; // 0x2
   }
 
+  public final class Rating2 {
+    method public static android.media.Rating2 fromBundle(android.content.Context, android.os.Bundle);
+    method public float getPercentRating();
+    method public int getRatingStyle();
+    method public float getStarRating();
+    method public boolean hasHeart();
+    method public boolean isRated();
+    method public boolean isThumbUp();
+    method public static android.media.Rating2 newHeartRating(android.content.Context, boolean);
+    method public static android.media.Rating2 newPercentageRating(android.content.Context, float);
+    method public static android.media.Rating2 newStarRating(android.content.Context, int, float);
+    method public static android.media.Rating2 newThumbRating(android.content.Context, boolean);
+    method public static android.media.Rating2 newUnratedRating(android.content.Context, int);
+    method public android.os.Bundle toBundle();
+    field public static final int RATING_3_STARS = 3; // 0x3
+    field public static final int RATING_4_STARS = 4; // 0x4
+    field public static final int RATING_5_STARS = 5; // 0x5
+    field public static final int RATING_HEART = 1; // 0x1
+    field public static final int RATING_NONE = 0; // 0x0
+    field public static final int RATING_PERCENTAGE = 6; // 0x6
+    field public static final int RATING_THUMB_UP_DOWN = 2; // 0x2
+  }
+
   public deprecated class RemoteControlClient {
     ctor public RemoteControlClient(android.app.PendingIntent);
     ctor public RemoteControlClient(android.app.PendingIntent, android.os.Looper);
@@ -24780,6 +25206,22 @@
     field public static final int URI_COLUMN_INDEX = 2; // 0x2
   }
 
+  public final class SessionToken2 {
+    ctor public SessionToken2(android.content.Context, java.lang.String, java.lang.String);
+    method public static android.media.SessionToken2 fromBundle(android.content.Context, android.os.Bundle);
+    method public java.lang.String getId();
+    method public java.lang.String getPackageName();
+    method public int getType();
+    method public int getUid();
+    method public android.os.Bundle toBundle();
+    field public static final int TYPE_LIBRARY_SERVICE = 2; // 0x2
+    field public static final int TYPE_SESSION = 0; // 0x0
+    field public static final int TYPE_SESSION_SERVICE = 1; // 0x1
+  }
+
+  public static abstract class SessionToken2.TokenType implements java.lang.annotation.Annotation {
+  }
+
   public class SoundPool {
     ctor public deprecated SoundPool(int, int, int);
     method public final void autoPause();
@@ -24983,6 +25425,19 @@
     field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
   }
 
+  public abstract class VolumeProvider2 {
+    ctor public VolumeProvider2(android.content.Context, int, int, int);
+    method public final int getControlType();
+    method public final int getCurrentVolume();
+    method public final int getMaxVolume();
+    method public void onAdjustVolume(int);
+    method public void onSetVolumeTo(int);
+    method public final void setCurrentVolume(int);
+    field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
+    field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
+    field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
+  }
+
   public final class VolumeShaper implements java.lang.AutoCloseable {
     method public void apply(android.media.VolumeShaper.Operation);
     method public void close();
@@ -25729,14 +26184,23 @@
   public final class MediaSessionManager {
     method public void addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName);
     method public void addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName, android.os.Handler);
+    method public void addOnSessionTokensChangedListener(java.util.concurrent.Executor, android.media.session.MediaSessionManager.OnSessionTokensChangedListener);
+    method public java.util.List<android.media.SessionToken2> getActiveSessionTokens();
     method public java.util.List<android.media.session.MediaController> getActiveSessions(android.content.ComponentName);
+    method public java.util.List<android.media.SessionToken2> getAllSessionTokens();
+    method public java.util.List<android.media.SessionToken2> getSessionServiceTokens();
     method public void removeOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener);
+    method public void removeOnSessionTokensChangedListener(android.media.session.MediaSessionManager.OnSessionTokensChangedListener);
   }
 
   public static abstract interface MediaSessionManager.OnActiveSessionsChangedListener {
     method public abstract void onActiveSessionsChanged(java.util.List<android.media.session.MediaController>);
   }
 
+  public static abstract interface MediaSessionManager.OnSessionTokensChangedListener {
+    method public abstract void onSessionTokensChanged(java.util.List<android.media.SessionToken2>);
+  }
+
   public final class PlaybackState implements android.os.Parcelable {
     method public int describeContents();
     method public long getActions();
diff --git a/api/system-current.txt b/api/system-current.txt
index a581bab..3aca59a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6130,7 +6130,9 @@
     method public final void onSmsReceived(int, java.lang.String, byte[]) throws java.lang.RuntimeException;
     method public final void onSmsStatusReportReceived(int, int, java.lang.String, byte[]) throws java.lang.RuntimeException;
     method public void sendSms(int, int, java.lang.String, java.lang.String, boolean, byte[]);
-    field public static final int DELIVER_STATUS_ERROR = 2; // 0x2
+    field public static final int DELIVER_STATUS_ERROR_GENERIC = 2; // 0x2
+    field public static final int DELIVER_STATUS_ERROR_NO_MEMORY = 3; // 0x3
+    field public static final int DELIVER_STATUS_ERROR_REQUEST_NOT_SUPPORTED = 4; // 0x4
     field public static final int DELIVER_STATUS_OK = 1; // 0x1
     field public static final int SEND_STATUS_ERROR = 2; // 0x2
     field public static final int SEND_STATUS_ERROR_FALLBACK = 4; // 0x4
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0442c9c..cb38c0f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7103,6 +7103,14 @@
                 SHOW_ROTATION_SUGGESTIONS_ENABLED;
 
         /**
+         * The number of accepted rotation suggestions. Used to determine if the user has been
+         * introduced to rotation suggestions.
+         * @hide
+         */
+        public static final String NUM_ROTATION_SUGGESTIONS_ACCEPTED =
+                "num_rotation_suggestions_accepted";
+
+        /**
          * Read only list of the service components that the current user has explicitly allowed to
          * see and assist with all of the user's notifications.
          *
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 6da51d1..db19681 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -1008,10 +1008,8 @@
             final long vsync = AnimationUtils.currentAnimationTimeMillis() * 1000000L;
             mFrameInfo.setVsync(vsync, vsync);
             mFrameInfo.addFlags(1 << 2 /* VSYNC */);
-            // TODO: remove this fence
-            nFence(mNativeProxy);
             if (callback != null) {
-                callback.onFrameDraw(mSurface.getNextFrameNumber());
+                nSetFrameCallback(mNativeProxy, callback);
             }
             nSyncAndDrawFrame(mNativeProxy, mFrameInfo.mFrameInfo, mFrameInfo.mFrameInfo.length);
         }
@@ -1184,6 +1182,7 @@
     private static native void nDrawRenderNode(long nativeProxy, long rootRenderNode);
     private static native void nSetContentDrawBounds(long nativeProxy, int left,
              int top, int right, int bottom);
+    private static native void nSetFrameCallback(long nativeProxy, FrameDrawingCallback callback);
 
     private static native long nAddFrameMetricsObserver(long nativeProxy, FrameMetricsObserver observer);
     private static native void nRemoveFrameMetricsObserver(long nativeProxy, long nativeObserver);
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 5b1dd5c..af6c701 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -3333,7 +3333,7 @@
                 final int actionCount = mActions.size();
 
                 int nonStandardActionCount = 0;
-                int defaultStandardActions = 0;
+                long defaultStandardActions = 0;
                 for (int i = 0; i < actionCount; i++) {
                     AccessibilityAction action = mActions.get(i);
                     if (isDefaultStandardAction(action)) {
@@ -3342,7 +3342,7 @@
                         nonStandardActionCount++;
                     }
                 }
-                parcel.writeInt(defaultStandardActions);
+                parcel.writeLong(defaultStandardActions);
 
                 parcel.writeInt(nonStandardActionCount);
                 for (int i = 0; i < actionCount; i++) {
@@ -3540,7 +3540,7 @@
         }
 
         if (isBitSet(nonDefaultFields, fieldIndex++)) {
-            final int standardActions = parcel.readInt();
+            final long standardActions = parcel.readLong();
             addStandardActions(standardActions);
             final int nonStandardActionCount = parcel.readInt();
             for (int i = 0; i < nonStandardActionCount; i++) {
@@ -3636,7 +3636,7 @@
         return null;
     }
 
-    private static AccessibilityAction getActionSingletonBySerializationFlag(int flag) {
+    private static AccessibilityAction getActionSingletonBySerializationFlag(long flag) {
         final int actions = AccessibilityAction.sStandardActions.size();
         for (int i = 0; i < actions; i++) {
             AccessibilityAction currentAction = AccessibilityAction.sStandardActions.valueAt(i);
@@ -3648,10 +3648,10 @@
         return null;
     }
 
-    private void addStandardActions(int serializationIdMask) {
-        int remainingIds = serializationIdMask;
+    private void addStandardActions(long serializationIdMask) {
+        long remainingIds = serializationIdMask;
         while (remainingIds > 0) {
-            final int id = 1 << Integer.numberOfTrailingZeros(remainingIds);
+            final int id = 1 << Long.numberOfTrailingZeros(remainingIds);
             remainingIds &= ~id;
             AccessibilityAction action = getActionSingletonBySerializationFlag(id);
             addAction(action);
@@ -4276,7 +4276,7 @@
         private final CharSequence mLabel;
 
         /** @hide */
-        public int mSerializationFlag = -1;
+        public long mSerializationFlag = -1L;
 
         /**
          * Creates a new AccessibilityAction. For adding a standard action without a specific label,
@@ -4310,7 +4310,7 @@
         private AccessibilityAction(int standardActionId) {
             this(standardActionId, null);
 
-            mSerializationFlag = (int) bitAt(sStandardActions.size());
+            mSerializationFlag = bitAt(sStandardActions.size());
             sStandardActions.add(this);
         }
 
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index eb2af60..6e87e23 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -483,12 +483,12 @@
          * Destroys this instance.
          */
         public void destroy() {
-            mRenderer.destroy();
-            mSurface.destroy();
-            mSurfaceControl.destroy();
-            mSurfaceSession.kill();
-            mBitmapRenderNode.destroy();
             synchronized (mLock) {
+                mRenderer.destroy();
+                mSurface.destroy();
+                mSurfaceControl.destroy();
+                mSurfaceSession.kill();
+                mBitmapRenderNode.destroy();
                 mHandler.removeCallbacks(mMagnifierUpdater);
                 if (mBitmap != null) {
                     mBitmap.recycle();
@@ -530,17 +530,22 @@
                     final int pendingY = mWindowPositionY;
 
                     callback = frame -> {
-                        mRenderer.setLightCenter(mDisplay, pendingX, pendingY);
-                        // Show or move the window at the content draw frame.
-                        SurfaceControl.openTransaction();
-                        mSurfaceControl.deferTransactionUntil(mSurface, frame);
-                        if (updateWindowPosition) {
-                            mSurfaceControl.setPosition(pendingX, pendingY);
+                        synchronized (mLock) {
+                            if (!mSurface.isValid()) {
+                                return;
+                            }
+                            mRenderer.setLightCenter(mDisplay, pendingX, pendingY);
+                            // Show or move the window at the content draw frame.
+                            SurfaceControl.openTransaction();
+                            mSurfaceControl.deferTransactionUntil(mSurface, frame);
+                            if (updateWindowPosition) {
+                                mSurfaceControl.setPosition(pendingX, pendingY);
+                            }
+                            if (firstDraw) {
+                                mSurfaceControl.show();
+                            }
+                            SurfaceControl.closeTransaction();
                         }
-                        if (firstDraw) {
-                            mSurfaceControl.show();
-                        }
-                        SurfaceControl.closeTransaction();
                     };
                 } else {
                     callback = null;
diff --git a/core/java/android/widget/VideoView2.java b/core/java/android/widget/VideoView2.java
index 340be46..a7ae3234 100644
--- a/core/java/android/widget/VideoView2.java
+++ b/core/java/android/widget/VideoView2.java
@@ -23,7 +23,7 @@
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.MediaMetadata2;
-import android.media.MediaPlayerInterface;
+import android.media.MediaPlayerBase;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
@@ -277,7 +277,7 @@
      * @hide
      */
     public void setRouteAttributes(@NonNull List<String> routeCategories,
-            @Nullable MediaPlayerInterface player) {
+            @Nullable MediaPlayerBase player) {
         mProvider.setRouteAttributes_impl(routeCategories, player);
     }
 
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 293471c..ffc21d5 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -319,7 +319,7 @@
         }
         final PackageManager pm = mContext.getPackageManager();
         String label = serviceInfo.getResolveInfo().loadLabel(pm).toString();
-        String summary = serviceInfo.loadSummary(pm).toString();
+        CharSequence summary = serviceInfo.loadSummary(pm);
         if (!includeSummary || TextUtils.isEmpty(summary)) {
             return label;
         }
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 9f3475a..13e0e4a 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -68,6 +68,10 @@
     jmethodID callback;
 } gFrameMetricsObserverClassInfo;
 
+struct {
+    jmethodID onFrameDraw;
+} gFrameDrawingCallback;
+
 static JNIEnv* getenv(JavaVM* vm) {
     JNIEnv* env;
     if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
@@ -849,6 +853,44 @@
     proxy->setContentDrawBounds(left, top, right, bottom);
 }
 
+class JGlobalRefHolder {
+public:
+    JGlobalRefHolder(JavaVM* vm, jobject object) : mVm(vm), mObject(object) {}
+
+    virtual ~JGlobalRefHolder() {
+        getenv(mVm)->DeleteGlobalRef(mObject);
+        mObject = nullptr;
+    }
+
+    jobject object() { return mObject; }
+    JavaVM* vm() { return mVm; }
+
+private:
+    JGlobalRefHolder(const JGlobalRefHolder&) = delete;
+    void operator=(const JGlobalRefHolder&) = delete;
+
+    JavaVM* mVm;
+    jobject mObject;
+};
+
+static void android_view_ThreadedRenderer_setFrameCallback(JNIEnv* env,
+        jobject clazz, jlong proxyPtr, jobject frameCallback) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+    if (!frameCallback) {
+        proxy->setFrameCallback(nullptr);
+    } else {
+        JavaVM* vm = nullptr;
+        LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM");
+        auto globalCallbackRef = std::make_shared<JGlobalRefHolder>(vm,
+                env->NewGlobalRef(frameCallback));
+        proxy->setFrameCallback([globalCallbackRef](int64_t frameNr) {
+            JNIEnv* env = getenv(globalCallbackRef->vm());
+            env->CallVoidMethod(globalCallbackRef->object(), gFrameDrawingCallback.onFrameDraw,
+                    static_cast<jlong>(frameNr));
+        });
+    }
+}
+
 static jint android_view_ThreadedRenderer_copySurfaceInto(JNIEnv* env,
         jobject clazz, jobject jsurface, jint left, jint top,
         jint right, jint bottom, jobject jbitmap) {
@@ -1034,6 +1076,8 @@
     { "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode},
     { "nDrawRenderNode", "(JJ)V", (void*) android_view_ThreadedRendererd_drawRenderNode},
     { "nSetContentDrawBounds", "(JIIII)V", (void*)android_view_ThreadedRenderer_setContentDrawBounds},
+    { "nSetFrameCallback", "(JLandroid/view/ThreadedRenderer$FrameDrawingCallback;)V",
+            (void*)android_view_ThreadedRenderer_setFrameCallback},
     { "nAddFrameMetricsObserver",
             "(JLandroid/view/FrameMetricsObserver;)J",
             (void*)android_view_ThreadedRenderer_addFrameMetricsObserver },
@@ -1078,6 +1122,11 @@
     gFrameMetricsObserverClassInfo.timingDataBuffer = GetFieldIDOrDie(
             env, metricsClass, "mTimingData", "[J");
 
+    jclass frameCallbackClass = FindClassOrDie(env,
+            "android/view/ThreadedRenderer$FrameDrawingCallback");
+    gFrameDrawingCallback.onFrameDraw = GetMethodIDOrDie(env, frameCallbackClass,
+            "onFrameDraw", "(J)V");
+
     return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
 }
 
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index be2c235..f4d4c44 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -524,6 +524,7 @@
                  Settings.Secure.NFC_PAYMENT_FOREGROUND,
                  Settings.Secure.NIGHT_DISPLAY_ACTIVATED,
                  Settings.Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
+                 Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED,
                  Settings.Secure.PACKAGE_VERIFIER_STATE,
                  Settings.Secure.PACKAGE_VERIFIER_USER_CONSENT,
                  Settings.Secure.PARENTAL_CONTROL_LAST_UPDATE,
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index b135025..ba9b963 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -38,10 +38,10 @@
     public void testStandardActions_serializationFlagIsValid() {
         AccessibilityAction brokenStandardAction = CollectionUtils.find(
                 new ArrayList<>(AccessibilityAction.sStandardActions),
-                action -> Integer.bitCount(action.mSerializationFlag) != 1);
+                action -> Long.bitCount(action.mSerializationFlag) != 1);
         if (brokenStandardAction != null) {
             String message = "Invalid serialization flag(0x"
-                    + Integer.toHexString(brokenStandardAction.mSerializationFlag)
+                    + Long.toHexString(brokenStandardAction.mSerializationFlag)
                     + ") in " + brokenStandardAction;
             if (brokenStandardAction.mSerializationFlag == 0L) {
                 message += "\nThis is likely due to an overflow";
@@ -56,7 +56,7 @@
                         && action.getId() != action.mSerializationFlag);
         if (brokenStandardAction != null) {
             fail("Serialization flag(0x"
-                    + Integer.toHexString(brokenStandardAction.mSerializationFlag)
+                    + Long.toHexString(brokenStandardAction.mSerializationFlag)
                     + ") is different from legacy action id(0x"
                     + Integer.toHexString(brokenStandardAction.getId())
                     + ") in " + brokenStandardAction);
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index 449e374..a15d337 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -398,6 +398,17 @@
     }
 
     @Test
+    public void testOnAccessibilityShortcut_forServiceWithNoSummary_doesNotCrash()
+            throws Exception {
+        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+        configureValidShortcutService();
+        when(mServiceInfo.loadSummary(any())).thenReturn(null);
+        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
+        getController().performAccessibilityShortcut();
+        verify(mAccessibilityManagerService).performAccessibilityShortcut();
+    }
+
+    @Test
     public void testOnAccessibilityShortcut_forFrameworkFeature_callsServiceWithNoToast()
             throws Exception {
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 8372331..778e768 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -94,12 +94,20 @@
 
     // Grab a copy of everything we need
     CanvasContext* context = mContext;
+    std::function<void(int64_t)> callback = std::move(mFrameCallback);
 
     // From this point on anything in "this" is *UNSAFE TO ACCESS*
     if (canUnblockUiThread) {
         unblockUiThread();
     }
 
+    // Even if we aren't drawing this vsync pulse the next frame number will still be accurate
+    if (CC_UNLIKELY(callback)) {
+        context->enqueueFrameWork([callback, frameNr = context->getFrameNumber()]() {
+            callback(frameNr);
+        });
+    }
+
     if (CC_LIKELY(canDrawThisFrame)) {
         context->draw();
     } else {
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index ea51ae4..d8c43e0 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -74,6 +74,10 @@
 
     void run();
 
+    void setFrameCallback(std::function<void(int64_t)>&& callback) {
+        mFrameCallback = std::move(callback);
+    }
+
 private:
     void postAndWait();
     bool syncFrameState(TreeInfo& info);
@@ -96,6 +100,8 @@
     int64_t mSyncQueued;
 
     int64_t mFrameInfo[UI_THREAD_FRAME_INFO_SIZE];
+
+    std::function<void(int64_t)> mFrameCallback;
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 79e46ed..4be7a57 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -270,6 +270,10 @@
     mDrawFrameTask.setContentDrawBounds(left, top, right, bottom);
 }
 
+void RenderProxy::setFrameCallback(std::function<void(int64_t)>&& callback) {
+    mDrawFrameTask.setFrameCallback(std::move(callback));
+}
+
 void RenderProxy::serializeDisplayListTree() {
     mRenderThread.queue().post([=]() { mContext->serializeDisplayListTree(); });
 }
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index bc57d92..3425c5c 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -115,6 +115,7 @@
     ANDROID_API void removeRenderNode(RenderNode* node);
     ANDROID_API void drawRenderNode(RenderNode* node);
     ANDROID_API void setContentDrawBounds(int left, int top, int right, int bottom);
+    ANDROID_API void setFrameCallback(std::function<void(int64_t)>&& callback);
 
     ANDROID_API void addFrameMetricsObserver(FrameMetricsObserver* observer);
     ANDROID_API void removeFrameMetricsObserver(FrameMetricsObserver* observer);
diff --git a/media/java/android/media/MediaBrowser2.java b/media/java/android/media/MediaBrowser2.java
index 3f9a4ef..dea38a8 100644
--- a/media/java/android/media/MediaBrowser2.java
+++ b/media/java/android/media/MediaBrowser2.java
@@ -31,7 +31,6 @@
 
 /**
  * Browses media content offered by a {@link MediaLibraryService2}.
- * @hide
  */
 public class MediaBrowser2 extends MediaController2 {
     // Equals to the ((MediaBrowser2Provider) getProvider())
diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java
index 6682e08..0114240 100644
--- a/media/java/android/media/MediaController2.java
+++ b/media/java/android/media/MediaController2.java
@@ -19,13 +19,13 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.media.MediaSession2.Command;
 import android.media.MediaSession2.CommandButton;
 import android.media.MediaSession2.CommandGroup;
 import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.ErrorCode;
 import android.media.MediaSession2.PlaylistParams;
 import android.media.session.MediaSessionManager;
 import android.media.update.ApiLoader;
@@ -64,9 +64,8 @@
  * <p>
  * @see MediaSession2
  * @see MediaSessionService2
- * @hide
  */
-public class MediaController2 implements AutoCloseable {
+public class MediaController2 implements AutoCloseable, MediaPlaylistController {
     /**
      * Interface for listening to change in activeness of the {@link MediaSession2}.  It's
      * active if and only if it has set a player.
@@ -126,19 +125,69 @@
 
         /**
          * Called when the playlist is changed.
+         * <p>
+         * When it's called, you should invalidate previous playback information such as position,
+         * player state, current item, etc.
          *
          * @param playlist A new playlist set by the session.
          */
+        // TODO(jaewan): Enhance doc
         public void onPlaylistChanged(@NonNull List<MediaItem2> playlist) { }
 
         /**
          * Called when the playback state is changed.
          *
          * @param state latest playback state
+         * @hide
          */
+        // TODo(jaewan): Remove
         public void onPlaybackStateChanged(@NonNull PlaybackState2 state) { }
 
         /**
+         * Called when the player state is changed.
+         *
+         * @param state
+         */
+        public void onPlayerStateChanged(int state) { }
+
+        /**
+         * Called when the player's position is changed
+         *
+         * @param updateTimeMs timestamp when the position information is sent from the session
+         * @param positionMs position in millis
+         */
+        public void onPositionUpdated(long updateTimeMs, long positionMs) { }
+
+        /**
+         * Called when playback speed is changed.
+         *
+         * @param speed speed
+         */
+        public void onPlaybackSpeedChanged(float speed) { }
+
+        /**
+         * Called when the player's buffering position
+         *
+         * @param positionMs buffering position in millis
+         */
+        public void onBufferedPositionChanged(long positionMs) { }
+
+        /**
+         * Called when a error from
+         *
+         * @param errorCode error code
+         * @param extra extra information
+         */
+        public void onError(@ErrorCode int errorCode, int extra) { }
+
+        /**
+         * Called when the player's current playing item is changed
+         *
+         * @param item new item
+         */
+        public void onCurrentPlaylistItemChanged(MediaItem2 item) { }
+
+        /**
          * Called when the playlist parameters are changed.
          *
          * @param params The new play list parameters.
@@ -166,7 +215,6 @@
         /**
          * @hide
          */
-        @SystemApi
         public PlaybackInfo(PlaybackInfoProvider provider) {
             mProvider = provider;
         }
@@ -174,7 +222,6 @@
         /**
          * @hide
          */
-        @SystemApi
         public PlaybackInfoProvider getProvider() {
             return mProvider;
         }
@@ -281,7 +328,6 @@
     /**
      * @hide
      */
-    @SystemApi
     public MediaController2Provider getProvider() {
         return mProvider;
     }
@@ -325,7 +371,7 @@
      * Request that the player prepare its playback. In other words, other sessions can continue
      * to play during the preparation of this session. This method can be used to speed up the
      * start of the playback. Once the preparation is done, the session will change its playback
-     * state to {@link PlaybackState2#STATE_PAUSED}. Afterwards, {@link #play} can be called to
+     * state to {@link MediaPlayerBase#STATE_PAUSED}. Afterwards, {@link #play} can be called to
      * start playback.
      */
     public void prepare() {
@@ -360,12 +406,13 @@
     /**
      * Sets the index of current DataSourceDesc in the play list to be played.
      *
-     * @param index the index of DataSourceDesc in the play list you want to play
+     * @param item the index of DataSourceDesc in the play list you want to play
      * @throws IllegalArgumentException if the play list is null
      * @throws NullPointerException if index is outside play list range
      */
-    public void setCurrentPlaylistItem(int index) {
-        mProvider.setCurrentPlaylistItem_impl(index);
+    @Override
+    public void skipToPlaylistItem(@NonNull MediaItem2 item) {
+        mProvider.skipToPlaylistItem_impl(item);
     }
 
     /**
@@ -375,7 +422,7 @@
      * @param params A {@link PlaylistParams} object to set.
      * @throws IllegalArgumentException if given {@param param} is null.
      */
-    public void setPlaylistParams(PlaylistParams params) {
+    public void setPlaylistParams(@NonNull PlaylistParams params) {
         mProvider.setPlaylistParams_impl(params);
     }
 
@@ -428,12 +475,11 @@
         mProvider.playFromUri_impl(uri, extras);
     }
 
-
     /**
      * Request that the player prepare playback for a specific media id. In other words, other
      * sessions can continue to play during the preparation of this session. This method can be
      * used to speed up the start of the playback. Once the preparation is done, the session
-     * will change its playback state to {@link PlaybackState2#STATE_PAUSED}. Afterwards,
+     * will change its playback state to {@link MediaPlayerBase#STATE_PAUSED}. Afterwards,
      * {@link #play} can be called to start playback. If the preparation is not needed,
      * {@link #playFromMediaId} can be directly called without this method.
      *
@@ -450,7 +496,7 @@
      * query should be treated as a request to prepare any music. In other words, other sessions
      * can continue to play during the preparation of this session. This method can be used to
      * speed up the start of the playback. Once the preparation is done, the session will
-     * change its playback state to {@link PlaybackState2#STATE_PAUSED}. Afterwards,
+     * change its playback state to {@link MediaPlayerBase#STATE_PAUSED}. Afterwards,
      * {@link #play} can be called to start playback. If the preparation is not needed,
      * {@link #playFromSearch} can be directly called without this method.
      *
@@ -466,7 +512,7 @@
      * Request that the player prepare playback for a specific {@link Uri}. In other words,
      * other sessions can continue to play during the preparation of this session. This method
      * can be used to speed up the start of the playback. Once the preparation is done, the
-     * session will change its playback state to {@link PlaybackState2#STATE_PAUSED}. Afterwards,
+     * session will change its playback state to {@link MediaPlayerBase#STATE_PAUSED}. Afterwards,
      * {@link #play} can be called to start playback. If the preparation is not needed,
      * {@link #playFromUri} can be directly called without this method.
      *
@@ -537,12 +583,65 @@
      * playback state.
      *
      * @return a playback state. Can be {@code null}
+     * @hide
      */
     public @Nullable PlaybackState2 getPlaybackState() {
         return mProvider.getPlaybackState_impl();
     }
 
     /**
+     * Get the lastly cached player state from {@link ControllerCallback#onPlayerStateChanged(int)}.
+     *
+     * @return player state
+     */
+    public int getPlayerState() {
+        return mProvider.getPlayerState_impl();
+    }
+
+    /**
+     * Get the lastly cached position from {@link ControllerCallback#onPositionUpdated(long, long)}.
+     * <p>
+     * This returns the calculated value of the position, based on the difference between the
+     * update time and current time.
+     *
+     * @return position
+     */
+    public long getPosition() {
+        return mProvider.getPosition_impl();
+    }
+
+    /**
+     * Get the lastly cached playback speed from
+     * {@link ControllerCallback#onPlaybackSpeedChanged(float)}.
+     *
+     * @return speed
+     */
+    public float getPlaybackSpeed() {
+        return mProvider.getPlaybackSpeed_impl();
+    }
+
+    /**
+     * Get the lastly cached buffered position from
+     * {@link ControllerCallback#onBufferedPositionChanged(long)}.
+     *
+     * @return buffering position in millis
+     */
+    public long getBufferedPosition() {
+        return mProvider.getBufferedPosition_impl();
+    }
+
+    /**
+     * Get the lastly cached current item from
+     * {@link ControllerCallback#onCurrentPlaylistItemChanged(MediaItem2)}.
+     *
+     * @return index of the current item
+     */
+    @Override
+    public MediaItem2 getCurrentPlaylistItem() {
+        return mProvider.getCurrentPlaylistItem_impl();
+    }
+
+    /**
      * Get the current playback info for this session.
      *
      * @return The current playback info or null.
@@ -584,6 +683,7 @@
      *
      * @return playlist. Can be {@code null} if the controller doesn't have enough permission.
      */
+    @Override
     public @Nullable List<MediaItem2> getPlaylist() {
         return mProvider.getPlaylist_impl();
     }
@@ -603,13 +703,11 @@
      * If index is same as the current index of the playlist, current playback
      * will be stopped and playback moves to next source in the list.
      *
-     * @return the removed DataSourceDesc at index in the play list
      * @throws IllegalArgumentException if the play list is null
      * @throws IndexOutOfBoundsException if index is outside play list range
      */
-    // TODO(jaewan): Remove with index was previously rejected by council (b/36524925)
-    // TODO(jaewan): Should we also add movePlaylistItem from index to index?
-    public void removePlaylistItem(MediaItem2 item) {
+    @Override
+    public void removePlaylistItem(@NonNull MediaItem2 item) {
         mProvider.removePlaylistItem_impl(item);
     }
 
@@ -620,12 +718,12 @@
      * If index is less than or equal to the current index of the play list,
      * the current index of the play list will be incremented correspondingly.
      *
-     * @param index the index you want to add dsd to the play list
-     * @param item the media item you want to add to the play list
+     * @param index the index you want to add
+     * @param item the media item you want to add
      * @throws IndexOutOfBoundsException if index is outside play list range
-     * @throws NullPointerException if dsd is null
      */
-    public void addPlaylistItem(int index, MediaItem2 item) {
+    @Override
+    public void addPlaylistItem(int index, @NonNull MediaItem2 item) {
         mProvider.addPlaylistItem_impl(index, item);
     }
 }
diff --git a/media/java/android/media/MediaItem2.java b/media/java/android/media/MediaItem2.java
index 667aac1..b7b75e4 100644
--- a/media/java/android/media/MediaItem2.java
+++ b/media/java/android/media/MediaItem2.java
@@ -19,7 +19,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 import android.content.Context;
 import android.media.update.ApiLoader;
 import android.media.update.MediaItem2Provider;
@@ -35,7 +34,6 @@
  * When it's sent to a controller or browser, it's anonymized and data descriptor wouldn't be sent.
  * <p>
  * This object isn't a thread safe.
- * @hide
  */
 public class MediaItem2 {
     /** @hide */
@@ -64,7 +62,9 @@
      * @param mediaId id of this item. It must be unique whithin this app
      * @param metadata metadata with the media id.
      * @param flags The flags for this item.
+     * @hide
      */
+    // TODO(jaewan): Remove this
     public MediaItem2(@NonNull Context context, @NonNull String mediaId,
             @NonNull DataSourceDesc dsd, @Nullable MediaMetadata2 metadata,
             @Flags int flags) {
@@ -76,7 +76,6 @@
      * Create a new media item
      * @hide
      */
-    @SystemApi
     public MediaItem2(MediaItem2Provider provider) {
         mProvider = provider;
     }
@@ -156,4 +155,87 @@
     public @Nullable DataSourceDesc getDataSourceDesc() {
         return mProvider.getDataSourceDesc_impl();
     }
+
+    /**
+     * Build {@link MediaItem2}
+     */
+    // TODO(jaewan): Move it to updatable
+    public static final class Builder {
+        private Context mContext;
+        private @Flags int mFlags;
+        private String mMediaId;
+        private MediaMetadata2 mMetadata;
+        private DataSourceDesc mDataSourceDesc;
+
+        /**
+         * Constructor for {@link Builder}
+         *
+         * @param context
+         * @param flags
+         */
+        public Builder(@NonNull Context context, @Flags int flags) {
+            mContext = context;
+            mFlags = flags;
+        }
+
+        /**
+         * Set the media id of this instance. {@code null} for unset.
+         * <p>
+         * Media id is used to identify a media contents between session and controller.
+         * <p>
+         * If the metadata is set with the {@link #setMetadata(MediaMetadata2)} and it has
+         * media id, id from {@link #setMediaId(String)} will be ignored and metadata's id will be
+         * used instead. If the id isn't set neither by {@link #setMediaId(String)} nor
+         * {@link #setMetadata(MediaMetadata2)}, id will be automatically generated.
+         *
+         * @param mediaId media id
+         * @return this instance for chaining
+         */
+        public Builder setMediaId(@Nullable String mediaId) {
+            mMediaId = mediaId;
+            return this;
+        }
+
+        /**
+         * Set the metadata of this instance. {@code null} for unset.
+         * <p>
+         * If the metadata is set with the {@link #setMetadata(MediaMetadata2)} and it has
+         * media id, id from {@link #setMediaId(String)} will be ignored and metadata's id will be
+         * used instead. If the id isn't set neither by {@link #setMediaId(String)} nor
+         * {@link #setMetadata(MediaMetadata2)}, id will be automatically generated.
+         *
+         * @param metadata metadata
+         * @return this instance for chaining
+         */
+        public Builder setMetadata(@Nullable MediaMetadata2 metadata) {
+            mMetadata = metadata;
+            return this;
+        }
+
+        /**
+         * Set the data source descriptor for this instance. {@code null} for unset.
+         *
+         * @param dataSourceDesc data source descriptor
+         * @return this instance for chaining
+         */
+        public Builder setDataSourceDesc(@Nullable DataSourceDesc dataSourceDesc) {
+            mDataSourceDesc = dataSourceDesc;
+            return this;
+        }
+
+        /**
+         * Build {@link MediaItem2}.
+         *
+         * @return a new {@link MediaItem2}.
+         */
+        public MediaItem2 build() {
+            String id = (mMetadata != null)
+                    ? mMetadata.getString(MediaMetadata2.METADATA_KEY_MEDIA_ID) : null;
+            if (id == null) {
+                //  TODO(jaewan): Double check if its sufficient (e.g. Use UUID instead?)
+                id = (mMediaId != null) ? mMediaId : toString();
+            }
+            return new MediaItem2(mContext, id, mDataSourceDesc, mMetadata, mFlags);
+        }
+    }
 }
diff --git a/media/java/android/media/MediaLibraryService2.java b/media/java/android/media/MediaLibraryService2.java
index 11b745a..768d044 100644
--- a/media/java/android/media/MediaLibraryService2.java
+++ b/media/java/android/media/MediaLibraryService2.java
@@ -19,10 +19,10 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 import android.app.PendingIntent;
 import android.content.Context;
-import android.media.MediaSession2.BuilderBase;
+import android.media.MediaLibraryService2.MediaLibrarySession.Builder;
+import android.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback;
 import android.media.MediaSession2.ControllerInfo;
 import android.media.update.ApiLoader;
 import android.media.update.MediaLibraryService2Provider.LibraryRootProvider;
@@ -40,7 +40,7 @@
  * and ask the application to start playing it. They may also be used to control content that
  * is already playing by way of a {@link MediaSession2}.
  * <p>
- * To extend this class, adding followings directly to your {@code AndroidManifest.xml}.
+ * When extending this class, also add the following to your {@code AndroidManifest.xml}.
  * <pre>
  * &lt;service android:name="component_name_of_your_implementation" &gt;
  *   &lt;intent-filter&gt;
@@ -48,11 +48,12 @@
  *   &lt;/intent-filter&gt;
  * &lt;/service&gt;</pre>
  * <p>
- * A {@link MediaLibraryService2} is extension of {@link MediaSessionService2}. IDs shouldn't
+ * The {@link MediaLibraryService2} class derives from {@link MediaSessionService2}. IDs shouldn't
  * be shared between the {@link MediaSessionService2} and {@link MediaSession2}. By
  * default, an empty string will be used for ID of the service. If you want to specify an ID,
  * declare metadata in the manifest as follows.
- * @hide
+ *
+ * @see MediaSessionService2
  */
 public abstract class MediaLibraryService2 extends MediaSessionService2 {
     /**
@@ -63,15 +64,178 @@
 
     /**
      * Session for the {@link MediaLibraryService2}. Build this object with
-     * {@link MediaLibrarySessionBuilder} and return in {@link #onCreateSession(String)}.
+     * {@link Builder} and return in {@link #onCreateSession(String)}.
      */
-    public static class MediaLibrarySession extends MediaSession2 {
+    public static final class MediaLibrarySession extends MediaSession2 {
         private final MediaLibrarySessionProvider mProvider;
 
         /**
+         * Callback for the {@link MediaLibrarySession}.
+         */
+        public static class MediaLibrarySessionCallback extends MediaSession2.SessionCallback {
+            public MediaLibrarySessionCallback(Context context) {
+                super(context);
+            }
+
+            /**
+             * Called to get the root information for browsing by a particular client.
+             * <p>
+             * The implementation should verify that the client package has permission
+             * to access browse media information before returning the root id; it
+             * should return null if the client is not allowed to access this
+             * information.
+             *
+             * @param controllerInfo information of the controller requesting access to browse media.
+             * @param rootHints An optional bundle of service-specific arguments to send
+             * to the media library service when connecting and retrieving the
+             * root id for browsing, or null if none. The contents of this
+             * bundle may affect the information returned when browsing.
+             * @return The {@link LibraryRoot} for accessing this app's content or null.
+             * @see LibraryRoot#EXTRA_RECENT
+             * @see LibraryRoot#EXTRA_OFFLINE
+             * @see LibraryRoot#EXTRA_SUGGESTED
+             */
+            public @Nullable LibraryRoot onGetLibraryRoot(@NonNull ControllerInfo controllerInfo,
+                    @Nullable Bundle rootHints) {
+                return null;
+            }
+
+            /**
+             * Called to get an item. Return result here for the browser.
+             * <p>
+             * Return {@code null} for no result or error.
+             *
+             * @param mediaId item id to get media item.
+             * @return a media item. {@code null} for no result or error.
+             */
+            public @Nullable MediaItem2 onGetItem(@NonNull ControllerInfo controllerInfo,
+                    @NonNull String mediaId) {
+                return null;
+            }
+
+            /**
+             * Called to get children of given parent id. Return the children here for the browser.
+             * <p>
+             * Return an empty list for no children, and return {@code null} for the error.
+             *
+             * @param parentId parent id to get children
+             * @param page number of page
+             * @param pageSize size of the page
+             * @param extras extra bundle
+             * @return list of children. Can be {@code null}.
+             */
+            public @Nullable List<MediaItem2> onGetChildren(@NonNull ControllerInfo controller,
+                    @NonNull String parentId, int page, int pageSize, @Nullable Bundle extras) {
+                return null;
+            }
+
+            /**
+             * Called when a controller subscribes to the parent.
+             * <p>
+             * It's your responsibility to keep subscriptions by your own and call
+             * {@link MediaLibrarySession#notifyChildrenChanged(ControllerInfo, String, int, Bundle)}
+             * when the parent is changed.
+             *
+             * @param controller controller
+             * @param parentId parent id
+             * @param extras extra bundle
+             */
+            public void onSubscribe(@NonNull ControllerInfo controller, @NonNull String parentId,
+                    @Nullable Bundle extras) {
+            }
+
+            /**
+             * Called when a controller unsubscribes to the parent.
+             *
+             * @param controller controller
+             * @param parentId parent id
+             */
+            public void onUnsubscribe(@NonNull ControllerInfo controller,
+                    @NonNull String parentId) {
+            }
+
+            /**
+             * Called when a controller requests search.
+             *
+             * @param query The search query sent from the media browser. It contains keywords
+             *              separated by space.
+             * @param extras The bundle of service-specific arguments sent from the media browser.
+             */
+            public void onSearch(@NonNull ControllerInfo controllerInfo, @NonNull String query,
+                    @Nullable Bundle extras) {
+            }
+
+            /**
+             * Called to get the search result. Return search result here for the browser which has
+             * requested search previously.
+             * <p>
+             * Return an empty list for no search result, and return {@code null} for the error.
+             *
+             * @param controllerInfo Information of the controller requesting the search result.
+             * @param query The search query which was previously sent through
+             *              {@link #onSearch(ControllerInfo, String, Bundle)} call.
+             * @param page page number. Starts from {@code 1}.
+             * @param pageSize page size. Should be greater or equal to {@code 1}.
+             * @param extras The bundle of service-specific arguments sent from the media browser.
+             * @return search result. {@code null} for error.
+             */
+            public @Nullable List<MediaItem2> onGetSearchResult(
+                    @NonNull ControllerInfo controllerInfo, @NonNull String query, int page,
+                    int pageSize, @Nullable Bundle extras) {
+                return null;
+            }
+        }
+
+        /**
+         * Builder for {@link MediaLibrarySession}.
+         */
+        // Override all methods just to show them with the type instead of generics in Javadoc.
+        // This workarounds javadoc issue described in the MediaSession2.BuilderBase.
+        public static final class Builder extends BuilderBase<MediaLibrarySession, Builder,
+                MediaLibrarySessionCallback> {
+            // Builder requires MediaLibraryService2 instead of Context just to ensure that the
+            // builder can be only instantiated within the MediaLibraryService2.
+            // Ideally it's better to make it inner class of service to enforce, it violates API
+            // guideline that Builders should be the inner class of the building target.
+            public Builder(@NonNull MediaLibraryService2 service,
+                    @NonNull MediaPlayerBase player,
+                    @NonNull @CallbackExecutor Executor callbackExecutor,
+                    @NonNull MediaLibrarySessionCallback callback) {
+                super((instance) -> ApiLoader.getProvider(service)
+                        .createMediaLibraryService2Builder(service, (Builder) instance, player,
+                                callbackExecutor, callback));
+            }
+
+            @Override
+            public Builder setVolumeProvider(@Nullable VolumeProvider2 volumeProvider) {
+                return super.setVolumeProvider(volumeProvider);
+            }
+
+            @Override
+            public Builder setSessionActivity(@Nullable PendingIntent pi) {
+                return super.setSessionActivity(pi);
+            }
+
+            @Override
+            public Builder setId(String id) {
+                return super.setId(id);
+            }
+
+            @Override
+            public Builder setSessionCallback(@NonNull Executor executor,
+                    @NonNull MediaLibrarySessionCallback callback) {
+                return super.setSessionCallback(executor, callback);
+            }
+
+            @Override
+            public MediaLibrarySession build() {
+                return super.build();
+            }
+        }
+
+        /**
          * @hide
          */
-        @SystemApi
         public MediaLibrarySession(MediaLibrarySessionProvider provider) {
             super(provider);
             mProvider = provider;
@@ -124,166 +288,6 @@
         }
     }
 
-    /**
-     * Callback for the {@link MediaLibrarySession}.
-     */
-    public static class MediaLibrarySessionCallback extends MediaSession2.SessionCallback {
-
-        public MediaLibrarySessionCallback(Context context) {
-            super(context);
-        }
-
-        /**
-         * Called to get the root information for browsing by a particular client.
-         * <p>
-         * The implementation should verify that the client package has permission
-         * to access browse media information before returning the root id; it
-         * should return null if the client is not allowed to access this
-         * information.
-         *
-         * @param controllerInfo information of the controller requesting access to browse media.
-         * @param rootHints An optional bundle of service-specific arguments to send
-         * to the media library service when connecting and retrieving the
-         * root id for browsing, or null if none. The contents of this
-         * bundle may affect the information returned when browsing.
-         * @return The {@link LibraryRoot} for accessing this app's content or null.
-         * @see LibraryRoot#EXTRA_RECENT
-         * @see LibraryRoot#EXTRA_OFFLINE
-         * @see LibraryRoot#EXTRA_SUGGESTED
-         */
-        public @Nullable LibraryRoot onGetLibraryRoot(@NonNull ControllerInfo controllerInfo,
-                @Nullable Bundle rootHints) {
-            return null;
-        }
-
-        /**
-         * Called to get an item. Return result here for the browser.
-         * <p>
-         * Return {@code null} for no result or error.
-         *
-         * @param mediaId item id to get media item.
-         * @return a media item. {@code null} for no result or error.
-         */
-        public @Nullable MediaItem2 onGetItem(@NonNull ControllerInfo controllerInfo,
-                @NonNull String mediaId) {
-            return null;
-        }
-
-        /**
-         * Called to get children of given parent id. Return the children here for the browser.
-         * <p>
-         * Return an empty list for no children, and return {@code null} for the error.
-         *
-         * @param parentId parent id to get children
-         * @param page number of page
-         * @param pageSize size of the page
-         * @param extras extra bundle
-         * @return list of children. Can be {@code null}.
-         */
-        public @Nullable List<MediaItem2> onGetChildren(@NonNull ControllerInfo controller,
-                @NonNull String parentId, int page, int pageSize, @Nullable Bundle extras) {
-            return null;
-        }
-
-        /**
-         * Called when a controller subscribes to the parent.
-         * <p>
-         * It's your responsibility to keep subscriptions by your own and call
-         * {@link MediaLibrarySession#notifyChildrenChanged(ControllerInfo, String, int, Bundle)}
-         * when the parent is changed.
-         *
-         * @param controller controller
-         * @param parentId parent id
-         * @param extras extra bundle
-         */
-        public void onSubscribe(@NonNull ControllerInfo controller, @NonNull String parentId,
-                @Nullable Bundle extras) {
-        }
-
-        /**
-         * Called when a controller unsubscribes to the parent.
-         *
-         * @param controller controller
-         * @param parentId parent id
-         */
-        public void onUnsubscribe(@NonNull ControllerInfo controller, @NonNull String parentId) {
-        }
-
-        /**
-         * Called when a controller requests search.
-         *
-         * @param query The search query sent from the media browser. It contains keywords separated
-         *              by space.
-         * @param extras The bundle of service-specific arguments sent from the media browser.
-         */
-        public void onSearch(@NonNull ControllerInfo controllerInfo, @NonNull String query,
-                @Nullable Bundle extras) {
-        }
-
-        /**
-         * Called to get the search result. Return search result here for the browser which has
-         * requested search previously.
-         * <p>
-         * Return an empty list for no search result, and return {@code null} for the error.
-         *
-         * @param controllerInfo Information of the controller requesting the search result.
-         * @param query The search query which was previously sent through
-         *              {@link #onSearch(ControllerInfo, String, Bundle)} call.
-         * @param page page number. Starts from {@code 1}.
-         * @param pageSize page size. Should be greater or equal to {@code 1}.
-         * @param extras The bundle of service-specific arguments sent from the media browser.
-         * @return search result. {@code null} for error.
-         */
-        public @Nullable List<MediaItem2> onGetSearchResult(@NonNull ControllerInfo controllerInfo,
-                @NonNull String query, int page, int pageSize, @Nullable Bundle extras) {
-            return null;
-        }
-    }
-
-    /**
-     * Builder for {@link MediaLibrarySession}.
-     */
-    // Override all methods just to show them with the type instead of generics in Javadoc.
-    // This workarounds javadoc issue described in the MediaSession2.BuilderBase.
-    public class MediaLibrarySessionBuilder extends BuilderBase<MediaLibrarySession,
-            MediaLibrarySessionBuilder, MediaLibrarySessionCallback> {
-        public MediaLibrarySessionBuilder(
-                @NonNull Context context, @NonNull MediaPlayerInterface player,
-                @NonNull @CallbackExecutor Executor callbackExecutor,
-                @NonNull MediaLibrarySessionCallback callback) {
-            super((instance) -> ApiLoader.getProvider(context).createMediaLibraryService2Builder(
-                    context, (MediaLibrarySessionBuilder) instance, player, callbackExecutor,
-                    callback));
-        }
-
-        @Override
-        public MediaLibrarySessionBuilder setVolumeProvider(
-                @Nullable VolumeProvider2 volumeProvider) {
-            return super.setVolumeProvider(volumeProvider);
-        }
-
-        @Override
-        public MediaLibrarySessionBuilder setSessionActivity(@Nullable PendingIntent pi) {
-            return super.setSessionActivity(pi);
-        }
-
-        @Override
-        public MediaLibrarySessionBuilder setId(String id) {
-            return super.setId(id);
-        }
-
-        @Override
-        public MediaLibrarySessionBuilder setSessionCallback(
-                @NonNull Executor executor, @NonNull MediaLibrarySessionCallback callback) {
-            return super.setSessionCallback(executor, callback);
-        }
-
-        @Override
-        public MediaLibrarySession build() {
-            return super.build();
-        }
-    }
-
     @Override
     MediaSessionService2Provider createProvider() {
         return ApiLoader.getProvider(this).createMediaLibraryService2(this);
@@ -302,7 +306,7 @@
      *
      * @param sessionId session id written in the AndroidManifest.xml.
      * @return a new library session
-     * @see MediaLibrarySessionBuilder
+     * @see Builder
      * @see #getSession()
      * @throws RuntimeException if returned session is invalid
      */
diff --git a/media/java/android/media/MediaMetadata2.java b/media/java/android/media/MediaMetadata2.java
index 1f856bc..b363831 100644
--- a/media/java/android/media/MediaMetadata2.java
+++ b/media/java/android/media/MediaMetadata2.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringDef;
-import android.annotation.SystemApi;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.media.update.ApiLoader;
@@ -33,15 +32,13 @@
 
 /**
  * Contains metadata about an item, such as the title, artist, etc.
- *
- * @hide
  */
+// New version of MediaMetadata with following changes
+//   - Don't implement Parcelable for updatable support.
+//   - Also support MediaDescription features. MediaDescription is deprecated instead because
+//     it was insufficient for controller to display media contents.
+// TODO(jaewan): Add @see for APIs from MediaDescription
 public final class MediaMetadata2 {
-    // New version of MediaMetadata that no longer implements Parcelable but added from/toBundle()
-    // for updatable.
-    // MediaDescription is deprecated because it was insufficient for controller to display media
-    // contents. Added getExtra() here to support all the features from the MediaDescription.
-
     /**
      * The title of the media.
      */
@@ -389,7 +386,6 @@
     /**
      * @hide
      */
-    @SystemApi
     public MediaMetadata2(MediaMetadata2Provider provider) {
         mProvider = provider;
     }
@@ -568,7 +564,6 @@
         /**
          * @hide
          */
-        @SystemApi
         public Builder(@NonNull MediaMetadata2Provider.BuilderProvider provider) {
             mProvider = provider;
         }
diff --git a/media/java/android/media/MediaPlayerBase.java b/media/java/android/media/MediaPlayerBase.java
new file mode 100644
index 0000000..3181362
--- /dev/null
+++ b/media/java/android/media/MediaPlayerBase.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.MediaSession2.PlaylistParams;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Base class for all media players that want media session.
+ */
+public abstract class MediaPlayerBase implements AutoCloseable {
+    /**
+     * @hide
+     */
+    @IntDef({STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_ERROR})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface State {}
+
+    /**
+     * State when the player is idle, and needs configuration to start playback.
+     */
+    public static final int STATE_IDLE = 0;
+
+    /**
+     * State when the player's playback is paused
+     */
+    public static final int STATE_PAUSED = 0;
+
+    /**
+     * State when the player's playback is ongoing
+     */
+    public static final int STATE_PLAYING = 0;
+
+    /**
+     * State when the player is in error state and cannot be recovered self.
+     */
+    public static final int STATE_ERROR = 0;
+
+    /**
+     * Unspecified media player error.
+     * @hide
+     */
+    public static final int MEDIA_ERROR_UNKNOWN = MediaPlayer2.MEDIA_ERROR_UNKNOWN;
+
+    /**
+     * The video is streamed and its container is not valid for progressive
+     * playback i.e the video's index (e.g moov atom) is not at the start of the
+     * file.
+     * @hide
+     */
+    public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK =
+            MediaPlayer2.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK;
+
+    /**
+     * File or network related operation errors.
+     * @hide
+     */
+    public static final int MEDIA_ERROR_IO = MediaPlayer2.MEDIA_ERROR_IO;
+
+    /**
+     * Bitstream is not conforming to the related coding standard or file spec.
+     * @hide
+     */
+    public static final int MEDIA_ERROR_MALFORMED = MediaPlayer2.MEDIA_ERROR_MALFORMED;
+
+    /**
+     * Bitstream is conforming to the related coding standard or file spec, but
+     * the media framework does not support the feature.
+     * @hide
+     */
+    public static final int MEDIA_ERROR_UNSUPPORTED = MediaPlayer2.MEDIA_ERROR_UNSUPPORTED;
+
+    /**
+     * Some operation takes too long to complete, usually more than 3-5 seconds.
+     * @hide
+     */
+    public static final int MEDIA_ERROR_TIMED_OUT = MediaPlayer2.MEDIA_ERROR_TIMED_OUT;
+
+    /**
+     * Callbacks to listens to the changes in {@link PlaybackState2} and error.
+     * @hide
+     */
+    public static abstract class EventCallback {
+        /**
+         * Called when {@link PlaybackState2} for this player is changed.
+         */
+        public void onPlaybackStateChanged(PlaybackState2 state) { }
+
+        /**
+         * Called to indicate an error.
+         *
+         * @param mediaId optional mediaId to indicate error
+         * @param what what
+         * @param extra
+         */
+        public void onError(@Nullable String mediaId, int what, int extra) { }
+    }
+
+    // Transport controls that session will send command directly to this player.
+    /**
+     * Start or resumes playback
+     */
+    public abstract void play();
+
+    /**
+     * @hide
+     */
+    public abstract void prepare();
+
+    /**
+     * Pause playback
+     */
+    public abstract void pause();
+
+    /**
+     * @hide
+     */
+    public abstract void stop();
+
+    /**
+     * @hide
+     */
+    public abstract void skipToPrevious();
+
+    /**
+     * @hide
+     */
+    public abstract void skipToNext();
+
+    /**
+     * @hide
+     */
+    public abstract void seekTo(long pos);
+
+    /**
+     * @hide
+     */
+    public abstract void fastForward();
+
+    /**
+     * @hide
+     */
+    public abstract void rewind();
+
+    /**
+     * @hide
+     */
+    public abstract PlaybackState2 getPlaybackState();
+
+    /**
+     * Return player state.
+     *
+     * @return player state
+     * @see #STATE_IDLE
+     * @see #STATE_PLAYING
+     * @see #STATE_PAUSED
+     * @see #STATE_ERROR
+     */
+    public abstract @State int getPlayerState();
+
+    /**
+     * Sets the {@link AudioAttributes} to be used during the playback of the media.
+     *
+     * @param attributes non-null <code>AudioAttributes</code>.
+     */
+    public abstract void setAudioAttributes(@NonNull AudioAttributes attributes);
+
+    /**
+     * Returns AudioAttributes that media player has.
+     */
+    public abstract @Nullable AudioAttributes getAudioAttributes();
+
+    /**
+     * @hide
+     */
+    public abstract void addPlaylistItem(int index, MediaItem2 item);
+
+    /**
+     * @hide
+     */
+    public abstract void removePlaylistItem(MediaItem2 item);
+
+    /**
+     * @hide
+     */
+    public abstract void setPlaylist(List<MediaItem2> playlist);
+
+    /**
+     * @hide
+     */
+    public abstract List<MediaItem2> getPlaylist();
+
+    /**
+     * @hide
+     */
+    public abstract void setCurrentPlaylistItem(MediaItem2 item);
+
+    /**
+     * @hide
+     */
+    public abstract void setPlaylistParams(PlaylistParams params);
+
+    /**
+     * @hide
+     */
+    public abstract PlaylistParams getPlaylistParams();
+
+    /**
+     * Register a {@link EventCallback}.
+     *
+     * @param executor a callback executor
+     * @param callback a EventCallback
+     * @hide
+     */
+    public abstract void registerEventCallback(@NonNull @CallbackExecutor Executor executor,
+            @NonNull EventCallback callback);
+
+    /**
+     * Unregister previously registered {@link EventCallback}.
+     *
+     * @param callback a EventCallback
+     * @hide
+     */
+    public abstract void unregisterEventCallback(@NonNull EventCallback callback);
+}
diff --git a/media/java/android/media/MediaPlayerInterface.java b/media/java/android/media/MediaPlayerInterface.java
deleted file mode 100644
index b81c3d6..0000000
--- a/media/java/android/media/MediaPlayerInterface.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-import android.annotation.CallbackExecutor;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.media.MediaSession2.PlaylistParams;
-
-import java.util.List;
-import java.util.concurrent.Executor;
-
-/**
- * Base interfaces for all media players that want media session.
- * @hide
- */
-public interface MediaPlayerInterface {
-    /**
-     * Unspecified media player error.
-     */
-    int MEDIA_ERROR_UNKNOWN = MediaPlayer2.MEDIA_ERROR_UNKNOWN;
-
-    /**
-     * The video is streamed and its container is not valid for progressive
-     * playback i.e the video's index (e.g moov atom) is not at the start of the
-     * file.
-     */
-    int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK =
-            MediaPlayer2.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK;
-
-    /**
-     * File or network related operation errors.
-     */
-    int MEDIA_ERROR_IO = MediaPlayer2.MEDIA_ERROR_IO;
-
-    /**
-     * Bitstream is not conforming to the related coding standard or file spec.
-     */
-    int MEDIA_ERROR_MALFORMED = MediaPlayer2.MEDIA_ERROR_MALFORMED;
-
-    /**
-     * Bitstream is conforming to the related coding standard or file spec, but
-     * the media framework does not support the feature.
-     */
-    int MEDIA_ERROR_UNSUPPORTED = MediaPlayer2.MEDIA_ERROR_UNSUPPORTED;
-
-    /**
-     * Some operation takes too long to complete, usually more than 3-5 seconds.
-     */
-    int MEDIA_ERROR_TIMED_OUT = MediaPlayer2.MEDIA_ERROR_TIMED_OUT;
-
-    /**
-     * Callbacks to listens to the changes in {@link PlaybackState2} and error.
-     */
-    interface EventCallback {
-        /**
-         * Called when {@link PlaybackState2} for this player is changed.
-         */
-        default void onPlaybackStateChanged(PlaybackState2 state) { }
-
-        /**
-         * Called to indicate an error.
-         *
-         * @param mediaId optional mediaId to indicate error
-         * @param what what
-         * @param extra
-         */
-        default void onError(@Nullable String mediaId, int what, int extra) { }
-    }
-
-    // Transport controls that session will send command directly to this player.
-    void play();
-    void prepare();
-    void pause();
-    void stop();
-    void skipToPrevious();
-    void skipToNext();
-    void seekTo(long pos);
-    void fastForward();
-    void rewind();
-
-    PlaybackState2 getPlaybackState();
-
-    /**
-     * Sets the {@link AudioAttributes} to be used during the playback of the media.
-     *
-     * @param attributes non-null <code>AudioAttributes</code>.
-     */
-    void setAudioAttributes(@NonNull AudioAttributes attributes);
-
-    /**
-     * Returns AudioAttributes that media player has.
-     */
-    @Nullable
-    AudioAttributes getAudioAttributes();
-
-    void addPlaylistItem(int index, MediaItem2 item);
-    void removePlaylistItem(MediaItem2 item);
-
-    void setPlaylist(List<MediaItem2> playlist);
-    List<MediaItem2> getPlaylist();
-
-    void setCurrentPlaylistItem(int index);
-    void setPlaylistParams(PlaylistParams params);
-    PlaylistParams getPlaylistParams();
-
-    /**
-     * Register a {@link EventCallback}.
-     *
-     * @param executor a callback executor
-     * @param callback a EventCallback
-     */
-    void registerEventCallback(@NonNull @CallbackExecutor Executor executor,
-            @NonNull EventCallback callback);
-
-    /**
-     * Unregister previously registered {@link EventCallback}.
-     *
-     * @param callback a EventCallback
-     */
-    void unregisterEventCallback(@NonNull EventCallback callback);
-}
diff --git a/media/java/android/media/MediaPlaylistController.java b/media/java/android/media/MediaPlaylistController.java
new file mode 100644
index 0000000..916c12a
--- /dev/null
+++ b/media/java/android/media/MediaPlaylistController.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.NonNull;
+
+import java.util.List;
+
+/**
+ * Controller interfaces for playlist management for both {@link MediaSession2} and
+ * {@link MediaController2} that related with metadata. This ensures that two classes share the same
+ * interface.
+ * <p>
+ * This class only includes methods that involves {@link MediaItem2}. Because other APIs are
+ * considered as the part of {@link MediaPlayerBase} (e.g. set/getPlaylistParams()}. Note that
+ * setPlaylist() isn't added on purpose because it's considered as session specific.
+ *
+ * @hide
+ */
+public interface MediaPlaylistController {
+    // TODO(jaewan): is Index correct here?
+    void addPlaylistItem(int index, @NonNull MediaItem2 item);
+    void removePlaylistItem(@NonNull MediaItem2 item);
+    MediaItem2 getCurrentPlaylistItem();
+    void skipToPlaylistItem(@NonNull MediaItem2 item);
+    List<MediaItem2> getPlaylist();
+}
diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java
index 63e4e65..54b1f0e 100644
--- a/media/java/android/media/MediaSession2.java
+++ b/media/java/android/media/MediaSession2.java
@@ -20,11 +20,10 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
-import android.media.MediaPlayerInterface.EventCallback;
+import android.media.MediaPlayerBase.EventCallback;
 import android.media.session.MediaSession;
 import android.media.session.MediaSession.Callback;
 import android.media.session.PlaybackState;
@@ -70,7 +69,7 @@
  * <p>
  * When a session receive transport control commands, the session sends the commands directly to
  * the the underlying media player set by {@link Builder} or
- * {@link #setPlayer(MediaPlayerInterface)}.
+ * {@link #setPlayer(MediaPlayerBase)}.
  * <p>
  * When an app is finished performing playback it must call {@link #close()} to clean up the session
  * and notify any controllers.
@@ -78,9 +77,8 @@
  * {@link MediaSession2} objects should be used on the thread on the looper.
  *
  * @see MediaSessionService2
- * @hide
  */
-public class MediaSession2 implements AutoCloseable {
+public class MediaSession2 implements AutoCloseable, MediaPlaylistController {
     private final MediaSession2Provider mProvider;
 
     // TODO(jaewan): Should we define IntDef? Currently we don't have to allow subclass to add more.
@@ -143,7 +141,7 @@
     public static final int COMMAND_CODE_PLAYBACK_PREPARE = 6;
 
     /**
-     * Command code for {@link MediaController2#fastForward()} ()}.
+     * Command code for {@link MediaController2#fastForward()}.
      * <p>
      * This is transport control command. Command would be sent directly to the player if the
      * session doesn't reject the request through the
@@ -160,14 +158,14 @@
     public static final int COMMAND_CODE_PLAYBACK_REWIND = 8;
 
     /**
-     * Command code for {@link MediaController2#seekTo(long)} ()}.
+     * Command code for {@link MediaController2#seekTo(long)}.
      * <p>
      * Command would be sent directly to the player if the session doesn't reject the request
      * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}.
      */
     public static final int COMMAND_CODE_PLAYBACK_SEEK_TO = 9;
     /**
-     * Command code for {@link MediaController2#setCurrentPlaylistItem(int)} ()}.
+     * Command code for {@link MediaController2#skipToPlaylistItem(MediaItem2)}.
      * <p>
      * Command would be sent directly to the player if the session doesn't reject the request
      * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}.
@@ -222,7 +220,7 @@
     public static final int COMMAND_CODE_PLAY_FROM_MEDIA_ID = 16;
 
     /**
-     * Command code for {@link MediaController2#playFromUri(String, Bundle)}.
+     * Command code for {@link MediaController2#playFromUri(Uri, Bundle)}.
      */
     public static final int COMMAND_CODE_PLAY_FROM_URI = 17;
 
@@ -256,6 +254,84 @@
     public static final int COMMAND_CODE_BROWSER = 22;
 
     /**
+     * @hide
+     */
+    @IntDef({ERROR_CODE_UNKNOWN_ERROR, ERROR_CODE_APP_ERROR, ERROR_CODE_NOT_SUPPORTED,
+            ERROR_CODE_AUTHENTICATION_EXPIRED, ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED,
+            ERROR_CODE_CONCURRENT_STREAM_LIMIT, ERROR_CODE_PARENTAL_CONTROL_RESTRICTED,
+            ERROR_CODE_NOT_AVAILABLE_IN_REGION, ERROR_CODE_CONTENT_ALREADY_PLAYING,
+            ERROR_CODE_SKIP_LIMIT_REACHED, ERROR_CODE_ACTION_ABORTED, ERROR_CODE_END_OF_QUEUE,
+            ERROR_CODE_SETUP_REQUIRED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ErrorCode {}
+
+    /**
+     * This is the default error code and indicates that none of the other error codes applies.
+     */
+    public static final int ERROR_CODE_UNKNOWN_ERROR = 0;
+
+    /**
+     * Error code when the application state is invalid to fulfill the request.
+     */
+    public static final int ERROR_CODE_APP_ERROR = 1;
+
+    /**
+     * Error code when the request is not supported by the application.
+     */
+    public static final int ERROR_CODE_NOT_SUPPORTED = 2;
+
+    /**
+     * Error code when the request cannot be performed because authentication has expired.
+     */
+    public static final int ERROR_CODE_AUTHENTICATION_EXPIRED = 3;
+
+    /**
+     * Error code when a premium account is required for the request to succeed.
+     */
+    public static final int ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED = 4;
+
+    /**
+     * Error code when too many concurrent streams are detected.
+     */
+    public static final int ERROR_CODE_CONCURRENT_STREAM_LIMIT = 5;
+
+    /**
+     * Error code when the content is blocked due to parental controls.
+     */
+    public static final int ERROR_CODE_PARENTAL_CONTROL_RESTRICTED = 6;
+
+    /**
+     * Error code when the content is blocked due to being regionally unavailable.
+     */
+    public static final int ERROR_CODE_NOT_AVAILABLE_IN_REGION = 7;
+
+    /**
+     * Error code when the requested content is already playing.
+     */
+    public static final int ERROR_CODE_CONTENT_ALREADY_PLAYING = 8;
+
+    /**
+     * Error code when the application cannot skip any more songs because skip limit is reached.
+     */
+    public static final int ERROR_CODE_SKIP_LIMIT_REACHED = 9;
+
+    /**
+     * Error code when the action is interrupted due to some external event.
+     */
+    public static final int ERROR_CODE_ACTION_ABORTED = 10;
+
+    /**
+     * Error code when the playback navigation (previous, next) is not possible because the queue
+     * was exhausted.
+     */
+    public static final int ERROR_CODE_END_OF_QUEUE = 11;
+
+    /**
+     * Error code when the session needs user's manual intervention.
+     */
+    public static final int ERROR_CODE_SETUP_REQUIRED = 12;
+
+    /**
      * Define a command that a {@link MediaController2} can send to a {@link MediaSession2}.
      * <p>
      * If {@link #getCommandCode()} isn't {@link #COMMAND_CODE_CUSTOM}), it's predefined command.
@@ -324,7 +400,7 @@
     /**
      * Represent set of {@link Command}.
      */
-    public static class CommandGroup {
+    public static final class CommandGroup {
         private final CommandGroupProvider mProvider;
 
         public CommandGroup(Context context) {
@@ -360,7 +436,6 @@
         /**
          * @hide
          */
-        @SystemApi
         public CommandGroupProvider getProvider() {
             return mProvider;
         }
@@ -390,7 +465,7 @@
      * default.
      */
     // TODO(jaewan): Can we move this inside of the updatable for default implementation.
-    public static class SessionCallback {
+    public static abstract class SessionCallback {
         private final Context mContext;
 
         public SessionCallback(Context context) {
@@ -587,7 +662,7 @@
     /**
      * Base builder class for MediaSession2 and its subclass. Any change in this class should be
      * also applied to the subclasses {@link MediaSession2.Builder} and
-     * {@link MediaLibraryService2.MediaLibrarySessionBuilder}.
+     * {@link MediaLibraryService2.MediaLibrarySession.Builder}.
      * <p>
      * APIs here should be package private, but should have documentations for developers.
      * Otherwise, javadoc will generate documentation with the generic types such as follows.
@@ -684,7 +759,7 @@
     // Override all methods just to show them with the type instead of generics in Javadoc.
     // This workarounds javadoc issue described in the MediaSession2.BuilderBase.
     public static final class Builder extends BuilderBase<MediaSession2, Builder, SessionCallback> {
-        public Builder(Context context, @NonNull MediaPlayerInterface player) {
+        public Builder(Context context, @NonNull MediaPlayerBase player) {
             super((instance) -> ApiLoader.getProvider(context).createMediaSession2Builder(
                     context, (Builder) instance, player));
         }
@@ -725,7 +800,6 @@
         /**
          * @hide
          */
-        // TODO(jaewan): SystemApi
         // TODO(jaewan): Also accept componentName to check notificaiton listener.
         public ControllerInfo(Context context, int uid, int pid, String packageName,
                 IInterface callback) {
@@ -762,7 +836,6 @@
         /**
          * @hide
          */
-        @SystemApi
         public ControllerInfoProvider getProvider() {
             return mProvider;
         }
@@ -794,13 +867,12 @@
      * <p>
      * It's up to the controller's decision to respect or ignore this customization request.
      */
-    public static class CommandButton {
+    public static final class CommandButton {
         private final CommandButtonProvider mProvider;
 
         /**
          * @hide
          */
-        @SystemApi
         public CommandButton(CommandButtonProvider provider) {
             mProvider = provider;
         }
@@ -856,7 +928,6 @@
         /**
          * @hide
          */
-        @SystemApi
         public CommandButtonProvider getProvider() {
             return mProvider;
         }
@@ -864,7 +935,7 @@
         /**
          * Builder for {@link CommandButton}.
          */
-        public static class Builder {
+        public static final class Builder {
             private final CommandButtonProvider.BuilderProvider mProvider;
 
             public Builder(@NonNull Context context) {
@@ -1038,7 +1109,6 @@
      *       framework had to add heuristics to figure out if an app is
      * @hide
      */
-    @SystemApi
     public MediaSession2(MediaSession2Provider provider) {
         super();
         mProvider = provider;
@@ -1047,39 +1117,34 @@
     /**
      * @hide
      */
-    @SystemApi
     public MediaSession2Provider getProvider() {
         return mProvider;
     }
 
     /**
-     * Set the underlying {@link MediaPlayerInterface} for this session to dispatch incoming event
+     * Set the underlying {@link MediaPlayerBase} for this session to dispatch incoming event
      * to. Events from the {@link MediaController2} will be sent directly to the underlying
      * player on the {@link Handler} where the session is created on.
      * <p>
-     * If the new player is successfully set,
-     * {@link EventCallback#onPlaybackStateChanged(PlaybackState2)} will be called to tell the
-     * current playback state of the new player.
-     * <p>
      * For the remote playback case which you want to handle volume by yourself, use
-     * {@link #setPlayer(MediaPlayerInterface, VolumeProvider2)}.
+     * {@link #setPlayer(MediaPlayerBase, VolumeProvider2)}.
      *
-     * @param player a {@link MediaPlayerInterface} that handles actual media playback in your app.
+     * @param player a {@link MediaPlayerBase} that handles actual media playback in your app.
      * @throws IllegalArgumentException if the player is {@code null}.
      */
-    public void setPlayer(@NonNull MediaPlayerInterface player) {
+    public void setPlayer(@NonNull MediaPlayerBase player) {
         mProvider.setPlayer_impl(player);
     }
 
     /**
-     * Set the underlying {@link MediaPlayerInterface} with the volume provider for remote playback.
+     * Set the underlying {@link MediaPlayerBase} with the volume provider for remote playback.
      *
-     * @param player a {@link MediaPlayerInterface} that handles actual media playback in your app.
+     * @param player a {@link MediaPlayerBase} that handles actual media playback in your app.
      * @param volumeProvider a volume provider
-     * @see #setPlayer(MediaPlayerInterface)
+     * @see #setPlayer(MediaPlayerBase)
      * @see Builder#setVolumeProvider(VolumeProvider2)
      */
-    public void setPlayer(@NonNull MediaPlayerInterface player,
+    public void setPlayer(@NonNull MediaPlayerBase player,
             @NonNull VolumeProvider2 volumeProvider) {
         mProvider.setPlayer_impl(player, volumeProvider);
     }
@@ -1093,7 +1158,7 @@
      * @return player
      */
     public @Nullable
-    MediaPlayerInterface getPlayer() {
+    MediaPlayerBase getPlayer() {
         return mProvider.getPlayer_impl();
     }
 
@@ -1123,7 +1188,9 @@
      * @param focusGain the type of audio focus gain that will be requested, or
      *                  {@link AudioManager#AUDIOFOCUS_NONE} to disable the use audio focus during
      *                  playback.
+     * @hide
      */
+    // TODO(jaewan): Revisit
     public void setAudioFocusRequest(int focusGain) {
         mProvider.setAudioFocusRequest_impl(focusGain);
     }
@@ -1164,14 +1231,6 @@
     }
 
     /**
-     * Notify changes in metadata of previously set playlist. Controller will get the whole set of
-     * playlist again.
-     */
-    public void notifyMetadataChanged() {
-        mProvider.notifyMetadataChanged_impl();
-    }
-
-    /**
      * Send custom command to all connected controllers.
      *
      * @param command a command
@@ -1264,14 +1323,14 @@
     }
 
     /**
-     * Sets the index of current DataSourceDesc in the play list to be played.
+     * Skip to the item in the play list.
      *
-     * @param index the index of DataSourceDesc in the play list you want to play
+     * @param item item in the play list you want to play
      * @throws IllegalArgumentException if the play list is null
      * @throws NullPointerException if index is outside play list range
      */
-    public void setCurrentPlaylistItem(int index) {
-        mProvider.setCurrentPlaylistItem_impl(index);
+    public void skipToPlaylistItem(MediaItem2 item) {
+        mProvider.skipToPlaylistItem_impl(item);
     }
 
     /**
@@ -1289,7 +1348,7 @@
     }
 
     /**
-     * Sets a list of {@link MediaItem2} as the current play list.
+     * Set a list of {@link MediaItem2} as the current play list.
      *
      * @param playlist A list of {@link MediaItem2} objects to set as a play list.
      * @throws IllegalArgumentException if given {@param playlist} is null.
@@ -1299,13 +1358,68 @@
     }
 
     /**
-     * Returns the playlist which is lastly set.
+     * Remove the media item at index in the play list.
+     * <p>
+     * If index is same as the current index of the playlist, current playback
+     * will be stopped and playback moves to next source in the list.
+     *
+     * @throws IllegalArgumentException if the play list is null
      */
+    // TODO(jaewan): Remove with index was previously rejected by council (b/36524925)
+    // TODO(jaewan): Should we also add movePlaylistItem from index to index?
+    public void removePlaylistItem(MediaItem2 item) {
+        mProvider.removePlaylistItem_impl(item);
+    }
+
+    /**
+     * Add the media item to the play list at position index.
+     * <p>
+     * This will not change the currently playing media item.
+     * If index is less than or equal to the current index of the play list,
+     * the current index of the play list will be incremented correspondingly.
+     *
+     * @param index the index you want to add
+     * @param item the media item you want to add
+     * @throws IndexOutOfBoundsException if index is outside play list range
+     */
+    @Override
+    public void addPlaylistItem(int index, @NonNull MediaItem2 item) {
+        mProvider.addPlaylistItem_impl(index, item);
+    }
+
+    /**
+     * Edit the media item to the play list at position index. This is expected to be called when
+     * the metadata information is updated.
+     * <p>
+     * This will not change the currently playing media item.
+     *
+     * @param item the media item you want to add to the play list
+     */
+    public void editPlaylistItem(@NonNull MediaItem2 item) {
+        mProvider.editPlaylistItem_impl(item);
+    }
+
+    /**
+     * Return the playlist which is lastly set.
+     *
+     * @return playlist
+     */
+    @Override
     public List<MediaItem2> getPlaylist() {
         return mProvider.getPlaylist_impl();
     }
 
     /**
+     * Return currently playing media item.
+     *
+     * @return currently playing media item
+     */
+    @Override
+    public MediaItem2 getCurrentPlaylistItem() {
+        return mProvider.getCurrentPlaylistItem_impl();
+    }
+
+    /**
      * Sets the {@link PlaylistParams} for the current play list. Repeat/shuffle mode and metadata
      * for the list can be set by calling this method.
      *
@@ -1324,16 +1438,28 @@
         return mProvider.getPlaylistParams_impl();
     }
 
-    /*
+    /**
+     * Notify errors to the connected controllers
+     *
+     * @param errorCode error code
+     * @param extra extra
+     */
+    public void notifyError(@ErrorCode int errorCode, int extra) {
+        mProvider.notifyError_impl(errorCode, extra);
+    }
+
+    /**
      * Register {@link EventCallback} to listen changes in the underlying
-     * {@link MediaPlayerInterface}, regardless of the change in the underlying player.
+     * {@link MediaPlayerBase}, regardless of the change in the underlying player.
      * <p>
      * Registered callbacks will be also called when the underlying player is changed.
      *
      * @param executor a callback Executor
      * @param callback a EventCallback
      * @throws IllegalArgumentException if executor or callback is {@code null}.
+     * @hide
      */
+    // TODO(jaewan): Unhide or remove
     public void registerPlayerEventCallback(@NonNull @CallbackExecutor Executor executor,
             @NonNull EventCallback callback) {
         mProvider.registerPlayerEventCallback_impl(executor, callback);
@@ -1344,7 +1470,9 @@
      *
      * @param callback the callback to be removed
      * @throws IllegalArgumentException if the callback is {@code null}.
+     * @hide
      */
+    // TODO(jaewan): Unhide or remove
     public void unregisterPlayerEventCallback(@NonNull EventCallback callback) {
         mProvider.unregisterPlayerEventCallback_impl(callback);
     }
@@ -1353,6 +1481,7 @@
      * Return the {@link PlaybackState2} from the player.
      *
      * @return playback state
+     * @hide
      */
     public PlaybackState2 getPlaybackState() {
         return mProvider.getPlaybackState_impl();
diff --git a/media/java/android/media/MediaSessionService2.java b/media/java/android/media/MediaSessionService2.java
index 0b5dddf..56e8e5d 100644
--- a/media/java/android/media/MediaSessionService2.java
+++ b/media/java/android/media/MediaSessionService2.java
@@ -90,7 +90,7 @@
  * rejected, the controller will unbind. If it's accepted, the controller will be available to use
  * and keep binding.
  * <p>
- * When playback is started for this session service, {@link #onUpdateNotification(PlaybackState2)}
+ * When playback is started for this session service, {@link #onUpdateNotification()}
  * is called and service would become a foreground service. It's needed to keep playback after the
  * controller is destroyed. The session service becomes background service when the playback is
  * stopped.
@@ -100,7 +100,6 @@
  * Any app can bind to the session service with controller, but the controller can be used only if
  * the session service accepted the connection request through
  * {@link MediaSession2.SessionCallback#onConnect(ControllerInfo)}.
- * @hide
  */
 public abstract class MediaSessionService2 extends Service {
     private final MediaSessionService2Provider mProvider;
@@ -158,17 +157,16 @@
     public @NonNull abstract MediaSession2 onCreateSession(String sessionId);
 
     /**
-     * Called when the playback state of this session is changed, and notification needs update.
-     * Override this method to show your own notification UI.
+     * Called when the playback state of this session is changed so notification needs update.
+     * Override this method to show or cancel your own notification UI.
      * <p>
      * With the notification returned here, the service become foreground service when the playback
      * is started. It becomes background service after the playback is stopped.
      *
-     * @param state playback state
      * @return a {@link MediaNotification}. If it's {@code null}, notification wouldn't be shown.
      */
-    public MediaNotification onUpdateNotification(PlaybackState2 state) {
-        return mProvider.onUpdateNotification_impl(state);
+    public MediaNotification onUpdateNotification() {
+        return mProvider.onUpdateNotification_impl();
     }
 
     /**
@@ -201,9 +199,9 @@
     }
 
     /**
-     * Returned by {@link #onUpdateNotification(PlaybackState2)} for making session service
-     * foreground service to keep playback running in the background. It's highly recommended to
-     * show media style notification here.
+     * Returned by {@link #onUpdateNotification()} for making session service forground service
+     * to keep playback running in the background. It's highly recommended to show media style
+     * notification here.
      */
     public static class MediaNotification {
         private final MediaNotificationProvider mProvider;
diff --git a/media/java/android/media/PlaybackState2.java b/media/java/android/media/PlaybackState2.java
index a95b8f2..7afb579 100644
--- a/media/java/android/media/PlaybackState2.java
+++ b/media/java/android/media/PlaybackState2.java
@@ -28,11 +28,12 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * Playback state for a {@link MediaPlayerInterface}, to be shared between {@link MediaSession2} and
+ * Playback state for a {@link MediaPlayerBase}, to be shared between {@link MediaSession2} and
  * {@link MediaController2}. This includes a playback state {@link #STATE_PLAYING},
  * the current playback position and extra.
  * @hide
  */
+// TODO(jaewan): Remove this.
 public final class PlaybackState2 {
     // Similar to the PlaybackState with following changes
     //    - Not implement Parcelable and added from/toBundle()
@@ -214,4 +215,4 @@
             @Nullable Bundle bundle) {
         return ApiLoader.getProvider(context).fromBundle_PlaybackState2(context, bundle);
     }
-}
\ No newline at end of file
+}
diff --git a/media/java/android/media/Rating2.java b/media/java/android/media/Rating2.java
index 4f77ecd..e5b05fb 100644
--- a/media/java/android/media/Rating2.java
+++ b/media/java/android/media/Rating2.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.IntDef;
-import android.annotation.SystemApi;
 import android.content.Context;
 import android.media.update.ApiLoader;
 import android.media.update.Rating2Provider;
@@ -36,12 +35,10 @@
  * {@link #RATING_5_STARS} or {@link #RATING_PERCENTAGE}) and the actual rating value (which may
  * be defined as "unrated"), both of which are defined when the rating instance is constructed
  * through one of the factory methods.
- * @hide
  */
+// New version of Rating with following change
+//   - Don't implement Parcelable for updatable support.
 public final class Rating2 {
-    // Mostly same as the android.media.Rating, but it's no longer implements Parcelable for
-    // updatable support.
-
     /**
      * @hide
      */
@@ -100,7 +97,6 @@
     /**
      * @hide
      */
-    @SystemApi
     public Rating2(@NonNull Rating2Provider provider) {
         mProvider = provider;
     }
@@ -113,7 +109,6 @@
     /**
      * @hide
      */
-    @SystemApi
     public Rating2Provider getProvider() {
         return mProvider;
     }
diff --git a/media/java/android/media/SessionToken2.java b/media/java/android/media/SessionToken2.java
index 2c2090c..fdfa43a 100644
--- a/media/java/android/media/SessionToken2.java
+++ b/media/java/android/media/SessionToken2.java
@@ -18,7 +18,6 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.SystemApi;
 import android.content.Context;
 import android.media.session.MediaSessionManager;
 import android.media.update.ApiLoader;
@@ -36,8 +35,11 @@
  * {@link MediaController2} to communicate with the session.
  * <p>
  * It can be also obtained by {@link MediaSessionManager}.
- * @hide
  */
+// New version of MediaSession.Token for following reasons
+//   - Stop implementing Parcelable for updatable support
+//   - Represent session and library service (formerly browser service) in one class.
+//     Previously MediaSession.Token was for session and ComponentName was for service.
 public final class SessionToken2 {
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(value = {TYPE_SESSION, TYPE_SESSION_SERVICE, TYPE_LIBRARY_SERVICE})
@@ -86,7 +88,6 @@
      * Constructor for the token.
      * @hide
      */
-    @SystemApi
     public SessionToken2(@NonNull SessionToken2Provider provider) {
         mProvider = provider;
     }
@@ -106,7 +107,9 @@
         return mProvider.toString_impl();
     }
 
-    @SystemApi
+    /**
+     * @hide
+     */
     public SessionToken2Provider getProvider() {
         return mProvider;
     }
@@ -147,7 +150,7 @@
      * @return
      */
     public static SessionToken2 fromBundle(@NonNull Context context, @NonNull Bundle bundle) {
-        return ApiLoader.getProvider(context).SessionToken2_fromBundle(context, bundle);
+        return ApiLoader.getProvider(context).fromBundle_SessionToken2(context, bundle);
     }
 
     /**
diff --git a/media/java/android/media/VolumeProvider2.java b/media/java/android/media/VolumeProvider2.java
index 53ba466..8e1cfbf 100644
--- a/media/java/android/media/VolumeProvider2.java
+++ b/media/java/android/media/VolumeProvider2.java
@@ -18,7 +18,6 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.SystemApi;
 import android.content.Context;
 import android.media.update.ApiLoader;
 import android.media.update.VolumeProvider2Provider;
@@ -32,12 +31,11 @@
  * {@link #setCurrentVolume(int)} each time the volume being provided changes.
  * <p>
  * You can set a volume provider on a session by calling
- * {@link MediaSession2#setPlayer(MediaPlayerInterface, VolumeProvider2)}.
- *
- * @hide
+ * {@link MediaSession2#setPlayer(MediaPlayerBase, VolumeProvider2)}.
  */
+// New version of VolumeProvider with following changes
+//   - Don't implement Parcelable for updatable support.
 public abstract class VolumeProvider2 {
-
     /**
      * @hide
      */
@@ -85,7 +83,6 @@
     /**
      * @hide
      */
-    @SystemApi
     public VolumeProvider2Provider getProvider() {
         return mProvider;
     }
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index e7fcc7f..d079b7a 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -343,7 +343,6 @@
      * Called when a {@link MediaSession2} is created.
      * @hide
      */
-    // TODO(jaewan): System API
     public boolean createSession2(@NonNull SessionToken2 token) {
         if (token == null) {
             return false;
@@ -356,10 +355,10 @@
         return false;
     }
 
-    /** Called when a {@link MediaSession2} is destroyed.
+    /**
+     * Called when a {@link MediaSession2} is destroyed.
      * @hide
      */
-    // TODO(jaewan): System API
     public void destroySession2(@NonNull SessionToken2 token) {
         if (token == null) {
             return;
@@ -381,9 +380,7 @@
      * using the {@link NotificationListenerService} APIs.
      *
      * @return list of tokens
-     * @hide
      */
-    // TODO(jaewan): Unhide
     public List<SessionToken2> getActiveSessionTokens() {
         try {
             List<Bundle> bundles = mService.getSessionTokens(
@@ -404,9 +401,7 @@
      * using the {@link NotificationListenerService} APIs.
      *
      * @return list of tokens
-     * @hide
      */
-    // TODO(jaewan): Unhide
     public List<SessionToken2> getSessionServiceTokens() {
         try {
             List<Bundle> bundles = mService.getSessionTokens(
@@ -429,9 +424,7 @@
      * @return list of tokens
      * @see #getActiveSessionTokens
      * @see #getSessionServiceTokens
-     * @hide
      */
-    // TODO(jaewan): Unhide
     public List<SessionToken2> getAllSessionTokens() {
         try {
             List<Bundle> bundles = mService.getSessionTokens(
@@ -452,9 +445,7 @@
      *
      * @param executor executor to run this command
      * @param listener The listener to add.
-     * @hide
      */
-    // TODO(jaewan): Unhide
     public void addOnSessionTokensChangedListener(@NonNull @CallbackExecutor Executor executor,
             @NonNull OnSessionTokensChangedListener listener) {
         addOnSessionTokensChangedListener(UserHandle.myUserId(), executor, listener);
@@ -501,9 +492,7 @@
      * Stop receiving session token updates on the specified listener.
      *
      * @param listener The listener to remove.
-     * @hide
      */
-    // TODO(jaewan): Unhide
     public void removeOnSessionTokensChangedListener(
             @NonNull OnSessionTokensChangedListener listener) {
         if (listener == null) {
@@ -660,9 +649,7 @@
     /**
      * Listens for changes to the {@link #getAllSessionTokens()}. This can be added
      * using {@link #addOnActiveSessionsChangedListener}.
-     * @hide
      */
-    // TODO(jaewan): Unhide
     public interface OnSessionTokensChangedListener {
         void onSessionTokensChanged(@NonNull List<SessionToken2> tokens);
     }
diff --git a/media/java/android/media/update/MediaController2Provider.java b/media/java/android/media/update/MediaController2Provider.java
index 8d9efd5..ca5c16d 100644
--- a/media/java/android/media/update/MediaController2Provider.java
+++ b/media/java/android/media/update/MediaController2Provider.java
@@ -16,7 +16,6 @@
 
 package android.media.update;
 
-import android.annotation.SystemApi;
 import android.app.PendingIntent;
 import android.media.AudioAttributes;
 import android.media.MediaController2.PlaybackInfo;
@@ -65,6 +64,11 @@
     PlaylistParams getPlaylistParams_impl();
     void setPlaylistParams_impl(PlaylistParams params);
     PlaybackState2 getPlaybackState_impl();
+    int getPlayerState_impl();
+    long getPosition_impl();
+    float getPlaybackSpeed_impl();
+    long getBufferedPosition_impl();
+    MediaItem2 getCurrentPlaylistItem_impl();
 
     interface PlaybackInfoProvider {
         int getPlaybackType_impl();
diff --git a/media/java/android/media/update/MediaItem2Provider.java b/media/java/android/media/update/MediaItem2Provider.java
index 2970f0e..1d5b414 100644
--- a/media/java/android/media/update/MediaItem2Provider.java
+++ b/media/java/android/media/update/MediaItem2Provider.java
@@ -23,7 +23,6 @@
 /**
  * @hide
  */
-// TODO(jaewan): SystemApi
 public interface MediaItem2Provider {
     Bundle toBundle_impl();
     String toString_impl();
diff --git a/media/java/android/media/update/MediaLibraryService2Provider.java b/media/java/android/media/update/MediaLibraryService2Provider.java
index 7489f76..9a0d693 100644
--- a/media/java/android/media/update/MediaLibraryService2Provider.java
+++ b/media/java/android/media/update/MediaLibraryService2Provider.java
@@ -16,16 +16,12 @@
 
 package android.media.update;
 
-import android.annotation.SystemApi;
-import android.media.MediaLibraryService2.MediaLibrarySession;
-import android.media.MediaLibraryService2.MediaLibrarySessionCallback;
 import android.media.MediaSession2.ControllerInfo;
 import android.os.Bundle;
 
 /**
  * @hide
  */
-// TODO: @SystemApi
 public interface MediaLibraryService2Provider extends MediaSessionService2Provider {
     // Nothing new for now
 
diff --git a/media/java/android/media/update/MediaMetadata2Provider.java b/media/java/android/media/update/MediaMetadata2Provider.java
index b6e5c8a..22463e9 100644
--- a/media/java/android/media/update/MediaMetadata2Provider.java
+++ b/media/java/android/media/update/MediaMetadata2Provider.java
@@ -11,7 +11,6 @@
 /**
  * @hide
  */
-// TODO(jaewan): SystemApi
 public interface MediaMetadata2Provider {
     boolean containsKey_impl(String key);
     CharSequence getText_impl(String key);
diff --git a/media/java/android/media/update/MediaSession2Provider.java b/media/java/android/media/update/MediaSession2Provider.java
index fc1f671..dbd4a0a 100644
--- a/media/java/android/media/update/MediaSession2Provider.java
+++ b/media/java/android/media/update/MediaSession2Provider.java
@@ -19,8 +19,8 @@
 import android.app.PendingIntent;
 import android.media.MediaItem2;
 import android.media.MediaMetadata2;
-import android.media.MediaPlayerInterface;
-import android.media.MediaPlayerInterface.EventCallback;
+import android.media.MediaPlayerBase;
+import android.media.MediaPlayerBase.EventCallback;
 import android.media.MediaSession2;
 import android.media.MediaSession2.Command;
 import android.media.MediaSession2.CommandButton;
@@ -40,27 +40,28 @@
 /**
  * @hide
  */
-// TODO: @SystemApi
 public interface MediaSession2Provider extends TransportControlProvider {
     void close_impl();
-    void setPlayer_impl(MediaPlayerInterface player);
-    void setPlayer_impl(MediaPlayerInterface player, VolumeProvider2 volumeProvider);
-    MediaPlayerInterface getPlayer_impl();
+    void setPlayer_impl(MediaPlayerBase player);
+    void setPlayer_impl(MediaPlayerBase player, VolumeProvider2 volumeProvider);
+    MediaPlayerBase getPlayer_impl();
     SessionToken2 getToken_impl();
     List<ControllerInfo> getConnectedControllers_impl();
     void setCustomLayout_impl(ControllerInfo controller, List<CommandButton> layout);
     void setAudioFocusRequest_impl(int focusGain);
-
     void setAllowedCommands_impl(ControllerInfo controller, CommandGroup commands);
-    void notifyMetadataChanged_impl();
     void sendCustomCommand_impl(ControllerInfo controller, Command command, Bundle args,
             ResultReceiver receiver);
     void sendCustomCommand_impl(Command command, Bundle args);
     void setPlaylist_impl(List<MediaItem2> playlist);
+    void addPlaylistItem_impl(int index, MediaItem2 item);
+    void removePlaylistItem_impl(MediaItem2 item);
+    void editPlaylistItem_impl(MediaItem2 item);
     List<MediaItem2> getPlaylist_impl();
+    MediaItem2 getCurrentPlaylistItem_impl();
     void setPlaylistParams_impl(PlaylistParams params);
     PlaylistParams getPlaylistParams_impl();
-
+    void notifyError_impl(int errorCode, int extra);
     void registerPlayerEventCallback_impl(Executor executor, EventCallback callback);
     void unregisterPlayerEventCallback_impl(EventCallback callback);
 
diff --git a/media/java/android/media/update/MediaSessionService2Provider.java b/media/java/android/media/update/MediaSessionService2Provider.java
index 42e7587..8697e70 100644
--- a/media/java/android/media/update/MediaSessionService2Provider.java
+++ b/media/java/android/media/update/MediaSessionService2Provider.java
@@ -16,7 +16,6 @@
 
 package android.media.update;
 
-import android.annotation.SystemApi;
 import android.app.Notification;
 import android.content.Intent;
 import android.media.MediaSession2;
@@ -29,7 +28,7 @@
  */
 public interface MediaSessionService2Provider {
     MediaSession2 getSession_impl();
-    MediaNotification onUpdateNotification_impl(PlaybackState2 state);
+    MediaNotification onUpdateNotification_impl();
 
     // Service
     void onCreate_impl();
diff --git a/media/java/android/media/update/PlaybackState2Provider.java b/media/java/android/media/update/PlaybackState2Provider.java
index 93f769c..66b8fa5 100644
--- a/media/java/android/media/update/PlaybackState2Provider.java
+++ b/media/java/android/media/update/PlaybackState2Provider.java
@@ -21,7 +21,6 @@
 /**
  * @hide
  */
-// TODO(jaewan): @SystemApi
 public interface PlaybackState2Provider {
     String toString_impl();
 
diff --git a/media/java/android/media/update/Rating2Provider.java b/media/java/android/media/update/Rating2Provider.java
index 8966196..28ad273 100644
--- a/media/java/android/media/update/Rating2Provider.java
+++ b/media/java/android/media/update/Rating2Provider.java
@@ -22,7 +22,6 @@
 /**
  * @hide
  */
-// TODO(jaewan): @SystemApi
 public interface Rating2Provider {
     String toString_impl();
     boolean equals_impl(Object obj);
@@ -34,4 +33,4 @@
     boolean isThumbUp_impl();
     float getStarRating_impl();
     float getPercentRating_impl();
-}
\ No newline at end of file
+}
diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java
index 29a30343..62759eb 100644
--- a/media/java/android/media/update/StaticProvider.java
+++ b/media/java/android/media/update/StaticProvider.java
@@ -28,10 +28,9 @@
 import android.media.MediaLibraryService2;
 import android.media.MediaLibraryService2.LibraryRoot;
 import android.media.MediaLibraryService2.MediaLibrarySession;
-import android.media.MediaLibraryService2.MediaLibrarySessionBuilder;
-import android.media.MediaLibraryService2.MediaLibrarySessionCallback;
+import android.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback;
 import android.media.MediaMetadata2;
-import android.media.MediaPlayerInterface;
+import android.media.MediaPlayerBase;
 import android.media.MediaSession2;
 import android.media.MediaSession2.CommandButton.Builder;
 import android.media.MediaSession2.PlaylistParams;
@@ -44,7 +43,7 @@
 import android.media.VolumeProvider2;
 import android.media.update.MediaLibraryService2Provider.LibraryRootProvider;
 import android.media.update.MediaSession2Provider.BuilderBaseProvider;
-import android.media.update.MediaSession2Provider.CommandButtonProvider.BuilderProvider;
+import android.media.update.MediaSession2Provider.CommandButtonProvider;
 import android.media.update.MediaSession2Provider.CommandGroupProvider;
 import android.media.update.MediaSession2Provider.CommandProvider;
 import android.media.update.MediaSession2Provider.ControllerInfoProvider;
@@ -86,9 +85,10 @@
             PlaylistParams playlistParams, int repeatMode, int shuffleMode,
             MediaMetadata2 playlistMetadata);
     PlaylistParams fromBundle_PlaylistParams(Context context, Bundle bundle);
-    BuilderProvider createMediaSession2CommandButtonBuilder(Context context, Builder builder);
+    CommandButtonProvider.BuilderProvider createMediaSession2CommandButtonBuilder(Context context,
+            MediaSession2.CommandButton.Builder builder);
     BuilderBaseProvider<MediaSession2, SessionCallback> createMediaSession2Builder(
-            Context context, MediaSession2.Builder instance, MediaPlayerInterface player);
+            Context context, MediaSession2.Builder instance, MediaPlayerBase player);
 
     MediaController2Provider createMediaController2(Context context, MediaController2 instance,
             SessionToken2 token, Executor executor, ControllerCallback callback);
@@ -103,14 +103,15 @@
     MediaSessionService2Provider createMediaLibraryService2(MediaLibraryService2 instance);
     BuilderBaseProvider<MediaLibrarySession, MediaLibrarySessionCallback>
         createMediaLibraryService2Builder(
-            Context context, MediaLibrarySessionBuilder instance, MediaPlayerInterface player,
-            Executor callbackExecutor, MediaLibrarySessionCallback callback);
+            MediaLibraryService2 service, MediaLibrarySession.Builder instance,
+            MediaPlayerBase player, Executor callbackExecutor,
+            MediaLibrarySessionCallback callback);
     LibraryRootProvider createMediaLibraryService2LibraryRoot(Context context, LibraryRoot instance,
             String rootId, Bundle extras);
 
     SessionToken2Provider createSessionToken2(Context context, SessionToken2 instance,
             String packageName, String serviceName, int uid);
-    SessionToken2 SessionToken2_fromBundle(Context context, Bundle bundle);
+    SessionToken2 fromBundle_SessionToken2(Context context, Bundle bundle);
 
     MediaItem2Provider createMediaItem2(Context context, MediaItem2 mediaItem2,
             String mediaId, DataSourceDesc dsd, MediaMetadata2 metadata, int flags);
diff --git a/media/java/android/media/update/TransportControlProvider.java b/media/java/android/media/update/TransportControlProvider.java
index 44f82b29..9af8ada 100644
--- a/media/java/android/media/update/TransportControlProvider.java
+++ b/media/java/android/media/update/TransportControlProvider.java
@@ -16,6 +16,7 @@
 
 package android.media.update;
 
+import android.media.MediaItem2;
 import android.media.PlaybackState2;
 
 /**
@@ -32,7 +33,7 @@
     void fastForward_impl();
     void rewind_impl();
     void seekTo_impl(long pos);
-    void setCurrentPlaylistItem_impl(int index);
+    void skipToPlaylistItem_impl(MediaItem2 item);
 
     PlaybackState2 getPlaybackState_impl();
 }
diff --git a/media/java/android/media/update/VideoView2Provider.java b/media/java/android/media/update/VideoView2Provider.java
index 4333c96..152ace9 100644
--- a/media/java/android/media/update/VideoView2Provider.java
+++ b/media/java/android/media/update/VideoView2Provider.java
@@ -19,7 +19,7 @@
 import android.annotation.SystemApi;
 import android.media.AudioAttributes;
 import android.media.MediaMetadata2;
-import android.media.MediaPlayerInterface;
+import android.media.MediaPlayerBase;
 import android.media.session.MediaController;
 import android.media.session.PlaybackState;
 import android.media.session.MediaSession;
@@ -65,7 +65,7 @@
     /**
      * @hide
      */
-    void setRouteAttributes_impl(List<String> routeCategories, MediaPlayerInterface player);
+    void setRouteAttributes_impl(List<String> routeCategories, MediaPlayerBase player);
     // TODO: remove setRouteAttributes_impl with MediaSession.Callback once MediaSession2 is ready.
     void setRouteAttributes_impl(List<String> routeCategories, MediaSession.Callback sessionPlayer);
     void setVideoPath_impl(String path);
diff --git a/media/java/android/media/update/VolumeProvider2Provider.java b/media/java/android/media/update/VolumeProvider2Provider.java
index 5657af6..5b5cfd3 100644
--- a/media/java/android/media/update/VolumeProvider2Provider.java
+++ b/media/java/android/media/update/VolumeProvider2Provider.java
@@ -18,7 +18,6 @@
 /**
  * @hide
  */
-// TODO(jaewan): @SystemApi
 public interface VolumeProvider2Provider {
     int getControlType_impl();
     int getMaxVolume_impl();
diff --git a/packages/SystemUI/res/layout/quick_settings_header.xml b/packages/SystemUI/res/layout/quick_settings_header.xml
deleted file mode 100644
index 43197c4..0000000
--- a/packages/SystemUI/res/layout/quick_settings_header.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2018 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<com.android.systemui.qs.QSTooltipView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="@dimen/qs_header_tooltip_height"
-    android:alpha="0"
-    android:gravity="center_horizontal|bottom"
-    android:visibility="invisible">
-
-    <TextView
-        android:id="@+id/header_text"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="@string/quick_settings_header_onboarding_text"
-        android:textAppearance="@style/TextAppearance.QS.TileLabel"
-        android:textColor="?android:attr/colorAccent" />
-
-</com.android.systemui.qs.QSTooltipView>
diff --git a/packages/SystemUI/res/layout/quick_settings_header_info.xml b/packages/SystemUI/res/layout/quick_settings_header_info.xml
new file mode 100644
index 0000000..89d6e99
--- /dev/null
+++ b/packages/SystemUI/res/layout/quick_settings_header_info.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/header_text_container"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/qs_header_tooltip_height"
+    android:layout_below="@id/quick_status_bar_system_icons"
+    android:layout_marginTop="12dp">
+
+    <TextView
+        android:id="@+id/long_press_tooltip"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal|bottom"
+        android:alpha="0"
+        android:text="@string/quick_settings_header_onboarding_text"
+        android:textAppearance="@style/TextAppearance.QS.TileLabel"
+        android:textColor="?android:attr/colorAccent"
+        android:visibility="invisible" />
+
+    <LinearLayout
+        android:id="@+id/next_alarm"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal|bottom"
+        android:gravity="center_vertical"
+        android:visibility="invisible">
+
+        <ImageView
+            android:layout_width="@dimen/qs_header_alarm_icon_size"
+            android:layout_height="@dimen/qs_header_alarm_icon_size"
+            android:src="@drawable/stat_sys_alarm"
+            android:tint="?android:attr/textColorPrimary" />
+
+        <TextView
+            android:id="@+id/next_alarm_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/qs_header_alarm_text_margin_start"
+            android:textAppearance="@style/TextAppearance.QS.TileLabel" />
+
+    </LinearLayout>
+
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index cc79d0d..959247e 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -32,8 +32,13 @@
     android:elevation="4dp" >
 
     <include layout="@layout/quick_status_bar_header_system_icons" />
+
+    <!-- Status icons within the panel itself (and not in the top-most status bar) -->
     <include layout="@layout/quick_qs_status_icons" />
 
+    <!-- Layout containing tooltips, alarm text, etc. -->
+    <include layout="@layout/quick_settings_header_info" />
+
     <com.android.systemui.qs.QuickQSPanel
         android:id="@+id/quick_qs_panel"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index aefcb55..dc230d4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -321,7 +321,7 @@
     <dimen name="qs_tile_padding_bottom">16dp</dimen>
     <dimen name="qs_tile_spacing">4dp</dimen>
     <dimen name="qs_panel_padding_bottom">0dp</dimen>
-    <dimen name="qs_panel_padding_top">32dp</dimen>
+    <dimen name="qs_panel_padding_top">30dp</dimen>
     <dimen name="qs_detail_header_height">56dp</dimen>
     <dimen name="qs_detail_header_padding">0dp</dimen>
     <dimen name="qs_detail_image_width">56dp</dimen>
@@ -345,7 +345,9 @@
     <dimen name="qs_detail_item_icon_width">32dp</dimen>
     <dimen name="qs_detail_item_icon_marginStart">0dp</dimen>
     <dimen name="qs_detail_item_icon_marginEnd">20dp</dimen>
-    <dimen name="qs_header_tooltip_height">30dp</dimen>
+    <dimen name="qs_header_tooltip_height">18dp</dimen>
+    <dimen name="qs_header_alarm_icon_size">18dp</dimen>
+    <dimen name="qs_header_alarm_text_margin_start">6dp</dimen>
     <dimen name="qs_footer_padding_start">16dp</dimen>
     <dimen name="qs_footer_padding_end">24dp</dimen>
     <dimen name="qs_footer_icon_size">16dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index f3f8d91f..6098e4e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -736,7 +736,8 @@
 
     private DisplayClientState mDisplayClientState = new DisplayClientState();
 
-    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+    @VisibleForTesting
+    final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
 
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -773,6 +774,13 @@
                                 maxChargingMicroWatt));
                 mHandler.sendMessage(msg);
             } else if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(action)) {
+                // ACTION_SIM_STATE_CHANGED is rebroadcast after unlocking the device to
+                // keep compatibility with apps that aren't direct boot aware.
+                // SysUI should just ignore this broadcast because it was already received
+                // and processed previously.
+                if (intent.getBooleanExtra(TelephonyIntents.EXTRA_REBROADCAST_ON_UNLOCK, false)) {
+                    return;
+                }
                 SimData args = SimData.fromIntent(intent);
                 if (DEBUG_SIM_STATES) {
                     Log.v(TAG, "action " + action
@@ -1508,7 +1516,8 @@
     /**
      * Handle {@link #MSG_SIM_STATE_CHANGE}
      */
-    private void handleSimStateChange(int subId, int slotId, State state) {
+    @VisibleForTesting
+    protected void handleSimStateChange(int subId, int slotId, State state) {
 
         if (DEBUG_SIM_STATES) {
             Log.d(TAG, "handleSimStateChange(subId=" + subId + ", slotId="
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 175cddc..2a4bb60 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -66,10 +66,6 @@
     private TouchAnimator mNonfirstPageDelayedAnimator;
     private TouchAnimator mBrightnessAnimator;
 
-    /**
-     * Whether we're in the middle of animating between the collapsed and expanded states.
-     */
-    private boolean mIsAnimating;
     private boolean mOnKeyguard;
 
     private boolean mAllowFancy;
@@ -94,9 +90,6 @@
             Log.w(TAG, "QS Not using page layout");
         }
         panel.setPageListener(this);
-
-        // At time of creation, the QS panel is never animating.
-        mIsAnimating = false;
     }
 
     public void onRtlChanged() {
@@ -251,11 +244,6 @@
             } else {
                 mBrightnessAnimator = null;
             }
-            View headerView = mQsPanel.getHeaderView();
-            if (headerView!= null) {
-                firstPageBuilder.addFloat(headerView, "translationY", heightDiff, 0);
-                mAllViews.add(headerView);
-            }
             mFirstPageAnimator = firstPageBuilder
                     .setListener(this)
                     .build();
@@ -342,21 +330,11 @@
 
     @Override
     public void onAnimationAtStart() {
-        if (mIsAnimating) {
-            mQsPanel.onCollapse();
-        }
-        mIsAnimating = false;
-
         mQuickQsPanel.setVisibility(View.VISIBLE);
     }
 
     @Override
     public void onAnimationAtEnd() {
-        if (mIsAnimating) {
-            mQsPanel.onExpanded();
-        }
-        mIsAnimating = false;
-
         mQuickQsPanel.setVisibility(View.INVISIBLE);
         final int N = mQuickQsViews.size();
         for (int i = 0; i < N; i++) {
@@ -366,11 +344,6 @@
 
     @Override
     public void onAnimationStarted() {
-        if (!mIsAnimating) {
-            mQsPanel.onAnimating();
-        }
-        mIsAnimating = true;
-
         mQuickQsPanel.setVisibility(mOnKeyguard ? View.INVISIBLE : View.VISIBLE);
         if (mOnFirstPage) {
             final int N = mQuickQsViews.size();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index d437f49..5758762 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -272,21 +272,27 @@
         mContainer.setExpansion(expansion);
         final float translationScaleY = expansion - 1;
         if (!mHeaderAnimating) {
-            int height = mHeader.getHeight();
-            getView().setTranslationY(mKeyguardShowing ? (translationScaleY * height)
-                    : headerTranslation);
+            getView().setTranslationY(
+                    mKeyguardShowing
+                            ? translationScaleY * mHeader.getHeight()
+                            : headerTranslation);
         }
         if (expansion == mLastQSExpansion) {
             return;
         }
         mLastQSExpansion = expansion;
-        mHeader.setExpansion(mKeyguardShowing ? 1 : expansion);
-        mFooter.setExpansion(mKeyguardShowing ? 1 : expansion);
+
+        boolean fullyExpanded = expansion == 1;
         int heightDiff = mQSPanel.getBottom() - mHeader.getBottom() + mHeader.getPaddingBottom()
                 + mFooter.getHeight();
+        float panelTranslationY = translationScaleY * heightDiff;
+
+        // Let the views animate their contents correctly by giving them the necessary context.
+        mHeader.setExpansion(mKeyguardShowing, expansion, panelTranslationY);
+        mFooter.setExpansion(mKeyguardShowing ? 1 : expansion);
         mQSPanel.setTranslationY(translationScaleY * heightDiff);
-        boolean fullyExpanded = expansion == 1;
         mQSDetail.setFullyExpanded(fullyExpanded);
+
         if (fullyExpanded) {
             // Always draw within the bounds of the view when fully expanded.
             mQSPanel.setClipBounds(null);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index a92e346..143ad21 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -29,7 +29,6 @@
 import android.service.quicksettings.Tile;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
-import android.view.MotionEvent;
 import android.view.View;
 import android.widget.LinearLayout;
 
@@ -54,11 +53,11 @@
 import java.util.ArrayList;
 import java.util.Collection;
 
-/** View that represents the quick settings tile panel. **/
+/** View that represents the quick settings tile panel (when expanded/pulled down). **/
 public class QSPanel extends LinearLayout implements Tunable, Callback, BrightnessMirrorListener {
 
     public static final String QS_SHOW_BRIGHTNESS = "qs_show_brightness";
-    public static final String QS_SHOW_LONG_PRESS_TOOLTIP = "qs_show_long_press";
+    public static final String QS_SHOW_HEADER = "qs_show_header";
 
     protected final Context mContext;
     protected final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>();
@@ -74,7 +73,6 @@
     private BrightnessController mBrightnessController;
     protected QSTileHost mHost;
 
-    protected QSTooltipView mTooltipView;
     protected QSSecurityFooter mFooter;
     private boolean mGridContentVisible = true;
 
@@ -96,11 +94,6 @@
 
         setOrientation(VERTICAL);
 
-        mTooltipView = (QSTooltipView) LayoutInflater.from(mContext)
-                .inflate(R.layout.quick_settings_header, this, false);
-
-        addView(mTooltipView);
-
         mBrightnessView = LayoutInflater.from(mContext).inflate(
             R.layout.quick_settings_brightness_dialog, this, false);
         addView(mBrightnessView);
@@ -152,7 +145,6 @@
         super.onAttachedToWindow();
         final TunerService tunerService = Dependency.get(TunerService.class);
         tunerService.addTunable(this, QS_SHOW_BRIGHTNESS);
-        tunerService.addTunable(this, QS_SHOW_LONG_PRESS_TOOLTIP);
 
         if (mHost != null) {
             setTiles(mHost.getTiles());
@@ -186,8 +178,6 @@
     public void onTuningChanged(String key, String newValue) {
         if (QS_SHOW_BRIGHTNESS.equals(key)) {
             updateViewVisibilityForTuningValue(mBrightnessView, newValue);
-        } else if (QS_SHOW_LONG_PRESS_TOOLTIP.equals(key)) {
-            updateViewVisibilityForTuningValue(mTooltipView, newValue);
         }
     }
 
@@ -229,10 +219,6 @@
         return mBrightnessView;
     }
 
-    View getHeaderView() {
-        return mTooltipView;
-    }
-
     public void setCallback(QSDetail.Callback callback) {
         mCallback = callback;
     }
@@ -254,10 +240,7 @@
 
     public void updateResources() {
         final Resources res = mContext.getResources();
-        setPadding(0, 0, 0, res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom));
-        mTooltipView.getLayoutParams().height =
-                res.getDimensionPixelSize(R.dimen.qs_header_tooltip_height);
-        mTooltipView.setLayoutParams(mTooltipView.getLayoutParams());
+        setPadding(0, res.getDimensionPixelSize(R.dimen.qs_panel_padding_top), 0, res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom));
         for (TileRecord r : mRecords) {
             r.tile.clearState();
         }
@@ -291,21 +274,6 @@
         if (mCustomizePanel != null && mCustomizePanel.isShown()) {
             mCustomizePanel.hide(mCustomizePanel.getWidth() / 2, mCustomizePanel.getHeight() / 2);
         }
-
-        // Instantly hide the header here since we don't want it to still be animating.
-        mTooltipView.setVisibility(View.INVISIBLE);
-    }
-
-    /**
-     * Called when the panel is fully animated out/expanded. This is different from the state
-     * tracked by {@link #mExpanded}, which only checks if the panel is even partially pulled out.
-     */
-    public void onExpanded() {
-        mTooltipView.fadeIn();
-    }
-
-    public void onAnimating() {
-        mTooltipView.fadeOut();
     }
 
     public void setExpanded(boolean expanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTooltipView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTooltipView.java
deleted file mode 100644
index d1f9741..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTooltipView.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.qs;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.content.Context;
-import android.os.Handler;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-
-import com.android.systemui.Prefs;
-
-import java.util.concurrent.TimeUnit;
-
-
-/**
- * Tooltip/header view for the Quick Settings panel.
- */
-public class QSTooltipView extends LinearLayout {
-
-    private static final int FADE_ANIMATION_DURATION_MS = 300;
-    private static final long AUTO_FADE_OUT_DELAY_MS = TimeUnit.SECONDS.toMillis(6);
-    private static final int TOOLTIP_NOT_YET_SHOWN_COUNT = 0;
-    public static final int MAX_TOOLTIP_SHOWN_COUNT = 3;
-
-    private final Handler mHandler = new Handler();
-    private final Runnable mAutoFadeOutRunnable = () -> fadeOut();
-
-    private int mShownCount;
-
-    public QSTooltipView(Context context) {
-        this(context, null);
-    }
-
-    public QSTooltipView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mShownCount = getStoredShownCount();
-    }
-
-    /** Returns the latest stored tooltip shown count from SharedPreferences. */
-    private int getStoredShownCount() {
-        return Prefs.getInt(
-                mContext,
-                Prefs.Key.QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT,
-                TOOLTIP_NOT_YET_SHOWN_COUNT);
-    }
-
-    /**
-     * Fades in the header view if we can show the tooltip - short circuits any running animation.
-     */
-    public void fadeIn() {
-        if (mShownCount < MAX_TOOLTIP_SHOWN_COUNT) {
-            animate().cancel();
-            setVisibility(View.VISIBLE);
-            animate()
-                    .alpha(1f)
-                    .setDuration(FADE_ANIMATION_DURATION_MS)
-                    .setListener(new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            mHandler.postDelayed(mAutoFadeOutRunnable, AUTO_FADE_OUT_DELAY_MS);
-                        }
-                    })
-                    .start();
-
-            // Increment and drop the shown count in prefs for the next time we're deciding to
-            // fade in the tooltip. We first sanity check that the tooltip count hasn't changed yet
-            // in prefs (say, from a long press).
-            if (getStoredShownCount() <= mShownCount) {
-                Prefs.putInt(mContext, Prefs.Key.QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT, ++mShownCount);
-            }
-        }
-    }
-
-    /**
-     * Fades out the header view if it's partially visible - short circuits any running animation.
-     */
-    public void fadeOut() {
-        animate().cancel();
-        if (getVisibility() == View.VISIBLE && getAlpha() != 0f) {
-            mHandler.removeCallbacks(mAutoFadeOutRunnable);
-            animate()
-                    .alpha(0f)
-                    .setDuration(FADE_ANIMATION_DURATION_MS)
-                    .setListener(new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            perhapsMakeViewInvisible();
-                        }
-                    })
-                    .start();
-        } else {
-            perhapsMakeViewInvisible();
-        }
-    }
-
-    /**
-     * Only update visibility if the view is currently being shown. Otherwise, it's already been
-     * hidden by some other manner.
-     */
-    private void perhapsMakeViewInvisible() {
-        if (getVisibility() == View.VISIBLE) {
-            setVisibility(View.INVISIBLE);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index f0684e1..2270b60 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -20,7 +20,6 @@
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.LinearLayout;
 import android.widget.Space;
 
@@ -123,7 +122,7 @@
 
     @Override
     public void onTuningChanged(String key, String newValue) {
-        if (QS_SHOW_BRIGHTNESS.equals(key) || QS_SHOW_LONG_PRESS_TOOLTIP.equals(key)) {
+        if (QS_SHOW_BRIGHTNESS.equals(key)) {
             // No Brightness or Tooltip for you!
             super.onTuningChanged(key, "0");
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 4d7333b..78481d3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -15,24 +15,30 @@
 package com.android.systemui.qs;
 
 import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
-import static android.app.StatusBarManager.DISABLE_NONE;
+import static com.android.systemui.keyguard.KeyguardSliceProvider.formatNextAlarm;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.AlarmManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Rect;
+import android.os.Handler;
 import android.provider.AlarmClock;
 import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.RelativeLayout;
-import android.widget.TextClock;
+import android.widget.TextView;
 
 import com.android.settingslib.Utils;
 import com.android.systemui.BatteryMeterView;
 import com.android.systemui.Dependency;
+import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.R.id;
 import com.android.systemui.SysUiServiceProvider;
@@ -43,11 +49,23 @@
 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
 import com.android.systemui.statusbar.policy.DarkIconDispatcher;
 import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.policy.NextAlarmController;
 
-public class QuickStatusBarHeader extends RelativeLayout
-        implements CommandQueue.Callbacks, View.OnClickListener {
+/**
+ * View that contains the top-most bits of the screen (primarily the status bar with date, time, and
+ * battery) and also contains the {@link QuickQSPanel} along with some of the panel's inner
+ * contents.
+ */
+public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue.Callbacks,
+        View.OnClickListener, NextAlarmController.NextAlarmChangeCallback {
 
-    private ActivityStarter mActivityStarter;
+    /** Delay for auto fading out the long press tooltip after it's fully visible (in ms). */
+    private static final long AUTO_FADE_OUT_DELAY_MS = DateUtils.SECOND_IN_MILLIS * 6;
+    private static final int FADE_ANIMATION_DURATION_MS = 300;
+    private static final int TOOLTIP_NOT_YET_SHOWN_COUNT = 0;
+    public static final int MAX_TOOLTIP_SHOWN_COUNT = 3;
+
+    private final Handler mHandler = new Handler();
 
     private QSPanel mQsPanel;
 
@@ -58,20 +76,39 @@
     protected QuickQSPanel mHeaderQsPanel;
     protected QSTileHost mHost;
     private TintedIconManager mIconManager;
-    private TouchAnimator mAlphaAnimator;
+    private TouchAnimator mStatusIconsAlphaAnimator;
+    private TouchAnimator mHeaderTextContainerAlphaAnimator;
 
     private View mQuickQsStatusIcons;
-
     private View mDate;
+    private View mHeaderTextContainerView;
+    /** View corresponding to the next alarm info (including the icon). */
+    private View mNextAlarmView;
+    /** Tooltip for educating users that they can long press on icons to see more details. */
+    private View mLongPressTooltipView;
+    /** {@link TextView} containing the actual text indicating when the next alarm will go off. */
+    private TextView mNextAlarmTextView;
+
+    private NextAlarmController mAlarmController;
+    private String mNextAlarmText;
+    /** Counts how many times the long press tooltip has been shown to the user. */
+    private int mShownCount;
+
+    /**
+     * Runnable for automatically fading out the long press tooltip (as if it were animating away).
+     */
+    private final Runnable mAutoFadeOutTooltipRunnable = () -> hideLongPressTooltip(false);
 
     public QuickStatusBarHeader(Context context, AttributeSet attrs) {
         super(context, attrs);
+
+        mAlarmController = Dependency.get(NextAlarmController.class);
+        mShownCount = getStoredShownCount();
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        Resources res = getResources();
 
         mHeaderQsPanel = findViewById(R.id.quick_qs_panel);
         mDate = findViewById(R.id.date);
@@ -79,8 +116,11 @@
         mQuickQsStatusIcons = findViewById(R.id.quick_qs_status_icons);
         mIconManager = new TintedIconManager(findViewById(R.id.statusIcons));
 
-        // RenderThread is doing more harm than good when touching the header (to expand quick
-        // settings), so disable it for this view
+        // Views corresponding to the header info section (e.g. tooltip and next alarm).
+        mHeaderTextContainerView = findViewById(R.id.header_text_container);
+        mLongPressTooltipView = findViewById(R.id.long_press_tooltip);
+        mNextAlarmView = findViewById(R.id.next_alarm);
+        mNextAlarmTextView = findViewById(R.id.next_alarm_text);
 
         updateResources();
 
@@ -98,8 +138,6 @@
 
         BatteryMeterView battery = findViewById(R.id.battery);
         battery.setForceShowPercent(true);
-
-        mActivityStarter = Dependency.get(ActivityStarter.class);
     }
 
     private void applyDarkness(int id, Rect tintArea, float intensity, int color) {
@@ -129,21 +167,26 @@
     }
 
     private void updateResources() {
-        updateAlphaAnimator();
+        // Update height, especially due to landscape mode restricting space.
+        mHeaderTextContainerView.getLayoutParams().height =
+                mContext.getResources().getDimensionPixelSize(R.dimen.qs_header_tooltip_height);
+        mHeaderTextContainerView.setLayoutParams(mHeaderTextContainerView.getLayoutParams());
+
+        updateStatusIconAlphaAnimator();
+        updateHeaderTextContainerAlphaAnimator();
     }
 
-    private void updateAlphaAnimator() {
-        mAlphaAnimator = new TouchAnimator.Builder()
+    private void updateStatusIconAlphaAnimator() {
+        mStatusIconsAlphaAnimator = new TouchAnimator.Builder()
                 .addFloat(mQuickQsStatusIcons, "alpha", 1, 0)
                 .build();
     }
 
-    public int getCollapsedHeight() {
-        return getHeight();
-    }
-
-    public int getExpandedHeight() {
-        return getHeight();
+    private void updateHeaderTextContainerAlphaAnimator() {
+        mHeaderTextContainerAlphaAnimator = new TouchAnimator.Builder()
+                .addFloat(mHeaderTextContainerView, "alpha", 0, 1)
+                .setStartDelay(.5f)
+                .build();
     }
 
     public void setExpanded(boolean expanded) {
@@ -153,10 +196,47 @@
         updateEverything();
     }
 
-    public void setExpansion(float headerExpansionFraction) {
-        if (mAlphaAnimator != null ) {
-            mAlphaAnimator.setPosition(headerExpansionFraction);
+    /**
+     * Animates the inner contents based on the given expansion details.
+     *
+     * @param isKeyguardShowing whether or not we're showing the keyguard (a.k.a. lockscreen)
+     * @param expansionFraction how much the QS panel is expanded/pulled out (up to 1f)
+     * @param panelTranslationY how much the panel has physically moved down vertically (required
+     *                          for keyguard animations only)
+     */
+    public void setExpansion(boolean isKeyguardShowing, float expansionFraction,
+                             float panelTranslationY) {
+        final float keyguardExpansionFraction = isKeyguardShowing ? 1f : expansionFraction;
+        if (mStatusIconsAlphaAnimator != null) {
+            mStatusIconsAlphaAnimator.setPosition(keyguardExpansionFraction);
         }
+
+        if (isKeyguardShowing) {
+            // If the keyguard is showing, we want to offset the text so that it comes in at the
+            // same time as the panel as it slides down.
+            mHeaderTextContainerView.setTranslationY(panelTranslationY);
+        } else {
+            mHeaderTextContainerView.setTranslationY(0f);
+        }
+
+        if (mHeaderTextContainerAlphaAnimator != null) {
+            mHeaderTextContainerAlphaAnimator.setPosition(keyguardExpansionFraction);
+        }
+
+        // Check the original expansion fraction - we don't want to show the tooltip until the
+        // panel is pulled all the way out.
+        if (expansionFraction == 1f) {
+            // QS is fully expanded, bring in the tooltip.
+            showLongPressTooltip();
+        }
+    }
+
+    /** Returns the latest stored tooltip shown count from SharedPreferences. */
+    private int getStoredShownCount() {
+        return Prefs.getInt(
+                mContext,
+                Prefs.Key.QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT,
+                TOOLTIP_NOT_YET_SHOWN_COUNT);
     }
 
     @Override
@@ -191,6 +271,12 @@
         }
         mHeaderQsPanel.setListening(listening);
         mListening = listening;
+
+        if (listening) {
+            mAlarmController.addCallback(this);
+        } else {
+            mAlarmController.removeCallback(this);
+        }
     }
 
     @Override
@@ -201,6 +287,125 @@
         }
     }
 
+    @Override
+    public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
+        mNextAlarmText = nextAlarm != null ? formatNextAlarm(mContext, nextAlarm) : null;
+        if (mNextAlarmText != null) {
+            hideLongPressTooltip(true /* shouldFadeInAlarmText */);
+        } else {
+            hideAlarmText();
+        }
+        updateHeaderTextContainerAlphaAnimator();
+    }
+
+    /**
+     * Animates in the long press tooltip (as long as the next alarm text isn't currently occupying
+     * the space).
+     */
+    public void showLongPressTooltip() {
+        // If we have alarm text to show, don't bother fading in the tooltip.
+        if (!TextUtils.isEmpty(mNextAlarmText)) {
+            return;
+        }
+
+        if (mShownCount < MAX_TOOLTIP_SHOWN_COUNT) {
+            mLongPressTooltipView.animate().cancel();
+            mLongPressTooltipView.setVisibility(View.VISIBLE);
+            mLongPressTooltipView.animate()
+                    .alpha(1f)
+                    .setDuration(FADE_ANIMATION_DURATION_MS)
+                    .setListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            mHandler.postDelayed(
+                                    mAutoFadeOutTooltipRunnable, AUTO_FADE_OUT_DELAY_MS);
+                        }
+                    })
+                    .start();
+
+            // Increment and drop the shown count in prefs for the next time we're deciding to
+            // fade in the tooltip. We first sanity check that the tooltip count hasn't changed yet
+            // in prefs (say, from a long press).
+            if (getStoredShownCount() <= mShownCount) {
+                Prefs.putInt(mContext, Prefs.Key.QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT, ++mShownCount);
+            }
+        }
+    }
+
+    /**
+     * Fades out the long press tooltip if it's partially visible - short circuits any running
+     * animation. Additionally has the ability to fade in the alarm info text.
+     *
+     * @param shouldShowAlarmText whether we should fade in the next alarm text
+     */
+    private void hideLongPressTooltip(boolean shouldShowAlarmText) {
+        mLongPressTooltipView.animate().cancel();
+        if (mLongPressTooltipView.getVisibility() == View.VISIBLE
+                && mLongPressTooltipView.getAlpha() != 0f) {
+            mHandler.removeCallbacks(mAutoFadeOutTooltipRunnable);
+            mLongPressTooltipView.animate()
+                    .alpha(0f)
+                    .setDuration(FADE_ANIMATION_DURATION_MS)
+                    .setListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            mLongPressTooltipView.setVisibility(View.INVISIBLE);
+
+                            if (shouldShowAlarmText) {
+                                showAlarmText();
+                            }
+                        }
+                    })
+                    .start();
+        } else {
+            mLongPressTooltipView.setVisibility(View.INVISIBLE);
+
+            if (shouldShowAlarmText) {
+                showAlarmText();
+            }
+        }
+    }
+
+    /**
+     * Fades in the updated alarm text. Note that if there's already an alarm showing, this will
+     * immediately hide it and fade in the updated time.
+     */
+    private void showAlarmText() {
+        mNextAlarmView.setAlpha(0f);
+        mNextAlarmView.setVisibility(View.VISIBLE);
+        mNextAlarmTextView.setText(mNextAlarmText);
+
+        mNextAlarmView.animate()
+                .alpha(1f)
+                .setDuration(FADE_ANIMATION_DURATION_MS)
+                .start();
+    }
+
+    /**
+     * Fades out and hides the next alarm text. This also resets the text contents to null in
+     * preparation for the next alarm update.
+     */
+    private void hideAlarmText() {
+        if (mNextAlarmView.getVisibility() == View.VISIBLE) {
+            mNextAlarmView.animate()
+                    .alpha(0f)
+                    .setListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            // Reset the alpha regardless of how the animation ends for the next
+                            // time we show this view/want to animate it.
+                            mNextAlarmView.setVisibility(View.INVISIBLE);
+                            mNextAlarmView.setAlpha(1f);
+                            mNextAlarmTextView.setText(null);
+                        }
+                    })
+                    .start();
+        } else {
+            // Next alarm view is already hidden, only need to clear the text.
+            mNextAlarmTextView.setText(null);
+        }
+    }
+
     public void updateEverything() {
         post(() -> setClickable(false));
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index bf9746e..77c3bfa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -23,7 +23,6 @@
 import com.android.systemui.plugins.qs.QSTileView;
 import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.qs.tiles.AirplaneModeTile;
-import com.android.systemui.qs.tiles.AlarmTile;
 import com.android.systemui.qs.tiles.BatterySaverTile;
 import com.android.systemui.qs.tiles.BluetoothTile;
 import com.android.systemui.qs.tiles.CastTile;
@@ -70,7 +69,6 @@
         else if (tileSpec.equals("saver")) return new DataSaverTile(mHost);
         else if (tileSpec.equals("night")) return new NightDisplayTile(mHost);
         else if (tileSpec.equals("nfc")) return new NfcTile(mHost);
-        else if (tileSpec.equals("alarm")) return new AlarmTile(mHost);
         // Intent tiles.
         else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(mHost, tileSpec);
         else if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(mHost, tileSpec);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 016cbd6..04dbb88 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -50,7 +50,7 @@
 import com.android.systemui.plugins.qs.QSTile.State;
 import com.android.systemui.qs.PagedTileLayout.TilePage;
 import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.QSTooltipView;
+import com.android.systemui.qs.QuickStatusBarHeader;
 
 import java.util.ArrayList;
 
@@ -197,7 +197,7 @@
         Prefs.putInt(
                 mContext,
                 Prefs.Key.QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT,
-                QSTooltipView.MAX_TOOLTIP_SHOWN_COUNT);
+                QuickStatusBarHeader.MAX_TOOLTIP_SHOWN_COUNT);
     }
 
     public LogMaker populate(LogMaker logMaker) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.java
deleted file mode 100644
index ff3fe73..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.qs.tiles;
-
-import static android.service.quicksettings.Tile.STATE_ACTIVE;
-import static android.service.quicksettings.Tile.STATE_UNAVAILABLE;
-
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.QS_ALARM;
-import static com.android.systemui.keyguard.KeyguardSliceProvider.formatNextAlarm;
-
-import android.app.AlarmManager.AlarmClockInfo;
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.provider.AlarmClock;
-
-import com.android.systemui.Dependency;
-import com.android.systemui.R;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.qs.QSTileHost;
-import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.statusbar.policy.NextAlarmController;
-import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
-
-public class AlarmTile extends QSTileImpl implements NextAlarmChangeCallback {
-    private final NextAlarmController mController;
-    private String mNextAlarm;
-    private PendingIntent mIntent;
-
-    public AlarmTile(QSTileHost host) {
-        super(host);
-        mController = Dependency.get(NextAlarmController.class);
-    }
-
-    @Override
-    public State newTileState() {
-        return new BooleanState();
-    }
-
-    @Override
-    protected void handleClick() {
-        if (mIntent != null) {
-            Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(mIntent);
-        }
-    }
-
-    @Override
-    protected void handleUpdateState(State state, Object arg) {
-        state.state = mNextAlarm != null ? STATE_ACTIVE : STATE_UNAVAILABLE;
-        state.label = getTileLabel();
-        state.secondaryLabel = mNextAlarm;
-        state.icon = ResourceIcon.get(R.drawable.stat_sys_alarm);
-        ((BooleanState) state).value = mNextAlarm != null;
-    }
-
-    @Override
-    public void onNextAlarmChanged(AlarmClockInfo nextAlarm) {
-        if (nextAlarm != null) {
-            mNextAlarm = formatNextAlarm(mContext, nextAlarm);
-            mIntent = nextAlarm.getShowIntent();
-        } else {
-            mNextAlarm = null;
-            mIntent = null;
-        }
-        refreshState();
-    }
-
-    @Override
-    public int getMetricsCategory() {
-        return QS_ALARM;
-    }
-
-    @Override
-    public Intent getLongClickIntent() {
-        return new Intent(AlarmClock.ACTION_SET_ALARM);
-    }
-
-    @Override
-    protected void handleSetListening(boolean listening) {
-        if (listening) {
-            mController.addCallback(this);
-        } else {
-            mController.removeCallback(this);
-        }
-    }
-
-    @Override
-    public CharSequence getTileLabel() {
-        return mContext.getString(R.string.status_bar_alarm);
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 840f55c..62151cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -115,6 +115,8 @@
     private final static int BUTTON_FADE_IN_OUT_DURATION_MS = 100;
     private final static int ROTATE_BUTTON_LOOP_DURATION_MS = 2000;
 
+    private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3;
+
     /** Allow some time inbetween the long press for back and recents. */
     private static final int LOCK_TO_APP_GESTURE_TOLERENCE = 200;
 
@@ -156,6 +158,7 @@
 
     private final Runnable mRemoveRotationProposal = () -> setRotateSuggestionButtonState(false);
     private Animator mRotateHideAnimator;
+    private ViewRippler mViewRippler = new ViewRippler();
 
     private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() {
         @Override
@@ -464,12 +467,16 @@
                 animIcon.start();
             }
 
+            if (!isRotateSuggestionIntroduced()) mViewRippler.start(view);
+
             // Set visibility, may fail if a11y service is active.
             // If invisible, call will stop animation.
             mNavigationBarView.setRotateButtonVisibility(true);
 
         } else { // Hide
 
+            mViewRippler.stop(); // Prevent any pending ripples, force hide or not
+
             if (force) {
                 // If a hide animator is running stop it and make invisible
                 if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
@@ -519,6 +526,25 @@
         return 6000;
     }
 
+    private boolean isRotateSuggestionIntroduced() {
+        ContentResolver cr = getContext().getContentResolver();
+        return Settings.Secure.getInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0)
+                >= NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION;
+    }
+
+    private void incrementNumAcceptedRotationSuggestionsIfNeeded() {
+        // Get the number of accepted suggestions
+        ContentResolver cr = getContext().getContentResolver();
+        final int numSuggestions = Settings.Secure.getInt(cr,
+                Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0);
+
+        // Increment the number of accepted suggestions only if it would change intro mode
+        if (numSuggestions < NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION) {
+            Settings.Secure.putInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED,
+                    numSuggestions + 1);
+        }
+    }
+
     // Injected from StatusBar at creation.
     public void setCurrentSysuiVisibility(int systemUiVisibility) {
         mSystemUiVisibility = systemUiVisibility;
@@ -861,6 +887,7 @@
 
     private void onRotateSuggestionClick(View v) {
         mMetricsLogger.action(MetricsEvent.ACTION_ROTATION_SUGGESTION_ACCEPTED);
+        incrementNumAcceptedRotationSuggestionsIfNeeded();
         mRotationLockController.setRotationLockedAtAngle(true, mLastRotationSuggestion);
     }
 
@@ -989,6 +1016,35 @@
         }
     }
 
+    private class ViewRippler {
+        private static final int RIPPLE_OFFSET_MS = 50;
+        private static final int RIPPLE_INTERVAL_MS = 2000;
+        private View mRoot;
+
+        public void start(View root) {
+            stop(); // Stop any pending ripple animations
+
+            mRoot = root;
+
+            // Schedule pending ripples, offset the 1st to avoid problems with visibility change
+            mRoot.postOnAnimationDelayed(mRipple, RIPPLE_OFFSET_MS);
+            mRoot.postOnAnimationDelayed(mRipple, RIPPLE_INTERVAL_MS);
+            mRoot.postOnAnimationDelayed(mRipple, 2*RIPPLE_INTERVAL_MS);
+        }
+
+        public void stop() {
+            if (mRoot != null) mRoot.removeCallbacks(mRipple);
+        }
+
+        private final Runnable mRipple = new Runnable() {
+            @Override
+            public void run() { // Cause the ripple to fire via false presses
+                mRoot.setPressed(true);
+                mRoot.setPressed(false);
+            }
+        };
+    }
+
     public static View create(Context context, FragmentListener listener) {
         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 747a551..c326fee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -762,20 +762,31 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-            if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION) ||
-                    action.equals(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)) {
-                updateVolumeZen();
-            } else if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
-                updateSimState(intent);
-            } else if (action.equals(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED)) {
-                updateTTY(intent.getIntExtra(TelecomManager.EXTRA_CURRENT_TTY_MODE,
-                        TelecomManager.TTY_MODE_OFF));
-            } else if (action.equals(Intent.ACTION_MANAGED_PROFILE_AVAILABLE) ||
-                    action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE) ||
-                    action.equals(Intent.ACTION_MANAGED_PROFILE_REMOVED)) {
-                updateManagedProfile();
-            } else if (action.equals(AudioManager.ACTION_HEADSET_PLUG)) {
-                updateHeadsetPlug(intent);
+            switch (action) {
+                case AudioManager.RINGER_MODE_CHANGED_ACTION:
+                case AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION:
+                    updateVolumeZen();
+                    break;
+                case TelephonyIntents.ACTION_SIM_STATE_CHANGED:
+                    // Avoid rebroadcast because SysUI is direct boot aware.
+                    if (intent.getBooleanExtra(TelephonyIntents.EXTRA_REBROADCAST_ON_UNLOCK,
+                            false)) {
+                        break;
+                    }
+                    updateSimState(intent);
+                    break;
+                case TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED:
+                    updateTTY(intent.getIntExtra(TelecomManager.EXTRA_CURRENT_TTY_MODE,
+                            TelecomManager.TTY_MODE_OFF));
+                    break;
+                case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
+                case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
+                case Intent.ACTION_MANAGED_PROFILE_REMOVED:
+                    updateManagedProfile();
+                    break;
+                case AudioManager.ACTION_HEADSET_PLUG:
+                    updateHeadsetPlug(intent);
+                    break;
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 255e5e4..d8d388c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -167,6 +167,7 @@
     private Callback mCallback;
     private boolean mWallpaperSupportsAmbientMode;
     private boolean mScreenOn;
+    private float mNotificationDensity;
 
     // Scrim blanking callbacks
     private Choreographer.FrameCallback mPendingFrameCallback;
@@ -251,7 +252,7 @@
         mCurrentInFrontTint = state.getFrontTint();
         mCurrentBehindTint = state.getBehindTint();
         mCurrentInFrontAlpha = state.getFrontAlpha();
-        mCurrentBehindAlpha = state.getBehindAlpha();
+        mCurrentBehindAlpha = state.getBehindAlpha(mNotificationDensity);
         applyExpansionToAlpha();
 
         // Cancel blanking transitions that were pending before we requested a new state
@@ -396,12 +397,13 @@
             // Either darken of make the scrim transparent when you
             // pull down the shade
             float interpolatedFract = getInterpolatedFraction();
+            float alphaBehind = mState.getBehindAlpha(mNotificationDensity);
             if (mDarkenWhileDragging) {
-                mCurrentBehindAlpha = MathUtils.lerp(mScrimBehindAlphaUnlocking,
-                        mScrimBehindAlphaKeyguard, interpolatedFract);
+                mCurrentBehindAlpha = MathUtils.lerp(mScrimBehindAlphaUnlocking, alphaBehind,
+                        interpolatedFract);
                 mCurrentInFrontAlpha = (1f - interpolatedFract) * SCRIM_IN_FRONT_ALPHA_LOCKED;
             } else {
-                mCurrentBehindAlpha = MathUtils.lerp(0 /* start */, mScrimBehindAlphaKeyguard,
+                mCurrentBehindAlpha = MathUtils.lerp(0 /* start */, alphaBehind,
                         interpolatedFract);
                 mCurrentInFrontAlpha = 0;
             }
@@ -415,15 +417,14 @@
     public void setNotificationCount(int notificationCount) {
         final float maxNotificationDensity = 3;
         float notificationDensity = Math.min(notificationCount / maxNotificationDensity, 1f);
-        float newAlpha = MathUtils.map(0, 1,
-                GRADIENT_SCRIM_ALPHA, GRADIENT_SCRIM_ALPHA_BUSY,
-                notificationDensity);
-        if (mScrimBehindAlphaKeyguard != newAlpha) {
-            mScrimBehindAlphaKeyguard = newAlpha;
+        if (mNotificationDensity == notificationDensity) {
+            return;
+        }
+        mNotificationDensity = notificationDensity;
 
-            if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER) {
-                scheduleUpdate();
-            }
+        if (mState == ScrimState.KEYGUARD) {
+            applyExpansionToAlpha();
+            scheduleUpdate();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 381e4af..053c5a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -18,6 +18,7 @@
 
 import android.graphics.Color;
 import android.os.Trace;
+import android.util.MathUtils;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.statusbar.ScrimView;
@@ -55,6 +56,13 @@
             mCurrentBehindAlpha = mScrimBehindAlphaKeyguard;
             mCurrentInFrontAlpha = 0;
         }
+
+        @Override
+        public float getBehindAlpha(float busynessFactor) {
+            return MathUtils.map(0 /* start */, 1 /* stop */,
+                   ScrimController.GRADIENT_SCRIM_ALPHA, ScrimController.GRADIENT_SCRIM_ALPHA_BUSY,
+                   busynessFactor);
+        }
     },
 
     /**
@@ -183,7 +191,7 @@
         return mCurrentInFrontAlpha;
     }
 
-    public float getBehindAlpha() {
+    public float getBehindAlpha(float busyness) {
         return mCurrentBehindAlpha;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index baf0ebf..5363742 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -403,54 +403,62 @@
             Log.d(TAG, "onReceive: intent=" + intent);
         }
         final String action = intent.getAction();
-        if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION) ||
-                action.equals(ConnectivityManager.INET_CONDITION_ACTION)) {
-            updateConnectivity();
-        } else if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
-            refreshLocale();
-            updateAirplaneMode(false);
-        } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED)) {
-            // We are using different subs now, we might be able to make calls.
-            recalculateEmergency();
-        } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) {
-            // Notify every MobileSignalController so they can know whether they are the
-            // data sim or not.
-            for (int i = 0; i < mMobileSignalControllers.size(); i++) {
-                MobileSignalController controller = mMobileSignalControllers.valueAt(i);
-                controller.handleBroadcast(intent);
-            }
-        } else if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
-            // Might have different subscriptions now.
-            updateMobileControllers();
-        } else if (action.equals(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED)) {
-            mLastServiceState = ServiceState.newFromBundle(intent.getExtras());
-            if (mMobileSignalControllers.size() == 0) {
-                // If none of the subscriptions are active, we might need to recalculate
-                // emergency state.
+        switch (action) {
+            case ConnectivityManager.CONNECTIVITY_ACTION:
+            case ConnectivityManager.INET_CONDITION_ACTION:
+                updateConnectivity();
+                break;
+            case Intent.ACTION_AIRPLANE_MODE_CHANGED:
+                refreshLocale();
+                updateAirplaneMode(false);
+                break;
+            case TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED:
+                // We are using different subs now, we might be able to make calls.
                 recalculateEmergency();
-            }
-        } else if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
-            mConfig = Config.readConfig(mContext);
-            mReceiverHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    handleConfigurationChanged();
+                break;
+            case TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
+                // Notify every MobileSignalController so they can know whether they are the
+                // data sim or not.
+                for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+                    MobileSignalController controller = mMobileSignalControllers.valueAt(i);
+                    controller.handleBroadcast(intent);
                 }
-            });
-        } else {
-            int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
-                    SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-            if (SubscriptionManager.isValidSubscriptionId(subId)) {
-                if (mMobileSignalControllers.indexOfKey(subId) >= 0) {
-                    mMobileSignalControllers.get(subId).handleBroadcast(intent);
+                break;
+            case TelephonyIntents.ACTION_SIM_STATE_CHANGED:
+                // Avoid rebroadcast because SysUI is direct boot aware.
+                if (intent.getBooleanExtra(TelephonyIntents.EXTRA_REBROADCAST_ON_UNLOCK, false)) {
+                    break;
+                }
+                // Might have different subscriptions now.
+                updateMobileControllers();
+                break;
+            case TelephonyIntents.ACTION_SERVICE_STATE_CHANGED:
+                mLastServiceState = ServiceState.newFromBundle(intent.getExtras());
+                if (mMobileSignalControllers.size() == 0) {
+                    // If none of the subscriptions are active, we might need to recalculate
+                    // emergency state.
+                    recalculateEmergency();
+                }
+                break;
+            case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
+                mConfig = Config.readConfig(mContext);
+                mReceiverHandler.post(this::handleConfigurationChanged);
+                break;
+            default:
+                int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+                if (SubscriptionManager.isValidSubscriptionId(subId)) {
+                    if (mMobileSignalControllers.indexOfKey(subId) >= 0) {
+                        mMobileSignalControllers.get(subId).handleBroadcast(intent);
+                    } else {
+                        // Can't find this subscription...  We must be out of date.
+                        updateMobileControllers();
+                    }
                 } else {
-                    // Can't find this subscription...  We must be out of date.
-                    updateMobileControllers();
+                    // No sub id, must be for the wifi.
+                    mWifiSignalController.handleBroadcast(intent);
                 }
-            } else {
-                // No sub id, must be for the wifi.
-                mWifiSignalController.handleBroadcast(intent);
-            }
+                break;
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
new file mode 100644
index 0000000..21483aa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.keyguard;
+
+import android.content.Intent;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class KeyguardUpdateMonitorTest extends SysuiTestCase {
+
+    private TestableLooper mTestableLooper;
+
+    @Before
+    public void setup() {
+        mTestableLooper = TestableLooper.get(this);
+    }
+
+    @Test
+    public void testIgnoresSimStateCallback_rebroadcast() {
+        Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+
+        AtomicBoolean simStateChanged = new AtomicBoolean(false);
+        KeyguardUpdateMonitor keyguardUpdateMonitor = new KeyguardUpdateMonitor(getContext()) {
+            @Override
+            protected void handleSimStateChange(int subId, int slotId,
+                    IccCardConstants.State state) {
+                simStateChanged.set(true);
+                super.handleSimStateChange(subId, slotId, state);
+            }
+        };
+
+        keyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext(), intent);
+        mTestableLooper.processAllMessages();
+        Assert.assertTrue("onSimStateChanged not called", simStateChanged.get());
+
+        intent.putExtra(TelephonyIntents.EXTRA_REBROADCAST_ON_UNLOCK, true);
+        simStateChanged.set(false);
+        keyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext(), intent);
+        mTestableLooper.processAllMessages();
+        Assert.assertFalse("onSimStateChanged should have been skipped", simStateChanged.get());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 8347fb0..168d8d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -356,6 +356,52 @@
         Assert.assertTrue(mScrimController.wasAnimationJustCancelled());
     }
 
+    /**
+     * Number of visible notifications affects scrim opacity.
+     */
+    @Test
+    public void testNotificationDensity() {
+        mScrimController.transitionTo(ScrimState.KEYGUARD);
+        mScrimController.finishAnimationsImmediately();
+
+        mScrimController.setNotificationCount(0);
+        mScrimController.finishAnimationsImmediately();
+        Assert.assertEquals("lower density when no notifications",
+                ScrimController.GRADIENT_SCRIM_ALPHA,  mScrimBehind.getViewAlpha(), 0.01f);
+
+        mScrimController.setNotificationCount(3);
+        mScrimController.finishAnimationsImmediately();
+        Assert.assertEquals("stronger density when notifications are visible",
+                ScrimController.GRADIENT_SCRIM_ALPHA_BUSY,  mScrimBehind.getViewAlpha(), 0.01f);
+    }
+
+    /**
+     * Moving from/to states conserves old notification density.
+     */
+    @Test
+    public void testConservesNotificationDensity() {
+        testConservesNotificationDensity(0 /* count */, ScrimController.GRADIENT_SCRIM_ALPHA);
+        testConservesNotificationDensity(3 /* count */, ScrimController.GRADIENT_SCRIM_ALPHA_BUSY);
+    }
+
+    /**
+     * Conserves old notification density after leaving state and coming back.
+     *
+     * @param count How many notification.
+     * @param expectedAlpha Expected alpha.
+     */
+    private void testConservesNotificationDensity(int count, float expectedAlpha) {
+        mScrimController.setNotificationCount(count);
+        mScrimController.transitionTo(ScrimState.UNLOCKED);
+        mScrimController.finishAnimationsImmediately();
+
+        mScrimController.transitionTo(ScrimState.KEYGUARD);
+        mScrimController.finishAnimationsImmediately();
+
+        Assert.assertEquals("Doesn't respect notification busyness after transition",
+                expectedAlpha,  mScrimBehind.getViewAlpha(), 0.01f);
+    }
+
     private void assertScrimTint(ScrimView scrimView, boolean tinted) {
         final boolean viewIsTinted = scrimView.getTint() != Color.TRANSPARENT;
         final String name = scrimView == mScrimInFront ? "front" : "back";
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 50968a0..0c3a5d1 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -128,6 +128,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.IntSupplier;
@@ -2055,7 +2056,7 @@
         }
 
         ComponentName componentName = ComponentName.unflattenFromString(componentId);
-        if (componentName.equals(userState.mServiceAssignedToAccessibilityButton)) {
+        if (Objects.equals(componentName, userState.mServiceAssignedToAccessibilityButton)) {
             return false;
         }
         userState.mServiceAssignedToAccessibilityButton = componentName;
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 8591304..e315bc5 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -146,6 +146,12 @@
         Intent intent = registerReceiver(null, filter);
         int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
         int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
+        boolean present = intent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true);
+
+        if (!present) {
+            // No battery, treat as if 100%, no possibility of draining battery.
+            return 100;
+        }
 
         if (level < 0 || scale <= 0) {
             // Battery data unavailable. This should never happen, so assume the worst.
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index e40dc4f..7efc9876 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -785,6 +785,9 @@
 
     private int mCurrentUserId;
 
+    /* Whether accessibility is magnifying the screen */
+    private boolean mScreenMagnificationActive;
+
     // Maps global key codes to the components that will handle them.
     private GlobalKeyManager mGlobalKeyManager;
 
@@ -8164,7 +8167,11 @@
      */
     private int configureNavBarOpacity(int visibility, boolean dockedStackVisible,
             boolean freeformStackVisible, boolean isDockedDividerResizing) {
-        if (mNavBarOpacityMode == NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED) {
+        if (mScreenMagnificationActive) {
+            // When the screen is magnified, the nav bar should be opaque since its background
+            // can vary as the user pans and zooms
+            visibility = setNavBarOpaqueFlag(visibility);
+        } else if (mNavBarOpacityMode == NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED) {
             if (dockedStackVisible || freeformStackVisible || isDockedDividerResizing) {
                 visibility = setNavBarOpaqueFlag(visibility);
             }
@@ -8319,6 +8326,14 @@
     }
 
     @Override
+    public void onScreenMagnificationStateChanged(boolean active) {
+        synchronized (mWindowManagerFuncs.getWindowManagerLock()) {
+            mScreenMagnificationActive = active;
+            updateSystemUiVisibilityLw();
+        }
+    }
+
+    @Override
     public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         proto.write(LAST_SYSTEM_UI_FLAGS, mLastSystemUiFlags);
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index dde4bc8..bf0c3da 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -1700,6 +1700,13 @@
     boolean canDismissBootAnimation();
 
     /**
+     * Called when the magnification state changes.
+     *
+     * @param active Whether magnification is active (that is, we are zoomed in).
+     */
+    void onScreenMagnificationStateChanged(boolean active);
+
+    /**
      * Convert the user rotation mode to a human readable format.
      */
     static String userRotationModeToString(int mode) {
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 659253f..c31cdec 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -87,6 +87,8 @@
 
     private WindowsForAccessibilityObserver mWindowsForAccessibilityObserver;
 
+    private boolean mScreenMagnificationActive;
+
     public void setMagnificationCallbacksLocked(MagnificationCallbacks callbacks) {
         if (callbacks != null) {
             if (mDisplayMagnifier != null) {
@@ -136,6 +138,11 @@
         if (mWindowsForAccessibilityObserver != null) {
             mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
         }
+        boolean nowActive = !spec.isNop();
+        if (nowActive != mScreenMagnificationActive) {
+            mScreenMagnificationActive = nowActive;
+            mService.mPolicy.onScreenMagnificationStateChanged(nowActive);
+        }
     }
 
     public void getMagnificationRegionLocked(Region outMagnificationRegion) {
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 0b99eaa..206ee7a 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -644,4 +644,8 @@
     public boolean canDismissBootAnimation() {
         return true;
     }
+
+    @Override
+    public void onScreenMagnificationStateChanged(boolean active) {
+    }
 }
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 0874b86..8a3f138 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -395,6 +395,112 @@
     }
 
     /**
+     * Send a text based SMS with messaging options.
+     *
+     * @param destinationAddress the address to send the message to
+     * @param scAddress is the service center address or null to use
+     *  the current default SMSC
+     * @param text the body of the message to send
+     * @param sentIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is successfully sent, or failed.
+     *  The result code will be <code>Activity.RESULT_OK</code> for success,
+     *  or one of these errors:<br>
+     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
+     *  <code>RESULT_ERROR_NULL_PDU</code><br>
+     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+     *  the extra "errorCode" containing a radio technology specific value,
+     *  generally only useful for troubleshooting.<br>
+     *  The per-application based SMS control checks sentIntent. If sentIntent
+     *  is NULL the caller will be checked against all unknown applications,
+     *  which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is delivered to the recipient.  The
+     *  raw pdu of the status report is in the extended data ("pdu").
+     * @param priority Priority level of the message
+     *  Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+     *  ---------------------------------
+     *  PRIORITY      | Level of Priority
+     *  ---------------------------------
+     *      '00'      |     Normal
+     *      '01'      |     Interactive
+     *      '10'      |     Urgent
+     *      '11'      |     Emergency
+     *  ----------------------------------
+     *  Any Other values included Negative considered as Invalid Priority Indicator of the message.
+     * @param expectMore is a boolean to indicate the sending messages through same link or not.
+     * @param validityPeriod Validity Period of the message in mins.
+     *  Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+     *  Validity Period(Minimum) -> 5 mins
+     *  Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+     *  Any Other values included Negative considered as Invalid Validity Period of the message.
+     *
+     * @throws IllegalArgumentException if destinationAddress or text are empty
+     * {@hide}
+     */
+    public void sendTextMessage(
+            String destinationAddress, String scAddress, String text,
+            PendingIntent sentIntent, PendingIntent deliveryIntent,
+            int priority, boolean expectMore, int validityPeriod) {
+        sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
+                true /* persistMessage*/, priority, expectMore, validityPeriod);
+    }
+
+    private void sendTextMessageInternal(
+            String destinationAddress, String scAddress, String text,
+            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean persistMessage,
+            int priority, boolean expectMore, int validityPeriod) {
+        if (TextUtils.isEmpty(destinationAddress)) {
+            throw new IllegalArgumentException("Invalid destinationAddress");
+        }
+
+        if (TextUtils.isEmpty(text)) {
+            throw new IllegalArgumentException("Invalid message body");
+        }
+
+        if (priority < 0x00 || priority > 0x03) {
+            throw new IllegalArgumentException("Invalid priority");
+        }
+
+        if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) {
+            throw new IllegalArgumentException("Invalid validity period");
+        }
+
+        try {
+             ISms iccISms = getISmsServiceOrThrow();
+            if (iccISms != null) {
+                iccISms.sendTextForSubscriberWithOptions(getSubscriptionId(),
+                        ActivityThread.currentPackageName(), destinationAddress, scAddress, text,
+                        sentIntent, deliveryIntent, persistMessage,  priority, expectMore,
+                        validityPeriod);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+    }
+
+    /**
+     * Send a text based SMS without writing it into the SMS Provider.
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
+     * privileges.
+     * </p>
+     *
+     * @see #sendTextMessage(String, String, String, PendingIntent,
+     * PendingIntent, int, boolean, int)
+     * @hide
+     */
+    public void sendTextMessageWithoutPersisting(
+            String destinationAddress, String scAddress, String text,
+            PendingIntent sentIntent, PendingIntent deliveryIntent, int priority,
+            boolean expectMore, int validityPeriod) {
+        sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
+                false /* persistMessage */, priority, expectMore, validityPeriod);
+    }
+
+    /**
+     *
      * Inject an SMS PDU into the android application framework.
      *
      * <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier
@@ -552,6 +658,140 @@
     }
 
     /**
+     * Send a multi-part text based SMS with messaging options. The callee should have already
+     * divided the message into correctly sized parts by calling
+     * <code>divideMessage</code>.
+     *
+     * <p class="note"><strong>Note:</strong> Using this method requires that your app has the
+     * {@link android.Manifest.permission#SEND_SMS} permission.</p>
+     *
+     * <p class="note"><strong>Note:</strong> Beginning with Android 4.4 (API level 19), if
+     * <em>and only if</em> an app is not selected as the default SMS app, the system automatically
+     * writes messages sent using this method to the SMS Provider (the default SMS app is always
+     * responsible for writing its sent messages to the SMS Provider). For information about
+     * how to behave as the default SMS app, see {@link android.provider.Telephony}.</p>
+     *
+     * @param destinationAddress the address to send the message to
+     * @param scAddress is the service center address or null to use
+     *   the current default SMSC
+     * @param parts an <code>ArrayList</code> of strings that, in order,
+     *   comprise the original message
+     * @param sentIntents if not null, an <code>ArrayList</code> of
+     *   <code>PendingIntent</code>s (one for each message part) that is
+     *   broadcast when the corresponding message part has been sent.
+     *   The result code will be <code>Activity.RESULT_OK</code> for success,
+     *   or one of these errors:<br>
+     *   <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *   <code>RESULT_ERROR_RADIO_OFF</code><br>
+     *   <code>RESULT_ERROR_NULL_PDU</code><br>
+     *   For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include
+     *   the extra "errorCode" containing a radio technology specific value,
+     *   generally only useful for troubleshooting.<br>
+     *   The per-application based SMS control checks sentIntent. If sentIntent
+     *   is NULL the caller will be checked against all unknown applications,
+     *   which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntents if not null, an <code>ArrayList</code> of
+     *   <code>PendingIntent</code>s (one for each message part) that is
+     *   broadcast when the corresponding message part has been delivered
+     *   to the recipient.  The raw pdu of the status report is in the
+     *   extended data ("pdu").
+     * @param priority Priority level of the message
+     *  Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+     *  ---------------------------------
+     *  PRIORITY      | Level of Priority
+     *  ---------------------------------
+     *      '00'      |     Normal
+     *      '01'      |     Interactive
+     *      '10'      |     Urgent
+     *      '11'      |     Emergency
+     *  ----------------------------------
+     *  Any Other values included Negative considered as Invalid Priority Indicator of the message.
+     * @param expectMore is a boolean to indicate the sending messages through same link or not.
+     * @param validityPeriod Validity Period of the message in mins.
+     *  Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+     *  Validity Period(Minimum) -> 5 mins
+     *  Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+     *  Any Other values included Negative considered as Invalid Validity Period of the message.
+     *
+     * @throws IllegalArgumentException if destinationAddress or data are empty
+     * {@hide}
+     */
+    public void sendMultipartTextMessage(
+            String destinationAddress, String scAddress, ArrayList<String> parts,
+            ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents,
+            int priority, boolean expectMore, int validityPeriod) {
+        sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
+                deliveryIntents, true /* persistMessage*/);
+    }
+
+    private void sendMultipartTextMessageInternal(
+            String destinationAddress, String scAddress, List<String> parts,
+            List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
+            boolean persistMessage, int priority, boolean expectMore, int validityPeriod) {
+        if (TextUtils.isEmpty(destinationAddress)) {
+            throw new IllegalArgumentException("Invalid destinationAddress");
+        }
+        if (parts == null || parts.size() < 1) {
+            throw new IllegalArgumentException("Invalid message body");
+        }
+
+        if (priority < 0x00 || priority > 0x03) {
+            throw new IllegalArgumentException("Invalid priority");
+        }
+
+        if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) {
+            throw new IllegalArgumentException("Invalid validity period");
+        }
+
+        if (parts.size() > 1) {
+            try {
+                 ISms iccISms = getISmsServiceOrThrow();
+                if (iccISms != null) {
+                    iccISms.sendMultipartTextForSubscriberWithOptions(getSubscriptionId(),
+                            ActivityThread.currentPackageName(), destinationAddress, scAddress,
+                            parts, sentIntents, deliveryIntents, persistMessage, priority,
+                            expectMore, validityPeriod);
+                }
+            } catch (RemoteException ex) {
+                // ignore it
+            }
+        } else {
+            PendingIntent sentIntent = null;
+            PendingIntent deliveryIntent = null;
+            if (sentIntents != null && sentIntents.size() > 0) {
+                sentIntent = sentIntents.get(0);
+            }
+            if (deliveryIntents != null && deliveryIntents.size() > 0) {
+                deliveryIntent = deliveryIntents.get(0);
+            }
+            sendTextMessageInternal(destinationAddress, scAddress, parts.get(0),
+                    sentIntent, deliveryIntent, persistMessage, priority, expectMore,
+                    validityPeriod);
+        }
+    }
+
+    /**
+     * Send a multi-part text based SMS without writing it into the SMS Provider.
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
+     * privileges.
+     * </p>
+     *
+     * @see #sendMultipartTextMessage(String, String, ArrayList, ArrayList,
+     * ArrayList, int, boolean, int)
+     * @hide
+     **/
+    public void sendMultipartTextMessageWithoutPersisting(
+            String destinationAddress, String scAddress, List<String> parts,
+            List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
+            int priority, boolean expectMore, int validityPeriod) {
+        sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
+                deliveryIntents, false /* persistMessage*/, priority, expectMore,
+                validityPeriod);
+    }
+
+   /**
      * Send a data based SMS to a specific application port.
      *
      * <p class="note"><strong>Note:</strong> Using this method requires that your app has the
@@ -1014,7 +1254,7 @@
      *   <code>getAllMessagesFromIcc</code>
      * @return <code>ArrayList</code> of <code>SmsMessage</code> objects.
      */
-    private static ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) {
+    private ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) {
         ArrayList<SmsMessage> messages = new ArrayList<SmsMessage>();
         if (records != null) {
             int count = records.size();
@@ -1022,7 +1262,8 @@
                 SmsRawData data = records.get(i);
                 // List contains all records, including "free" records (null)
                 if (data != null) {
-                    SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes());
+                    SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes(),
+                            getSubscriptionId());
                     if (sms != null) {
                         messages.add(sms);
                     }
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index 577ea7d..9d03b59 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -277,6 +277,31 @@
     }
 
     /**
+     * Create an SmsMessage from an SMS EF record.
+     *
+     * @param index Index of SMS record. This should be index in ArrayList
+     *              returned by SmsManager.getAllMessagesFromSim + 1.
+     * @param data Record data.
+     * @param subId Subscription Id of the SMS
+     * @return An SmsMessage representing the record.
+     *
+     * @hide
+     */
+    public static SmsMessage createFromEfRecord(int index, byte[] data, int subId) {
+        SmsMessageBase wrappedMessage;
+
+        if (isCdmaVoice(subId)) {
+            wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord(
+                    index, data);
+        } else {
+            wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromEfRecord(
+                    index, data);
+        }
+
+        return wrappedMessage != null ? new SmsMessage(wrappedMessage) : null;
+    }
+
+    /**
      * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
      * length in bytes (not hex chars) less the SMSC header
      *
@@ -836,6 +861,7 @@
          int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(subId);
          return (PHONE_TYPE_CDMA == activePhone);
    }
+
     /**
      * Decide if the carrier supports long SMS.
      * {@hide}
diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java
index 41c1430..e8597b2 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -17,6 +17,7 @@
 package android.telephony.data;
 
 import android.annotation.SystemApi;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -230,8 +231,10 @@
 
     @Override
     public String toString() {
-        return "DataProfile=" + mProfileId + "/" + mApn + "/" + mProtocol + "/" + mAuthType
-                + "/" + mUserName + "/" + mPassword + "/" + mType + "/" + mMaxConnsTime
+        return "DataProfile=" + mProfileId + "/" + mProtocol + "/" + mAuthType
+                + "/" + (Build.IS_USER ? "***/***/***" :
+                         (mApn + "/" + mUserName + "/" + mPassword))
+                + "/" + mType + "/" + mMaxConnsTime
                 + "/" + mMaxConns + "/" + mWaitTime + "/" + mEnabled + "/"
                 + mSupportedApnTypesBitmap + "/" + mRoamingProtocol + "/" + mBearerBitmap + "/"
                 + mMtu + "/" + mMvnoType + "/" + mMvnoMatchData + "/" + mModemCognitive;
diff --git a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
index 0673a38..0664a7e 100644
--- a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
@@ -74,7 +74,9 @@
     /** @hide */
     @IntDef({
             DELIVER_STATUS_OK,
-            DELIVER_STATUS_ERROR
+            DELIVER_STATUS_ERROR_GENERIC,
+            DELIVER_STATUS_ERROR_NO_MEMORY,
+            DELIVER_STATUS_ERROR_REQUEST_NOT_SUPPORTED
         })
     @Retention(RetentionPolicy.SOURCE)
     public @interface DeliverStatusResult {}
@@ -86,7 +88,17 @@
     /**
      * Message was not delivered.
      */
-    public static final int DELIVER_STATUS_ERROR = 2;
+    public static final int DELIVER_STATUS_ERROR_GENERIC = 2;
+
+    /**
+     * Message was not delivered due to lack of memory.
+     */
+    public static final int DELIVER_STATUS_ERROR_NO_MEMORY = 3;
+
+    /**
+     * Message was not delivered as the request is not supported.
+     */
+    public static final int DELIVER_STATUS_ERROR_REQUEST_NOT_SUPPORTED = 4;
 
     /** @hide */
     @IntDef({
@@ -106,7 +118,6 @@
      */
     public static final int STATUS_REPORT_STATUS_ERROR = 2;
 
-
     // Lock for feature synchronization
     private final Object mLock = new Object();
     private IImsSmsListener mListener;
@@ -157,7 +168,9 @@
      * @param token token provided in {@link #onSmsReceived(int, String, byte[])}
      * @param result result of delivering the message. Valid values are:
      *  {@link #DELIVER_STATUS_OK},
-     *  {@link #DELIVER_STATUS_ERROR}
+     *  {@link #DELIVER_STATUS_ERROR_GENERIC},
+     *  {@link #DELIVER_STATUS_ERROR_NO_MEMORY},
+     *  {@link #DELIVER_STATUS_ERROR_REQUEST_NOT_SUPPORTED}
      * @param messageRef the message reference
      */
     public void acknowledgeSms(int token, @DeliverStatusResult int messageRef, int result) {
@@ -200,7 +213,7 @@
                 mListener.onSmsReceived(token, format, pdu);
             } catch (RemoteException e) {
                 Log.e(LOG_TAG, "Can not deliver sms: " + e.getMessage());
-                acknowledgeSms(token, 0, DELIVER_STATUS_ERROR);
+                acknowledgeSms(token, 0, DELIVER_STATUS_ERROR_GENERIC);
             }
         }
     }
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index fe37531..a4eb424 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -187,6 +187,57 @@
             in PendingIntent deliveryIntent, in boolean persistMessage);
 
     /**
+     * Send an SMS with options using Subscription Id.
+     *
+     * @param subId the subId on which the SMS has to be sent.
+     * @param destAddr the address to send the message to
+     * @param scAddr the SMSC to send the message through, or NULL for the
+     *  default SMSC
+     * @param text the body of the message to send
+     * @param sentIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is sucessfully sent, or failed.
+     *  The result code will be <code>Activity.RESULT_OK<code> for success,
+     *  or one of these errors:<br>
+     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
+     *  <code>RESULT_ERROR_NULL_PDU</code><br>
+     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+     *  the extra "errorCode" containing a radio technology specific value,
+     *  generally only useful for troubleshooting.<br>
+     *  The per-application based SMS control checks sentIntent. If sentIntent
+     *  is NULL the caller will be checked against all unknown applications,
+     *  which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is delivered to the recipient.  The
+     *  raw pdu of the status report is in the extended data ("pdu").
+     * @param persistMessageForNonDefaultSmsApp whether the sent message should
+     *   be automatically persisted in the SMS db. It only affects messages sent
+     *   by a non-default SMS app. Currently only the carrier app can set this
+     *   parameter to false to skip auto message persistence.
+     * @param priority Priority level of the message
+     *  Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+     *  ---------------------------------
+     *  PRIORITY      | Level of Priority
+     *  ---------------------------------
+     *      '00'      |     Normal
+     *      '01'      |     Interactive
+     *      '10'      |     Urgent
+     *      '11'      |     Emergency
+     *  ----------------------------------
+     *  Any Other values included Negative considered as Invalid Priority Indicator of the message.
+     * @param expectMore is a boolean to indicate the sending message is multi segmented or not.
+     * @param validityPeriod Validity Period of the message in mins.
+     *  Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+     *  Validity Period(Minimum) -> 5 mins
+     *  Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+     *  Any Other values included Negative considered as Invalid Validity Period of the message.
+     */
+    void sendTextForSubscriberWithOptions(in int subId, String callingPkg, in String destAddr,
+            in String scAddr, in String text, in PendingIntent sentIntent,
+            in PendingIntent deliveryIntent, in boolean persistMessageForNonDefaultSmsApp,
+            in int priority, in boolean expectMore, in int validityPeriod);
+
+    /**
      * Inject an SMS PDU into the android platform.
      *
      * @param subId the subId on which the SMS has to be injected.
@@ -234,6 +285,56 @@
             in List<PendingIntent> deliveryIntents, in boolean persistMessageForNonDefaultSmsApp);
 
     /**
+     * Send a multi-part text based SMS with options using Subscription Id.
+     *
+     * @param subId the subId on which the SMS has to be sent.
+     * @param destinationAddress the address to send the message to
+     * @param scAddress is the service center address or null to use
+     *   the current default SMSC
+     * @param parts an <code>ArrayList</code> of strings that, in order,
+     *   comprise the original message
+     * @param sentIntents if not null, an <code>ArrayList</code> of
+     *   <code>PendingIntent</code>s (one for each message part) that is
+     *   broadcast when the corresponding message part has been sent.
+     *   The result code will be <code>Activity.RESULT_OK<code> for success,
+     *   or one of these errors:
+     *   <code>RESULT_ERROR_GENERIC_FAILURE</code>
+     *   <code>RESULT_ERROR_RADIO_OFF</code>
+     *   <code>RESULT_ERROR_NULL_PDU</code>.
+     * @param deliveryIntents if not null, an <code>ArrayList</code> of
+     *   <code>PendingIntent</code>s (one for each message part) that is
+     *   broadcast when the corresponding message part has been delivered
+     *   to the recipient.  The raw pdu of the status report is in the
+     *   extended data ("pdu").
+     * @param persistMessageForNonDefaultSmsApp whether the sent message should
+     *   be automatically persisted in the SMS db. It only affects messages sent
+     *   by a non-default SMS app. Currently only the carrier app can set this
+     *   parameter to false to skip auto message persistence.
+     * @param priority Priority level of the message
+     *  Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+     *  ---------------------------------
+     *  PRIORITY      | Level of Priority
+     *  ---------------------------------
+     *      '00'      |     Normal
+     *      '01'      |     Interactive
+     *      '10'      |     Urgent
+     *      '11'      |     Emergency
+     *  ----------------------------------
+     *  Any Other values included Negative considered as Invalid Priority Indicator of the message.
+     * @param expectMore is a boolean to indicate the sending message is multi segmented or not.
+     * @param validityPeriod Validity Period of the message in mins.
+     *  Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+     *  Validity Period(Minimum) -> 5 mins
+     *  Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+     *  Any Other values included Negative considered as Invalid Validity Period of the message.
+     */
+    void sendMultipartTextForSubscriberWithOptions(in int subId, String callingPkg,
+            in String destinationAddress, in String scAddress, in List<String> parts,
+            in List<PendingIntent> sentIntents, in List<PendingIntent> deliveryIntents,
+            in boolean persistMessageForNonDefaultSmsApp, in int priority, in boolean expectMore,
+            in int validityPeriod);
+
+    /**
      * Enable reception of cell broadcast (SMS-CB) messages with the given
      * message identifier and RAN type. The RAN type specify this message ID
      * belong to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different clients
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
index 7a53ef6..14c5f4b 100644
--- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -99,6 +99,15 @@
     private static final int RETURN_NO_ACK  = 0;
     private static final int RETURN_ACK     = 1;
 
+    /**
+     * Supported priority modes for CDMA SMS messages
+     * (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1)
+     */
+    private static final int PRIORITY_NORMAL        = 0x0;
+    private static final int PRIORITY_INTERACTIVE   = 0x1;
+    private static final int PRIORITY_URGENT        = 0x2;
+    private static final int PRIORITY_EMERGENCY     = 0x3;
+
     private SmsEnvelope mEnvelope;
     private BearerData mBearerData;
 
@@ -211,6 +220,26 @@
      */
     public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message,
             boolean statusReportRequested, SmsHeader smsHeader) {
+        return getSubmitPdu(scAddr, destAddr, message, statusReportRequested, smsHeader, -1);
+    }
+
+    /**
+     * Get an SMS-SUBMIT PDU for a destination address and a message
+     *
+     * @param scAddr                Service Centre address.  Null means use default.
+     * @param destAddr              Address of the recipient.
+     * @param message               String representation of the message payload.
+     * @param statusReportRequested Indicates whether a report is requested for this message.
+     * @param smsHeader             Array containing the data for the User Data Header, preceded
+     *                              by the Element Identifiers.
+     * @param priority              Priority level of the message
+     * @return a <code>SubmitPdu</code> containing the encoded SC
+     *         address, if applicable, and the encoded message.
+     *         Returns null on encode error.
+     * @hide
+     */
+    public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message,
+            boolean statusReportRequested, SmsHeader smsHeader, int priority) {
 
         /**
          * TODO(cleanup): Do we really want silent failure like this?
@@ -224,7 +253,7 @@
         UserData uData = new UserData();
         uData.payloadStr = message;
         uData.userDataHeader = smsHeader;
-        return privateGetSubmitPdu(destAddr, statusReportRequested, uData);
+        return privateGetSubmitPdu(destAddr, statusReportRequested, uData, priority);
     }
 
     /**
@@ -282,6 +311,22 @@
     }
 
     /**
+     * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
+     *
+     * @param destAddr the address of the destination for the message
+     * @param userData the data for the message
+     * @param statusReportRequested Indicates whether a report is requested for this message.
+     * @param priority Priority level of the message
+     * @return a <code>SubmitPdu</code> containing the encoded SC
+     *         address, if applicable, and the encoded message.
+     *         Returns null on encode error.
+     */
+    public static SubmitPdu getSubmitPdu(String destAddr, UserData userData,
+            boolean statusReportRequested, int priority) {
+        return privateGetSubmitPdu(destAddr, statusReportRequested, userData, priority);
+    }
+
+    /**
      * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
      */
     @Override
@@ -764,6 +809,15 @@
      */
     private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested,
             UserData userData) {
+        return privateGetSubmitPdu(destAddrStr, statusReportRequested, userData, -1);
+    }
+
+    /**
+     * Creates BearerData and Envelope from parameters for a Submit SMS.
+     * @return byte stream for SubmitPdu.
+     */
+    private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested,
+            UserData userData, int priority) {
 
         /**
          * TODO(cleanup): give this function a more meaningful name.
@@ -792,6 +846,10 @@
         bearerData.userAckReq = false;
         bearerData.readAckReq = false;
         bearerData.reportReq = false;
+        if (priority >= PRIORITY_NORMAL && priority <= PRIORITY_EMERGENCY) {
+            bearerData.priorityIndicatorSet = true;
+            bearerData.priority = priority;
+        }
 
         bearerData.userData = userData;
 
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index 1ca19e0..4f5bfa9 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -89,6 +89,18 @@
 
     private int mVoiceMailCount = 0;
 
+    private static final int VALIDITY_PERIOD_FORMAT_NONE = 0x00;
+    private static final int VALIDITY_PERIOD_FORMAT_ENHANCED = 0x01;
+    private static final int VALIDITY_PERIOD_FORMAT_RELATIVE = 0x02;
+    private static final int VALIDITY_PERIOD_FORMAT_ABSOLUTE = 0x03;
+
+    //Validity Period min - 5 mins
+    private static final int VALIDITY_PERIOD_MIN = 5;
+    //Validity Period max - 63 weeks
+    private static final int VALIDITY_PERIOD_MAX = 635040;
+
+    private static final int INVALID_VALIDITY_PERIOD = -1;
+
     public static class SubmitPdu extends SubmitPduBase {
     }
 
@@ -202,6 +214,45 @@
     }
 
     /**
+     * Get Encoded Relative Validty Period Value from Validity period in mins.
+     *
+     * @param validityPeriod Validity period in mins.
+     *
+     * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+     * ||relValidityPeriod (TP-VP)  ||                 ||  validityPeriod   ||
+     *
+     *      0 to 143                            --->       (TP-VP + 1) x 5 minutes
+     *
+     *      144 to 167                         --->        12 hours + ((TP-VP -143) x 30 minutes)
+     *
+     *      168 to 196                         --->        (TP-VP - 166) x 1 day
+     *
+     *      197 to 255                         --->        (TP-VP - 192) x 1 week
+     *
+     * @return relValidityPeriod Encoded Relative Validity Period Value.
+     * @hide
+     */
+    public static int getRelativeValidityPeriod(int validityPeriod) {
+        int relValidityPeriod = INVALID_VALIDITY_PERIOD;
+
+        if (validityPeriod < VALIDITY_PERIOD_MIN  || validityPeriod > VALIDITY_PERIOD_MAX) {
+            Rlog.e(LOG_TAG,"Invalid Validity Period" + validityPeriod);
+            return relValidityPeriod;
+        }
+
+        if (validityPeriod <= 720) {
+            relValidityPeriod = (validityPeriod  / 5) - 1;
+        } else if (validityPeriod <= 1440) {
+            relValidityPeriod = ((validityPeriod - 720) / 30) + 143;
+        } else if (validityPeriod <= 43200) {
+            relValidityPeriod = (validityPeriod  / 1440) + 166;
+        } else if (validityPeriod <= 635040) {
+            relValidityPeriod = (validityPeriod  / 10080) + 192;
+        }
+        return relValidityPeriod;
+    }
+
+    /**
      * Get an SMS-SUBMIT PDU for a destination address and a message
      *
      * @param scAddress Service Centre address.  Null means use default.
@@ -236,6 +287,29 @@
             String destinationAddress, String message,
             boolean statusReportRequested, byte[] header, int encoding,
             int languageTable, int languageShiftTable) {
+        return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested,
+            header, encoding, languageTable, languageShiftTable, -1);
+    }
+
+    /**
+     * Get an SMS-SUBMIT PDU for a destination address and a message using the
+     * specified encoding.
+     *
+     * @param scAddress Service Centre address.  Null means use default.
+     * @param encoding Encoding defined by constants in
+     *        com.android.internal.telephony.SmsConstants.ENCODING_*
+     * @param languageTable
+     * @param languageShiftTable
+     * @param validityPeriod Validity Period of the message in Minutes.
+     * @return a <code>SubmitPdu</code> containing the encoded SC
+     *         address, if applicable, and the encoded message.
+     *         Returns null on encode error.
+     * @hide
+     */
+    public static SubmitPdu getSubmitPdu(String scAddress,
+            String destinationAddress, String message,
+            boolean statusReportRequested, byte[] header, int encoding,
+            int languageTable, int languageShiftTable, int validityPeriod) {
 
         // Perform null parameter checks.
         if (message == null || destinationAddress == null) {
@@ -272,8 +346,19 @@
         }
 
         SubmitPdu ret = new SubmitPdu();
-        // MTI = SMS-SUBMIT, UDHI = header != null
-        byte mtiByte = (byte)(0x01 | (header != null ? 0x40 : 0x00));
+
+        int validityPeriodFormat = VALIDITY_PERIOD_FORMAT_NONE;
+        int relativeValidityPeriod = INVALID_VALIDITY_PERIOD;
+
+        // TP-Validity-Period-Format (TP-VPF) in 3GPP TS 23.040 V6.8.1 section 9.2.3.3
+        //bit 4:3 = 10 - TP-VP field present - relative format
+        if((relativeValidityPeriod = getRelativeValidityPeriod(validityPeriod)) >= 0) {
+            validityPeriodFormat = VALIDITY_PERIOD_FORMAT_RELATIVE;
+        }
+
+        byte mtiByte = (byte)(0x01 | (validityPeriodFormat << 0x03) |
+                (header != null ? 0x40 : 0x00));
+
         ByteArrayOutputStream bo = getSubmitPduHead(
                 scAddress, destinationAddress, mtiByte,
                 statusReportRequested, ret);
@@ -338,7 +423,11 @@
             bo.write(0x08);
         }
 
-        // (no TP-Validity-Period)
+        if (validityPeriodFormat == VALIDITY_PERIOD_FORMAT_RELATIVE) {
+            // ( TP-Validity-Period - relative format)
+            bo.write(relativeValidityPeriod);
+        }
+
         bo.write(userData, 0, userData.length);
         ret.encodedMessage = bo.toByteArray();
         return ret;
@@ -388,6 +477,24 @@
     }
 
     /**
+     * Get an SMS-SUBMIT PDU for a destination address and a message
+     *
+     * @param scAddress Service Centre address.  Null means use default.
+     * @param destinationAddress the address of the destination for the message
+     * @param statusReportRequested staus report of the message Requested
+     * @param validityPeriod Validity Period of the message in Minutes.
+     * @return a <code>SubmitPdu</code> containing the encoded SC
+     *         address, if applicable, and the encoded message.
+     *         Returns null on encode error.
+     */
+    public static SubmitPdu getSubmitPdu(String scAddress,
+            String destinationAddress, String message,
+            boolean statusReportRequested, int validityPeriod) {
+        return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested,
+                null, ENCODING_UNKNOWN, 0, 0, validityPeriod);
+    }
+
+    /**
      * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
      *
      * @param scAddress Service Centre address. null == use default
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
index b573adf..c5ad7de 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
@@ -263,6 +263,7 @@
      *
      * @throws Exception
      */
+    @Test
     public void validateCertCredentialWithoutCaCert() throws Exception {
         Credential cred = createCredentialWithCertificateCredential();
         cred.setCaCertificate(null);