Merge changes Ifa5bb06f,Ie9793424

* changes:
  [TIAF] Add more time shift APIs
  [TIAF] Add recording callback and cueing message APIs
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl
index c4f60c3..c52cd59 100644
--- a/media/java/android/media/tv/ITvInputClient.aidl
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -52,6 +52,9 @@
     void onTimeShiftCurrentPositionChanged(long timeMs, int seq);
     void onAitInfoUpdated(in AitInfo aitInfo, int seq);
     void onSignalStrength(int stength, int seq);
+    void onCueingMessageAvailability(boolean available, int seq);
+    void onTimeShiftMode(int mode, int seq);
+    void onAvailableSpeeds(in float[] speeds, int seq);
     void onTvMessage(in String type, in Bundle data, int seq);
 
     void onTuned(in Uri channelUri, int seq);
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index 746cfa2..e9aa321 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -99,6 +99,7 @@
     void timeShiftResume(in IBinder sessionToken, int userId);
     void timeShiftSeekTo(in IBinder sessionToken, long timeMs, int userId);
     void timeShiftSetPlaybackParams(in IBinder sessionToken, in PlaybackParams params, int userId);
+    void timeShiftSetMode(in IBinder sessionToken, int mode, int userId);
     void timeShiftEnablePositionTracking(in IBinder sessionToken, boolean enable, int userId);
 
     List<TunedInfo> getCurrentTunedInfos(int userId);
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index e6c09a9..82875e5 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -60,6 +60,7 @@
     void timeShiftResume();
     void timeShiftSeekTo(long timeMs);
     void timeShiftSetPlaybackParams(in PlaybackParams params);
+    void timeShiftSetMode(int mode);
     void timeShiftEnablePositionTracking(boolean enable);
 
     // For the recording session
diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl
index 0111f09..449c2d6 100644
--- a/media/java/android/media/tv/ITvInputSessionCallback.aidl
+++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl
@@ -49,6 +49,9 @@
     void onTimeShiftCurrentPositionChanged(long timeMs);
     void onAitInfoUpdated(in AitInfo aitInfo);
     void onSignalStrength(int strength);
+    void onCueingMessageAvailability(boolean available);
+    void onTimeShiftMode(int mode);
+    void onAvailableSpeeds(in float[] speeds);
 
     // For the recording session
     void onTuned(in Uri channelUri);
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 847762f..46573f2 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -30,6 +30,7 @@
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
 import android.view.Surface;
+
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.SomeArgs;
 
@@ -75,6 +76,7 @@
     private static final int DO_REQUEST_AD = 27;
     private static final int DO_NOTIFY_AD_BUFFER = 28;
     private static final int DO_SELECT_AUDIO_PRESENTATION = 29;
+    private static final int DO_TIME_SHIFT_SET_MODE = 30;
 
     private final boolean mIsRecordingSession;
     private final HandlerCaller mCaller;
@@ -218,6 +220,9 @@
                 mTvInputSessionImpl.timeShiftSetPlaybackParams((PlaybackParams) msg.obj);
                 break;
             }
+            case DO_TIME_SHIFT_SET_MODE: {
+                mTvInputSessionImpl.timeShiftSetMode((Integer) msg.obj);
+            }
             case DO_TIME_SHIFT_ENABLE_POSITION_TRACKING: {
                 mTvInputSessionImpl.timeShiftEnablePositionTracking((Boolean) msg.obj);
                 break;
@@ -401,6 +406,11 @@
     }
 
     @Override
+    public void timeShiftSetMode(int mode) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageI(DO_TIME_SHIFT_SET_MODE, mode));
+    }
+
+    @Override
     public void timeShiftEnablePositionTracking(boolean enable) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageO(
                 DO_TIME_SHIFT_ENABLE_POSITION_TRACKING, enable));
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index a4e2212..67d28d0 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -56,7 +56,9 @@
 import android.view.KeyEvent;
 import android.view.Surface;
 import android.view.View;
+
 import com.android.internal.util.Preconditions;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -309,6 +311,39 @@
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = false, prefix = "TIME_SHIFT_MODE_", value = {
+            TIME_SHIFT_MODE_OFF,
+            TIME_SHIFT_MODE_LOCAL,
+            TIME_SHIFT_MODE_NETWORK,
+            TIME_SHIFT_MODE_AUTO})
+    public @interface TimeShiftMode {}
+    /**
+     * Time shift mode: off.
+     * <p>Time shift is disabled.
+     * @hide
+     */
+    public static final int TIME_SHIFT_MODE_OFF = 1;
+    /**
+     * Time shift mode: local.
+     * <p>Time shift is handle locally, using on-device data. E.g. playing a local file.
+     * @hide
+     */
+    public static final int TIME_SHIFT_MODE_LOCAL = 2;
+    /**
+     * Time shift mode: network.
+     * <p>Time shift is handle remotely via network. E.g. online streaming.
+     * @hide
+     */
+    public static final int TIME_SHIFT_MODE_NETWORK = 3;
+    /**
+     * Time shift mode: auto.
+     * <p>Time shift mode is handled automatically.
+     * @hide
+     */
+    public static final int TIME_SHIFT_MODE_AUTO = 4;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
     @IntDef({RECORDING_ERROR_UNKNOWN, RECORDING_ERROR_INSUFFICIENT_SPACE,
             RECORDING_ERROR_RESOURCE_BUSY})
     public @interface RecordingError {}
@@ -719,6 +754,36 @@
         }
 
         /**
+         * This is called when cueing message becomes available or unavailable.
+         * @param session A {@link TvInputManager.Session} associated with this callback.
+         * @param available The current availability of cueing message. {@code true} if cueing
+         *                  message is available; {@code false} if it becomes unavailable.
+         */
+        public void onCueingMessageAvailability(Session session, boolean available) {
+        }
+
+        /**
+         * This is called when time shift mode is set or updated.
+         * @param session A {@link TvInputManager.Session} associated with this callback.
+         * @param mode The current time shift mode. The value is one of the following:
+         * {@link TvInputManager#TIME_SHIFT_MODE_OFF}, {@link TvInputManager#TIME_SHIFT_MODE_LOCAL},
+         * {@link TvInputManager#TIME_SHIFT_MODE_NETWORK},
+         * {@link TvInputManager#TIME_SHIFT_MODE_AUTO}.
+         */
+        public void onTimeShiftMode(Session session, @TimeShiftMode int mode) {
+        }
+
+        /**
+         * Informs the app available speeds for time-shifting.
+         * @param session A {@link TvInputManager.Session} associated with this callback.
+         * @param speeds An ordered array of playback speeds, expressed as values relative to the
+         *               normal playback speed 1.0.
+         * @see PlaybackParams#getSpeed()
+         */
+        public void onAvailableSpeeds(Session session, float[] speeds) {
+        }
+
+        /**
          * This is called when the session has been tuned to the given channel.
          *
          * @param channelUri The URI of a channel.
@@ -972,6 +1037,33 @@
             });
         }
 
+        void postCueingMessageAvailability(final boolean available) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onCueingMessageAvailability(mSession, available);
+                }
+            });
+        }
+
+        void postTimeShiftMode(final int mode) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onTimeShiftMode(mSession, mode);
+                }
+            });
+        }
+
+        void postAvailableSpeeds(float[] speeds) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onAvailableSpeeds(mSession, speeds);
+                }
+            });
+        }
+
         void postTuned(final Uri channelUri) {
             mHandler.post(new Runnable() {
                 @Override
@@ -1475,6 +1567,42 @@
             }
 
             @Override
+            public void onCueingMessageAvailability(boolean available, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postCueingMessageAvailability(available);
+                }
+            }
+
+            @Override
+            public void onTimeShiftMode(int mode, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postTimeShiftMode(mode);
+                }
+            }
+
+            @Override
+            public void onAvailableSpeeds(float[] speeds, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postAvailableSpeeds(speeds);
+                }
+            }
+
+            @Override
             public void onTuned(Uri channelUri, int seq) {
                 synchronized (mSessionCallbackRecordMap) {
                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
@@ -3058,6 +3186,27 @@
         }
 
         /**
+         * Sets time shift mode.
+         *
+         * @param mode The time shift mode. The value is one of the following:
+         * {@link TvInputManager#TIME_SHIFT_MODE_OFF}, {@link TvInputManager#TIME_SHIFT_MODE_LOCAL},
+         * {@link TvInputManager#TIME_SHIFT_MODE_NETWORK},
+         * {@link TvInputManager#TIME_SHIFT_MODE_AUTO}.
+         * @hide
+         */
+        void timeShiftSetMode(@TimeShiftMode int mode) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.timeShiftSetMode(mToken, mode, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
          * Enable/disable position tracking.
          *
          * @param enable {@code true} to enable tracking, {@code false} otherwise.
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index b769757..7a4d988d 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -59,11 +59,14 @@
 import android.view.WindowManager;
 import android.view.accessibility.CaptioningManager;
 import android.widget.FrameLayout;
+
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.Preconditions;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -1082,6 +1085,59 @@
         }
 
         /**
+         * Informs the app that the time shift mode is set or updated.
+         *
+         * @param mode The current time shift mode. The value is one of the following:
+         * {@link TvInputManager#TIME_SHIFT_MODE_OFF}, {@link TvInputManager#TIME_SHIFT_MODE_LOCAL},
+         * {@link TvInputManager#TIME_SHIFT_MODE_NETWORK},
+         * {@link TvInputManager#TIME_SHIFT_MODE_AUTO}.
+         * @hide
+         */
+        public void notifyTimeShiftMode(@android.media.tv.TvInputManager.TimeShiftMode int mode) {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) Log.d(TAG, "notifyTimeShiftMode");
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onTimeShiftMode(mode);
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in notifyTimeShiftMode", e);
+                    }
+                }
+            });
+        }
+
+        /**
+         * Informs the app available speeds for time-shifting.
+         * <p>This should be called when time-shifting is enabled.
+         *
+         * @param speeds An ordered array of playback speeds, expressed as values relative to the
+         *               normal playback speed 1.0.
+         * @see PlaybackParams#getSpeed()
+         * @hide
+         */
+        public void notifyAvailableSpeeds(@NonNull float[] speeds) {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) Log.d(TAG, "notifyAvailableSpeeds");
+                        if (mSessionCallback != null) {
+                            Arrays.sort(speeds);
+                            mSessionCallback.onAvailableSpeeds(speeds);
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in notifyAvailableSpeeds", e);
+                    }
+                }
+            });
+        }
+
+        /**
          * Notifies signal strength.
          */
         public void notifySignalStrength(@TvInputManager.SignalStrength final int strength) {
@@ -1102,6 +1158,33 @@
         }
 
         /**
+         * Informs the application that cueing message is available or unavailable.
+         *
+         * <p>The cueing message is used for digital program insertion, based on the standard
+         * ANSI/SCTE 35 2019r1.
+         *
+         * @param available {@code true} if cueing message is available; {@code false} if it becomes
+         *                  unavailable.
+         * @hide
+         */
+        public void notifyCueingMessageAvailability(boolean available) {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) Log.d(TAG, "notifyCueingMessageAvailability");
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onCueingMessageAvailability(available);
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in notifyCueingMessageAvailability", e);
+                    }
+                }
+            });
+        }
+
+        /**
          * Assigns a size and position to the surface passed in {@link #onSetSurface}. The position
          * is relative to the overlay view that sits on top of this surface.
          *
@@ -1448,6 +1531,18 @@
         }
 
         /**
+         * Called when the application sets time shift mode.
+         *
+         * @param mode The time shift mode. The value is one of the following:
+         * {@link TvInputManager#TIME_SHIFT_MODE_OFF}, {@link TvInputManager#TIME_SHIFT_MODE_LOCAL},
+         * {@link TvInputManager#TIME_SHIFT_MODE_NETWORK},
+         * {@link TvInputManager#TIME_SHIFT_MODE_AUTO}.
+         * @hide
+         */
+        public void onTimeShiftSetMode(@android.media.tv.TvInputManager.TimeShiftMode int mode) {
+        }
+
+        /**
          * Returns the start position for time shifting, in milliseconds since the epoch.
          * Returns {@link TvInputManager#TIME_SHIFT_INVALID_TIME} if the position is unknown at the
          * moment.
@@ -1853,6 +1948,13 @@
         }
 
         /**
+         * Calls {@link #onTimeShiftSetMode}.
+         */
+        void timeShiftSetMode(int mode) {
+            onTimeShiftSetMode(mode);
+        }
+
+        /**
          * Enable/disable position tracking.
          *
          * @param enable {@code true} to enable tracking, {@code false} otherwise.
diff --git a/media/java/android/media/tv/TvRecordingClient.java b/media/java/android/media/tv/TvRecordingClient.java
index 87d7a0b..b1356f5 100644
--- a/media/java/android/media/tv/TvRecordingClient.java
+++ b/media/java/android/media/tv/TvRecordingClient.java
@@ -21,6 +21,7 @@
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.media.tv.TvInputManager;
+import android.media.tv.interactive.TvInteractiveAppView;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -53,6 +54,8 @@
     private boolean mIsPaused;
     private boolean mIsRecordingStopping;
     private final Queue<Pair<String, Bundle>> mPendingAppPrivateCommands = new ArrayDeque<>();
+    private TvInteractiveAppView mTvIAppView;
+    private String mRecordingId;
 
     /**
      * Creates a new TvRecordingClient object.
@@ -70,6 +73,26 @@
     }
 
     /**
+     * Sets the related {@link TvInteractiveAppView} instance so the interactive app service can be
+     * notified for recording events.
+     *
+     * @param view The related {@link TvInteractiveAppView} instance that is linked to this TV
+     *             recording client. {@code null} to unlink the view.
+     * @param recordingId The ID of the recording which is assigned by applications. {@code null} is
+     *                    valid only when the TvInteractiveAppView parameter is null.
+     * @hide
+     */
+    public void setTvInteractiveAppView(
+            @Nullable TvInteractiveAppView view, @Nullable String recordingId) {
+        if (view != null && recordingId == null) {
+            throw new IllegalArgumentException(
+                    "null recordingId is allowed only when the view is null");
+        }
+        mTvIAppView = view;
+        mRecordingId = view == null ? null : recordingId;
+    }
+
+    /**
      * Tunes to a given channel for TV program recording. The first tune request will create a new
      * recording session for the corresponding TV input and establish a connection between the
      * application and the session. If recording has already started in the current recording
@@ -468,6 +491,9 @@
                 if (mCallback != null) {
                     mCallback.onConnectionFailed(mInputId);
                 }
+                if (mTvIAppView != null) {
+                    mTvIAppView.notifyRecordingConnectionFailed(mRecordingId, mInputId);
+                }
             }
         }
 
@@ -485,7 +511,12 @@
                 return;
             }
             mIsTuned = true;
-            mCallback.onTuned(channelUri);
+            if (mCallback != null) {
+                mCallback.onTuned(channelUri);
+            }
+            if (mTvIAppView != null) {
+                mTvIAppView.notifyRecordingTuned(mRecordingId, channelUri);
+            }
         }
 
         @Override
@@ -506,6 +537,9 @@
             if (mCallback != null) {
                 mCallback.onDisconnected(mInputId);
             }
+            if (mTvIAppView != null) {
+                mTvIAppView.notifyRecordingDisconnected(mRecordingId, mInputId);
+            }
         }
 
         @Override
@@ -524,7 +558,12 @@
             mIsRecordingStarted = false;
             mIsPaused = false;
             mIsRecordingStopping = false;
-            mCallback.onRecordingStopped(recordedProgramUri);
+            if (mCallback != null) {
+                mCallback.onRecordingStopped(recordedProgramUri);
+            }
+            if (mTvIAppView != null) {
+                mTvIAppView.notifyRecordingStopped(mRecordingId);
+            }
         }
 
         @Override
@@ -536,7 +575,12 @@
                 Log.w(TAG, "onError - session not created");
                 return;
             }
-            mCallback.onError(error);
+            if (mCallback == null) {
+                mCallback.onError(error);
+            }
+            if (mTvIAppView != null) {
+                mTvIAppView.notifyRecordingError(mRecordingId, error);
+            }
         }
 
         @Override
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index c7a63ac..3864983 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -53,9 +53,11 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewRootImpl;
+
 import java.lang.ref.WeakReference;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Queue;
 
@@ -625,6 +627,21 @@
     }
 
     /**
+     * Sets time shift mode.
+     *
+     * @param mode The time shift mode. The value is one of the following:
+     * {@link TvInputManager#TIME_SHIFT_MODE_OFF}, {@link TvInputManager#TIME_SHIFT_MODE_LOCAL},
+     * {@link TvInputManager#TIME_SHIFT_MODE_NETWORK},
+     * {@link TvInputManager#TIME_SHIFT_MODE_AUTO}.
+     * @hide
+     */
+    public void timeShiftSetMode(@android.media.tv.TvInputManager.TimeShiftMode int mode) {
+        if (mSession != null) {
+            mSession.timeShiftSetMode(mode);
+        }
+    }
+
+    /**
      * Sets the callback to be invoked when the time shift position is changed.
      *
      * @param callback The callback to receive time shift position changes. A value of {@code null}
@@ -1171,6 +1188,43 @@
         }
 
         /**
+         * This is called when cueing message becomes available or unavailable.
+         *
+         * @param inputId The ID of the TV input bound to this view.
+         * @param available The current availability of cueing message. {@code true} if cueing
+         *                  message is available; {@code false} if it becomes unavailable.
+         * @hide
+         */
+        public void onCueingMessageAvailability(@NonNull String inputId, boolean available) {
+        }
+
+        /**
+         * This is called when time shift mode is set or updated.
+         *
+         * @param inputId The ID of the TV input bound to this view.
+         * @param mode The current time shift mode. The value is one of the following:
+         * {@link TvInputManager#TIME_SHIFT_MODE_OFF}, {@link TvInputManager#TIME_SHIFT_MODE_LOCAL},
+         * {@link TvInputManager#TIME_SHIFT_MODE_NETWORK},
+         * {@link TvInputManager#TIME_SHIFT_MODE_AUTO}.
+         * @hide
+         */
+        public void onTimeShiftMode(
+                @NonNull String inputId, @TvInputManager.TimeShiftMode int mode) {
+        }
+
+        /**
+         * This is called when time-shifting is enabled to inform the available speeds.
+         *
+         * @param inputId The ID of the TV input bound to this view.
+         * @param speeds An ordered array of playback speeds, expressed as values relative to the
+         *               normal playback speed 1.0.
+         * @see PlaybackParams#getSpeed()
+         * @hide
+         */
+        public void onAvailableSpeeds(@NonNull String inputId, float[] speeds) {
+        }
+
+        /**
          * This is called when the session has been tuned to the given channel.
          *
          * @param channelUri The URI of a channel.
@@ -1545,6 +1599,48 @@
         }
 
         @Override
+        public void onCueingMessageAvailability(Session session, boolean available) {
+            if (DEBUG) {
+                Log.d(TAG, "onCueingMessageAvailability(available=" + available + ")");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onCueingMessageAvailability - session not created");
+                return;
+            }
+            if (mCallback != null) {
+                mCallback.onCueingMessageAvailability(mInputId, available);
+            }
+        }
+
+        @Override
+        public void onTimeShiftMode(Session session, int mode) {
+            if (DEBUG) {
+                Log.d(TAG, "onTimeShiftMode(mode=" + mode + ")");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onTimeShiftMode - session not created");
+                return;
+            }
+            if (mCallback != null) {
+                mCallback.onTimeShiftMode(mInputId, mode);
+            }
+        }
+
+        @Override
+        public void onAvailableSpeeds(Session session, float[] speeds) {
+            if (DEBUG) {
+                Log.d(TAG, "onAvailableSpeeds(speeds=" + Arrays.toString(speeds) + ")");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onAvailableSpeeds - session not created");
+                return;
+            }
+            if (mCallback != null) {
+                mCallback.onAvailableSpeeds(mInputId, speeds);
+            }
+        }
+
+        @Override
         public void onTuned(Session session, Uri channelUri) {
             if (DEBUG) {
                 Log.d(TAG, "onTuned(channelUri=" + channelUri + ")");
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
index ad9312f..aac2d61 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
@@ -49,8 +49,14 @@
     void onRequestStreamVolume(int seq);
     void onRequestTrackInfoList(int seq);
     void onRequestCurrentTvInputId(int seq);
+    void onRequestTimeShiftMode(int seq);
+    void onRequestAvailableSpeeds(int seq);
     void onRequestStartRecording(in Uri programUri, int seq);
     void onRequestStopRecording(in String recordingId, int seq);
+    void onRequestScheduleRecording(in String inputId, in Uri channelUri, in Uri programUri,
+            in Bundle params, int seq);
+    void onRequestScheduleRecording2(in String inputId, in Uri channelUri, long start,
+            long duration, int repeat, in Bundle params, int seq);
     void onSetTvRecordingInfo(in String recordingId, in TvRecordingInfo recordingInfo, int seq);
     void onRequestTvRecordingInfo(in String recordingId, int seq);
     void onRequestTvRecordingInfoList(in int type, int seq);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
index c0723f7..e362af2 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
@@ -54,6 +54,8 @@
     void sendStreamVolume(in IBinder sessionToken, float volume, int userId);
     void sendTrackInfoList(in IBinder sessionToken, in List<TvTrackInfo> tracks, int userId);
     void sendCurrentTvInputId(in IBinder sessionToken, in String inputId, int userId);
+    void sendTimeShiftMode(in IBinder sessionToken, int mode, int userId);
+    void sendAvailableSpeeds(in IBinder sessionToken, in float[] speeds, int userId);
     void sendSigningResult(in IBinder sessionToken, in String signingId, in byte[] result,
             int userId);
     void sendTvRecordingInfo(in IBinder sessionToken, in TvRecordingInfo recordingInfo, int userId);
@@ -68,6 +70,16 @@
             in IBinder sessionToken, in String inputId, long timeMs, int userId);
     void notifyTimeShiftCurrentPositionChanged(
             in IBinder sessionToken, in String inputId, long timeMs, int userId);
+    void notifyRecordingConnectionFailed(
+            in IBinder sessionToken, in String recordingId, in String inputId, int userId);
+    void notifyRecordingDisconnected(
+            in IBinder sessionToken, in String recordingId, in String inputId, int userId);
+    void notifyRecordingTuned(
+            in IBinder sessionToken, in String recordingId, in Uri channelUri, int userId);
+    void notifyRecordingError(
+            in IBinder sessionToken, in String recordingId, int err, int userId);
+    void notifyRecordingScheduled(
+            in IBinder sessionToken, in String recordingId, in String requestId, int userId);
     void createSession(in ITvInteractiveAppClient client, in String iAppServiceId, int type,
             int seq, int userId);
     void releaseSession(in IBinder sessionToken, int userId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index 9ae9ca7..8d77141 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -46,6 +46,8 @@
     void sendStreamVolume(float volume);
     void sendTrackInfoList(in List<TvTrackInfo> tracks);
     void sendCurrentTvInputId(in String inputId);
+    void sendTimeShiftMode(int mode);
+    void sendAvailableSpeeds(in float[] speeds);
     void sendSigningResult(in String signingId, in byte[] result);
     void sendTvRecordingInfo(in TvRecordingInfo recordingInfo);
     void sendTvRecordingInfoList(in List<TvRecordingInfo> recordingInfoList);
@@ -54,6 +56,11 @@
     void notifyTimeShiftStatusChanged(in String inputId, int status);
     void notifyTimeShiftStartPositionChanged(in String inputId, long timeMs);
     void notifyTimeShiftCurrentPositionChanged(in String inputId, long timeMs);
+    void notifyRecordingConnectionFailed(in String recordingId, in String inputId);
+    void notifyRecordingDisconnected(in String recordingId, in String inputId);
+    void notifyRecordingTuned(in String recordingId, in Uri channelUri);
+    void notifyRecordingError(in String recordingId, int err);
+    void notifyRecordingScheduled(in String recordingId, in String requestId);
     void release();
     void notifyTuned(in Uri channelUri);
     void notifyTrackSelected(int type, in String trackId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
index d84affd..b71f23c 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
@@ -48,8 +48,14 @@
     void onRequestStreamVolume();
     void onRequestTrackInfoList();
     void onRequestCurrentTvInputId();
+    void onRequestTimeShiftMode();
+    void onRequestAvailableSpeeds();
     void onRequestStartRecording(in Uri programUri);
     void onRequestStopRecording(in String recordingId);
+    void onRequestScheduleRecording(in String inputId, in Uri channelUri, in Uri programUri,
+            in Bundle params);
+    void onRequestScheduleRecording2(in String inputId, in Uri channelUri, long start,
+            long duration, int repeat, in Bundle params);
     void onSetTvRecordingInfo(in String recordingId, in TvRecordingInfo recordingInfo);
     void onRequestTvRecordingInfo(in String recordingId);
     void onRequestTvRecordingInfoList(in int type);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index 8a23e65..fa339ce 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -95,6 +95,13 @@
     private static final int DO_NOTIFY_TIME_SHIFT_START_POSITION_CHANGED = 38;
     private static final int DO_NOTIFY_TIME_SHIFT_CURRENT_POSITION_CHANGED = 39;
     private static final int DO_SEND_CURRENT_VIDEO_BOUNDS = 40;
+    private static final int DO_NOTIFY_RECORDING_CONNECTION_FAILED = 41;
+    private static final int DO_NOTIFY_RECORDING_DISCONNECTED = 42;
+    private static final int DO_NOTIFY_RECORDING_TUNED = 43;
+    private static final int DO_NOTIFY_RECORDING_ERROR = 44;
+    private static final int DO_NOTIFY_RECORDING_SCHEDULED = 45;
+    private static final int DO_SEND_TIME_SHIFT_MODE = 46;
+    private static final int DO_SEND_AVAILABLE_SPEEDS = 47;
 
     private final HandlerCaller mCaller;
     private Session mSessionImpl;
@@ -182,6 +189,14 @@
                 mSessionImpl.sendCurrentTvInputId((String) msg.obj);
                 break;
             }
+            case DO_SEND_TIME_SHIFT_MODE: {
+                mSessionImpl.sendTimeShiftMode((Integer) msg.obj);
+                break;
+            }
+            case DO_SEND_AVAILABLE_SPEEDS: {
+                mSessionImpl.sendAvailableSpeeds((float[]) msg.obj);
+                break;
+            }
             case DO_SEND_RECORDING_INFO: {
                 mSessionImpl.sendTvRecordingInfo((TvRecordingInfo) msg.obj);
                 break;
@@ -311,6 +326,37 @@
                 args.recycle();
                 break;
             }
+            case DO_NOTIFY_RECORDING_CONNECTION_FAILED: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                mSessionImpl.notifyRecordingConnectionFailed(
+                        (String) args.arg1, (String) args.arg2);
+                args.recycle();
+                break;
+            }
+            case DO_NOTIFY_RECORDING_DISCONNECTED: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                mSessionImpl.notifyRecordingDisconnected((String) args.arg1, (String) args.arg2);
+                args.recycle();
+                break;
+            }
+            case DO_NOTIFY_RECORDING_TUNED: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                mSessionImpl.notifyRecordingTuned((String) args.arg1, (Uri) args.arg2);
+                args.recycle();
+                break;
+            }
+            case DO_NOTIFY_RECORDING_ERROR: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                mSessionImpl.notifyRecordingError((String) args.arg1, (Integer) args.arg2);
+                args.recycle();
+                break;
+            }
+            case DO_NOTIFY_RECORDING_SCHEDULED: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                mSessionImpl.notifyRecordingScheduled((String) args.arg1, (String) args.arg2);
+                args.recycle();
+                break;
+            }
             default: {
                 Log.w(TAG, "Unhandled message code: " + msg.what);
                 break;
@@ -396,6 +442,18 @@
     }
 
     @Override
+    public void sendTimeShiftMode(int mode) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageI(DO_SEND_TIME_SHIFT_MODE, mode));
+    }
+
+    @Override
+    public void sendAvailableSpeeds(float[] speeds) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageO(DO_SEND_AVAILABLE_SPEEDS, speeds));
+    }
+
+    @Override
     public void sendTvRecordingInfo(@Nullable TvRecordingInfo recordingInfo) {
         mCaller.executeOrSendMessage(
                 mCaller.obtainMessageO(DO_SEND_RECORDING_INFO, recordingInfo));
@@ -509,6 +567,36 @@
     }
 
     @Override
+    public void notifyRecordingConnectionFailed(String recordingId, String inputId) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(
+                DO_NOTIFY_RECORDING_CONNECTION_FAILED, recordingId, inputId));
+    }
+
+    @Override
+    public void notifyRecordingDisconnected(String recordingId, String inputId) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(
+                DO_NOTIFY_RECORDING_DISCONNECTED, recordingId, inputId));
+    }
+
+    @Override
+    public void notifyRecordingTuned(String recordingId, Uri channelUri) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(
+                DO_NOTIFY_RECORDING_TUNED, recordingId, channelUri));
+    }
+
+    @Override
+    public void notifyRecordingError(String recordingId, int err) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(
+                DO_NOTIFY_RECORDING_ERROR, recordingId, err));
+    }
+
+    @Override
+    public void notifyRecordingScheduled(String recordingId, String requestId) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(
+                DO_NOTIFY_RECORDING_SCHEDULED, recordingId, recordingId));
+    }
+
+    @Override
     public void setSurface(Surface surface) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_SURFACE, surface));
     }
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index fd3c29b..c88c8d4 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -33,6 +33,7 @@
 import android.media.tv.TvInputManager;
 import android.media.tv.TvRecordingInfo;
 import android.media.tv.TvTrackInfo;
+import android.media.tv.interactive.TvInteractiveAppService.Session;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -517,6 +518,30 @@
             }
 
             @Override
+            public void onRequestTimeShiftMode(int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postRequestTimeShiftMode();
+                }
+            }
+
+            @Override
+            public void onRequestAvailableSpeeds(int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postRequestAvailableSpeeds();
+                }
+            }
+
+            @Override
             public void onRequestStartRecording(Uri programUri, int seq) {
                 synchronized (mSessionCallbackRecordMap) {
                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
@@ -541,6 +566,33 @@
             }
 
             @Override
+            public void onRequestScheduleRecording(String inputId, Uri channelUri, Uri programUri,
+                    Bundle params, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postRequestScheduleRecording(inputId, channelUri, programUri, params);
+                }
+            }
+
+            @Override
+            public void onRequestScheduleRecording2(String inputId, Uri channelUri, long startTime,
+                    long duration, int repeatDays, Bundle params, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postRequestScheduleRecording(
+                            inputId, channelUri, startTime, duration, repeatDays, params);
+                }
+            }
+
+            @Override
             public void onSetTvRecordingInfo(String recordingId, TvRecordingInfo recordingInfo,
                     int seq) {
                 synchronized (mSessionCallbackRecordMap) {
@@ -1168,6 +1220,30 @@
             }
         }
 
+        void sendTimeShiftMode(int mode) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.sendTimeShiftMode(mToken, mode, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        void sendAvailableSpeeds(float[] speeds) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.sendAvailableSpeeds(mToken, speeds, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         void sendTvRecordingInfo(@Nullable TvRecordingInfo recordingInfo) {
             if (mToken == null) {
                 Log.w(TAG, "The session has been already released");
@@ -1289,6 +1365,66 @@
             }
         }
 
+        void notifyRecordingConnectionFailed(@NonNull String recordingId, @NonNull String inputId) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyRecordingConnectionFailed(mToken, recordingId, inputId, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        void notifyRecordingDisconnected(@NonNull String recordingId, @NonNull String inputId) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyRecordingDisconnected(mToken, recordingId, inputId, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        void notifyRecordingTuned(@NonNull String recordingId, @NonNull Uri channelUri) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyRecordingTuned(mToken, recordingId, channelUri, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        void notifyRecordingError(@NonNull String recordingId, int err) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyRecordingError(mToken, recordingId, err, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        void notifyRecordingScheduled(@NonNull String recordingId, @Nullable String requestId) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyRecordingScheduled(mToken, recordingId, recordingId, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         /**
          * Sets the {@link android.view.Surface} for this session.
          *
@@ -1976,6 +2112,24 @@
             });
         }
 
+        void postRequestTimeShiftMode() {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onRequestTimeShiftMode(mSession);
+                }
+            });
+        }
+
+        void postRequestAvailableSpeeds() {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onRequestAvailableSpeeds(mSession);
+                }
+            });
+        }
+
         void postRequestStartRecording(Uri programUri) {
             mHandler.post(new Runnable() {
                 @Override
@@ -1994,6 +2148,28 @@
             });
         }
 
+        void postRequestScheduleRecording(String inputId, Uri channelUri, Uri programUri,
+                Bundle params) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onRequestScheduleRecording(
+                            mSession, inputId, channelUri, programUri, params);
+                }
+            });
+        }
+
+        void postRequestScheduleRecording(String inputId, Uri channelUri, long startTime,
+                long duration, int repeatDays, Bundle params) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onRequestScheduleRecording(
+                            mSession, inputId, channelUri, startTime, duration, repeatDays, params);
+                }
+            });
+        }
+
         void postRequestSigning(String id, String algorithm, String alias, byte[] data) {
             mHandler.post(new Runnable() {
                 @Override
@@ -2144,7 +2320,7 @@
         }
 
         /**
-         * This is called when {@link TvInteractiveAppService.Session#SetVideoBounds} is called.
+         * This is called when {@link TvInteractiveAppService.Session#setVideoBounds} is called.
          *
          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
          */
@@ -2152,7 +2328,7 @@
         }
 
         /**
-         * This is called when {@link TvInteractiveAppService.Session#RequestCurrentVideoBounds} is
+         * This is called when {@link TvInteractiveAppService.Session#requestCurrentVideoBounds} is
          * called.
          *
          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
@@ -2161,7 +2337,7 @@
         }
 
         /**
-         * This is called when {@link TvInteractiveAppService.Session#RequestCurrentChannelUri} is
+         * This is called when {@link TvInteractiveAppService.Session#requestCurrentChannelUri} is
          * called.
          *
          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
@@ -2170,7 +2346,7 @@
         }
 
         /**
-         * This is called when {@link TvInteractiveAppService.Session#RequestCurrentChannelLcn} is
+         * This is called when {@link TvInteractiveAppService.Session#requestCurrentChannelLcn} is
          * called.
          *
          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
@@ -2179,7 +2355,7 @@
         }
 
         /**
-         * This is called when {@link TvInteractiveAppService.Session#RequestStreamVolume} is
+         * This is called when {@link TvInteractiveAppService.Session#requestStreamVolume} is
          * called.
          *
          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
@@ -2188,7 +2364,7 @@
         }
 
         /**
-         * This is called when {@link TvInteractiveAppService.Session#RequestTrackInfoList} is
+         * This is called when {@link TvInteractiveAppService.Session#requestTrackInfoList} is
          * called.
          *
          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
@@ -2197,7 +2373,7 @@
         }
 
         /**
-         * This is called when {@link TvInteractiveAppService.Session#RequestCurrentTvInputId} is
+         * This is called when {@link TvInteractiveAppService.Session#requestCurrentTvInputId} is
          * called.
          *
          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
@@ -2206,7 +2382,25 @@
         }
 
         /**
-         * This is called when {@link TvInteractiveAppService.Session#RequestStartRecording} is
+         * This is called when {@link TvInteractiveAppService.Session#requestTimeShiftMode()} is
+         * called.
+         *
+         * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
+         */
+        public void onRequestTimeShiftMode(Session session) {
+        }
+
+        /**
+         * This is called when {@link TvInteractiveAppService.Session#requestAvailableSpeeds()} is
+         * called.
+         *
+         * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
+         */
+        public void onRequestAvailableSpeeds(Session session) {
+        }
+
+        /**
+         * This is called when {@link TvInteractiveAppService.Session#requestStartRecording} is
          * called.
          *
          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
@@ -2216,8 +2410,8 @@
         }
 
         /**
-         * This is called when {@link TvInteractiveAppService.Session#RequestStopRecording} is
-         * called.
+         * This is called when {@link TvInteractiveAppService.Session#requestStopRecording(String)}
+         * is called.
          *
          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
          * @param recordingId The recordingId of the recording to be stopped.
@@ -2227,6 +2421,47 @@
 
         /**
          * This is called when
+         * {@link TvInteractiveAppService.Session#requestScheduleRecording(String, Uri, Uri, Bundle)}
+         * is called.
+         *
+         * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
+         * @param inputId The ID of the TV input for the given channel.
+         * @param channelUri The URI of a channel to be recorded.
+         * @param programUri The URI of the TV program to be recorded.
+         * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
+         *            name, i.e. prefixed with a package name you own, so that different developers
+         *            will not create conflicting keys.
+         * @see android.media.tv.TvRecordingClient#tune(String, Uri, Bundle)
+         * @see android.media.tv.TvRecordingClient#startRecording(Uri)
+         */
+        public void onRequestScheduleRecording(Session session, @NonNull String inputId,
+                @NonNull Uri channelUri, @NonNull Uri programUri, @NonNull Bundle params) {
+        }
+
+        /**
+         * This is called when
+         * {@link TvInteractiveAppService.Session#requestScheduleRecording(String, Uri, long, long, int, Bundle)}
+         * is called.
+         *
+         * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
+         * @param inputId The ID of the TV input for the given channel.
+         * @param channelUri The URI of a channel to be recorded.
+         * @param startTime The start time of the recording in milliseconds since epoch.
+         * @param duration The duration of the recording in milliseconds.
+         * @param repeatDays The repeated days. 0 if not repeated.
+         * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
+         *            name, i.e. prefixed with a package name you own, so that different developers
+         *            will not create conflicting keys.
+         * @see android.media.tv.TvRecordingClient#tune(String, Uri, Bundle)
+         * @see android.media.tv.TvRecordingClient#startRecording(Uri)
+         */
+        public void onRequestScheduleRecording(Session session, @NonNull String inputId,
+                @NonNull Uri channelUri, long startTime, long duration, int repeatDays,
+                @NonNull Bundle params) {
+        }
+
+        /**
+         * This is called when
          * {@link TvInteractiveAppService.Session#setTvRecordingInfo(String, TvRecordingInfo)} is
          * called.
          *
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index d1777cc..360073d 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -224,6 +224,7 @@
             TIME_SHIFT_COMMAND_TYPE_RESUME,
             TIME_SHIFT_COMMAND_TYPE_SEEK_TO,
             TIME_SHIFT_COMMAND_TYPE_SET_PLAYBACK_PARAMS,
+            TIME_SHIFT_COMMAND_TYPE_SET_MODE,
     })
     public @interface TimeShiftCommandType {}
 
@@ -262,6 +263,12 @@
      * @hide
      */
     public static final String TIME_SHIFT_COMMAND_TYPE_SET_PLAYBACK_PARAMS = "set_playback_params";
+    /**
+     * Time shift command type: set time shift mode.
+     *
+     * @hide
+     */
+    public static final String TIME_SHIFT_COMMAND_TYPE_SET_MODE = "set_mode";
 
     /**
      * Time shift command parameter: program URI.
@@ -287,6 +294,17 @@
      * @hide
      */
     public static final String COMMAND_PARAMETER_KEY_PLAYBACK_PARAMS = "command_playback_params";
+    /**
+     * Time shift command parameter: playback params.
+     * <p>Type: Integer. One of {@link TvInputManager#TIME_SHIFT_MODE_OFF},
+     * {@link TvInputManager#TIME_SHIFT_MODE_LOCAL},
+     * {@link TvInputManager#TIME_SHIFT_MODE_NETWORK},
+     * {@link TvInputManager#TIME_SHIFT_MODE_AUTO}.
+     *
+     * @see #TIME_SHIFT_COMMAND_TYPE_SET_MODE
+     * @hide
+     */
+    public static final String COMMAND_PARAMETER_KEY_TIME_SHIFT_MODE = "command_time_shift_mode";
 
     private final Handler mServiceHandler = new ServiceHandler();
     private final RemoteCallbackList<ITvInteractiveAppServiceCallback> mCallbacks =
@@ -570,6 +588,24 @@
         }
 
         /**
+         * Receives current time shift mode.
+         * @param mode The current time shift mode. The value is one of the following:
+         * {@link TvInputManager#TIME_SHIFT_MODE_OFF}, {@link TvInputManager#TIME_SHIFT_MODE_LOCAL},
+         * {@link TvInputManager#TIME_SHIFT_MODE_NETWORK},
+         * {@link TvInputManager#TIME_SHIFT_MODE_AUTO}.
+         * @hide
+         */
+        public void onTimeShiftMode(@android.media.tv.TvInputManager.TimeShiftMode int mode) {
+        }
+
+        /**
+         * Receives available speeds.
+         * @hide
+         */
+        public void onAvailableSpeeds(@NonNull float[] speeds) {
+        }
+
+        /**
          * Receives requested recording info.
          *
          * @param recordingInfo The requested recording info. Null if recording not found.
@@ -608,6 +644,77 @@
         }
 
         /**
+         * This is called when an error occurred while establishing a connection to the recording
+         * session for the corresponding TV input.
+         *
+         * @param recordingId The ID of the related recording which is sent via
+         *                    {@link #notifyRecordingStarted(String)}
+         * @param inputId The ID of the TV input bound to the current TvRecordingClient.
+         * @see android.media.tv.TvRecordingClient.RecordingCallback#onConnectionFailed(String)
+         * @hide
+         */
+        public void onRecordingConnectionFailed(
+                @NonNull String recordingId, @NonNull String inputId) {
+        }
+
+        /**
+         * This is called when the connection to the current recording session is lost.
+         *
+         * @param recordingId The ID of the related recording which is sent via
+         *                    {@link #notifyRecordingStarted(String)}
+         * @param inputId The ID of the TV input bound to the current TvRecordingClient.
+         * @see android.media.tv.TvRecordingClient.RecordingCallback#onDisconnected(String)
+         * @hide
+         */
+        public void onRecordingDisconnected(@NonNull String recordingId, @NonNull String inputId) {
+        }
+
+        /**
+         * This is called when the recording session has been tuned to the given channel and is
+         * ready to start recording.
+         *
+         * @param recordingId The ID of the related recording which is sent via
+         *                    {@link #notifyRecordingStarted(String)}
+         * @param channelUri The URI of the tuned channel.
+         * @see android.media.tv.TvRecordingClient.RecordingCallback#onTuned(Uri)
+         * @hide
+         */
+        public void onRecordingTuned(@NonNull String recordingId, @NonNull Uri channelUri) {
+        }
+
+        /**
+         * This is called when an issue has occurred. It may be called at any time after the current
+         * recording session is created until it is released.
+         *
+         * @param recordingId The ID of the related recording which is sent via
+         *                    {@link #notifyRecordingStarted(String)}
+         * @param err The error code. Should be one of the following.
+         * <ul>
+         * <li>{@link TvInputManager#RECORDING_ERROR_UNKNOWN}
+         * <li>{@link TvInputManager#RECORDING_ERROR_INSUFFICIENT_SPACE}
+         * <li>{@link TvInputManager#RECORDING_ERROR_RESOURCE_BUSY}
+         * </ul>
+         * @see android.media.tv.TvRecordingClient.RecordingCallback#onError(int)
+         * @hide
+         */
+        public void onRecordingError(
+                @NonNull String recordingId, @TvInputManager.RecordingError int err) {
+        }
+
+        /**
+         * This is called when the recording has been scheduled.
+         *
+         * @param recordingId The ID assigned to this recording by the app. It can be used to send
+         *                    recording related requests such as
+         *                    {@link #requestStopRecording(String)}.
+         * @param requestId The ID of the request when requestScheduleRecording is called.
+         *                  {@code null} if the recording is not triggered by a request.
+         * @hide
+         */
+        public void onRecordingScheduled(@NonNull String recordingId, @Nullable String requestId) {
+        }
+
+        /**
          * Receives signing result.
          * @param signingId the ID to identify the request. It's the same as the corresponding ID in
          *        {@link Session#requestSigning(String, String, String, byte[])}
@@ -1164,6 +1271,54 @@
         }
 
         /**
+         * Requests time shift mode.
+         * @hide
+         */
+        @CallSuper
+        public void requestTimeShiftMode() {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) {
+                            Log.d(TAG, "requestTimeShiftMode");
+                        }
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onRequestTimeShiftMode();
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in requestTimeShiftMode", e);
+                    }
+                }
+            });
+        }
+
+        /**
+         * Requests available speeds for time shift.
+         * @hide
+         */
+        @CallSuper
+        public void requestAvailableSpeeds() {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) {
+                            Log.d(TAG, "requestAvailableSpeeds");
+                        }
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onRequestAvailableSpeeds();
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in requestAvailableSpeeds", e);
+                    }
+                }
+            });
+        }
+
+        /**
          * Requests starting of recording
          *
          * <p> This is used to request the active {@link android.media.tv.TvRecordingClient} to
@@ -1219,7 +1374,71 @@
         }
 
         /**
-         * Sets the recording info for the specified recording.
+         * Requests scheduling of a recording.
+         *
+         * @param inputId The ID of the TV input for the given channel.
+         * @param channelUri The URI of a channel to be recorded.
+         * @param programUri The URI of the TV program to be recorded.
+         * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
+         *            name, i.e. prefixed with a package name you own, so that different developers
+         *            will not create conflicting keys.
+         * @see android.media.tv.TvRecordingClient#tune(String, Uri, Bundle)
+         * @see android.media.tv.TvRecordingClient#startRecording(Uri)
+         * @hide
+         */
+        @CallSuper
+        public void requestScheduleRecording(@NonNull String inputId, @NonNull Uri channelUri,
+                @NonNull Uri programUri, @NonNull Bundle params) {
+            executeOrPostRunnableOnMainThread(() -> {
+                try {
+                    if (DEBUG) {
+                        Log.d(TAG, "requestScheduleRecording");
+                    }
+                    if (mSessionCallback != null) {
+                        mSessionCallback.onRequestScheduleRecording(
+                                inputId, channelUri, programUri, params);
+                    }
+                } catch (RemoteException e) {
+                    Log.w(TAG, "error in requestScheduleRecording", e);
+                }
+            });
+        }
+
+        /**
+         * Requests scheduling of a recording.
+         *
+         * @param inputId The ID of the TV input for the given channel.
+         * @param channelUri The URI of a channel to be recorded.
+         * @param startTime The start time of the recording in milliseconds since epoch.
+         * @param duration The duration of the recording in milliseconds.
+         * @param repeatDays The repeated days. 0 if not repeated.
+         * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
+         *            name, i.e. prefixed with a package name you own, so that different developers
+         *            will not create conflicting keys.
+         * @see android.media.tv.TvRecordingClient#tune(String, Uri, Bundle)
+         * @see android.media.tv.TvRecordingClient#startRecording(Uri)
+         * @hide
+         */
+        @CallSuper
+        public void requestScheduleRecording(@NonNull String inputId, @NonNull Uri channelUri,
+                long startTime, long duration, int repeatDays, @NonNull Bundle params) {
+            executeOrPostRunnableOnMainThread(() -> {
+                try {
+                    if (DEBUG) {
+                        Log.d(TAG, "requestScheduleRecording");
+                    }
+                    if (mSessionCallback != null) {
+                        mSessionCallback.onRequestScheduleRecording2(
+                                inputId, channelUri, startTime, duration, repeatDays, params);
+                    }
+                } catch (RemoteException e) {
+                    Log.w(TAG, "error in requestScheduleRecording", e);
+                }
+            });
+        }
+
+        /**
+         * Sets the recording info for the specified recording
          *
          * @param recordingId The ID of the recording to set the info for. This is provided by the
          *     TV app in {@link TvInteractiveAppView#notifyRecordingStarted(String)}
@@ -1399,6 +1618,14 @@
             onCurrentTvInputId(inputId);
         }
 
+        void sendTimeShiftMode(int mode) {
+            onTimeShiftMode(mode);
+        }
+
+        void sendAvailableSpeeds(@NonNull float[] speeds) {
+            onAvailableSpeeds(speeds);
+        }
+
         void sendTvRecordingInfo(@Nullable TvRecordingInfo recordingInfo) {
             onTvRecordingInfo(recordingInfo);
         }
@@ -1540,6 +1767,41 @@
         }
 
         /**
+         * Calls {@link #onRecordingConnectionFailed(String, String)}.
+         */
+        void notifyRecordingConnectionFailed(String recordingId, String inputId) {
+            onRecordingConnectionFailed(recordingId, inputId);
+        }
+
+        /**
+         * Calls {@link #onRecordingDisconnected(String, String)}.
+         */
+        void notifyRecordingDisconnected(String recordingId, String inputId) {
+            onRecordingDisconnected(recordingId, inputId);
+        }
+
+        /**
+         * Calls {@link #onRecordingTuned(String, Uri)}.
+         */
+        void notifyRecordingTuned(String recordingId, Uri channelUri) {
+            onRecordingTuned(recordingId, channelUri);
+        }
+
+        /**
+         * Calls {@link #onRecordingError(String, int)}.
+         */
+        void notifyRecordingError(String recordingId, int err) {
+            onRecordingError(recordingId, err);
+        }
+
+        /**
+         * Calls {@link #onRecordingScheduled(String, String)}.
+         */
+        void notifyRecordingScheduled(String recordingId, String requestId) {
+            onRecordingScheduled(recordingId, requestId);
+        }
+
+        /**
          * Calls {@link #onTimeShiftPlaybackParams(PlaybackParams)}.
          */
         void notifyTimeShiftPlaybackParams(PlaybackParams params) {
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 4a01440..78c6bcf 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -49,6 +49,7 @@
 import android.view.ViewRootImpl;
 
 import java.security.KeyStore;
+import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -596,6 +597,42 @@
     }
 
     /**
+     * Sends current time shift mode to related TV interactive app.
+     *
+     * @param mode The current time shift mode. The value is one of the following:
+     * {@link TvInputManager#TIME_SHIFT_MODE_OFF}, {@link TvInputManager#TIME_SHIFT_MODE_LOCAL},
+     * {@link TvInputManager#TIME_SHIFT_MODE_NETWORK},
+     * {@link TvInputManager#TIME_SHIFT_MODE_AUTO}.
+     * @hide
+     */
+    public void sendTimeShiftMode(@android.media.tv.TvInputManager.TimeShiftMode int mode) {
+        if (DEBUG) {
+            Log.d(TAG, "sendTimeShiftMode");
+        }
+        if (mSession != null) {
+            mSession.sendTimeShiftMode(mode);
+        }
+    }
+
+    /**
+     * Sends available supported speeds to related TV interactive app.
+     *
+     * @param speeds An ordered array of playback speeds, expressed as values relative to the normal
+     *               playback speed 1.0.
+     * @see PlaybackParams#getSpeed()
+     * @hide
+     */
+    public void sendAvailableSpeeds(@NonNull float[] speeds) {
+        if (DEBUG) {
+            Log.d(TAG, "sendAvailableSpeeds");
+        }
+        if (mSession != null) {
+            Arrays.sort(speeds);
+            mSession.sendAvailableSpeeds(speeds);
+        }
+    }
+
+    /**
      * Sends the requested {@link android.media.tv.TvRecordingInfo}.
      *
      * @param recordingInfo The recording info requested {@code null} if no recording found.
@@ -633,6 +670,7 @@
      * @see TvInteractiveAppView#notifyRecordingStopped(String)
      */
     public void notifyRecordingStarted(@NonNull String recordingId) {
+        // TODO: add request ID to identify and map the corresponding request.
         if (DEBUG) {
             Log.d(TAG, "notifyRecordingStarted");
         }
@@ -767,6 +805,118 @@
         }
     }
 
+    /**
+     * This is called to notify the corresponding interactive app service when an error occurred
+     * while establishing a connection to the recording session for the corresponding TV input.
+     *
+     * @param recordingId The ID of the related recording which is sent via
+     *                    {@link #notifyRecordingStarted(String)}
+     * @param inputId The ID of the TV input bound to the current TvRecordingClient.
+     * @see android.media.tv.TvRecordingClient.RecordingCallback#onConnectionFailed(String)
+     * @hide
+     */
+    public void notifyRecordingConnectionFailed(
+            @NonNull String recordingId, @NonNull String inputId) {
+        if (DEBUG) {
+            Log.d(TAG, "notifyRecordingConnectionFailed recordingId=" + recordingId
+                    + "; inputId=" + inputId);
+        }
+        if (mSession != null) {
+            mSession.notifyRecordingConnectionFailed(recordingId, inputId);
+        }
+    }
+
+    /**
+     * This is called to notify the corresponding interactive app service when the connection to
+     * the current recording session is lost.
+     *
+     * @param recordingId The ID of the related recording which is sent via
+     *                    {@link #notifyRecordingStarted(String)}
+     * @param inputId The ID of the TV input bound to the current TvRecordingClient.
+     * @see android.media.tv.TvRecordingClient.RecordingCallback#onDisconnected(String)
+     * @hide
+     */
+    public void notifyRecordingDisconnected(
+            @NonNull String recordingId, @NonNull String inputId) {
+        if (DEBUG) {
+            Log.d(TAG, "notifyRecordingDisconnected recordingId=" + recordingId
+                    + "; inputId=" + inputId);
+        }
+        if (mSession != null) {
+            mSession.notifyRecordingDisconnected(recordingId, inputId);
+        }
+    }
+
+    /**
+     * This is called to notify the corresponding interactive app service when the recording session
+     * has been tuned to the given channel and is ready to start recording.
+     *
+     * @param recordingId The ID of the related recording which is sent via
+     *                    {@link #notifyRecordingStarted(String)}
+     * @param channelUri The URI of the tuned channel.
+     * @see android.media.tv.TvRecordingClient.RecordingCallback#onTuned(Uri)
+     * @hide
+     */
+    public void notifyRecordingTuned(
+            @NonNull String recordingId, @NonNull Uri channelUri) {
+        if (DEBUG) {
+            Log.d(TAG, "notifyRecordingTuned recordingId=" + recordingId
+                    + "; channelUri=" + channelUri);
+        }
+        if (mSession != null) {
+            mSession.notifyRecordingTuned(recordingId, channelUri);
+        }
+    }
+
+    /**
+     * This is called to notify the corresponding interactive app service when an issue has
+     * occurred. It may be called at any time after the current recording session is created until
+     * it is released.
+     *
+     * @param recordingId The ID of the related recording which is sent via
+     *                    {@link #notifyRecordingStarted(String)}
+     * @param err The error code. Should be one of the following.
+     * <ul>
+     * <li>{@link TvInputManager#RECORDING_ERROR_UNKNOWN}
+     * <li>{@link TvInputManager#RECORDING_ERROR_INSUFFICIENT_SPACE}
+     * <li>{@link TvInputManager#RECORDING_ERROR_RESOURCE_BUSY}
+     * </ul>
+     * @see android.media.tv.TvRecordingClient.RecordingCallback#onError(int)
+     * @hide
+     */
+    public void notifyRecordingError(
+            @NonNull String recordingId, @TvInputManager.RecordingError int err) {
+        if (DEBUG) {
+            Log.d(TAG, "notifyRecordingError recordingId=" + recordingId
+                    + "; err=" + err);
+        }
+        if (mSession != null) {
+            mSession.notifyRecordingError(recordingId, err);
+        }
+    }
+
+    /**
+     * This is called to notify the corresponding interactive app service when the recording has
+     * been scheduled.
+     *
+     * @param recordingId The ID assigned to this recording by the app. It can be used to send
+     *                    recording related requests such as
+     *                    {@link TvInteractiveAppService.Session#requestStopRecording(String)}.
+     * @param requestId The ID of the request when requestScheduleRecording is called.
+     *                  {@code null} if the recording is not triggered by a request.
+     * @hide
+     */
+    public void notifyRecordingScheduled(
+            @NonNull String recordingId, @Nullable String requestId) {
+        if (DEBUG) {
+            Log.d(TAG, "notifyRecordingScheduled recordingId=" + recordingId
+                    + "; requestId=" + requestId);
+        }
+        if (mSession != null) {
+            mSession.notifyRecordingScheduled(recordingId, requestId);
+        }
+    }
+
     private void resetInternal() {
         mSessionCallback = null;
         if (mSession != null) {
@@ -1012,6 +1162,26 @@
         }
 
         /**
+         * This is called when {@link TvInteractiveAppService.Session#requestTimeShiftMode()} is
+         * called.
+         *
+         * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @hide
+         */
+        public void onRequestTimeShiftMode(@NonNull String iAppServiceId) {
+        }
+
+        /**
+         * This is called when {@link TvInteractiveAppService.Session#requestAvailableSpeeds()} is
+         * called.
+         *
+         * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @hide
+         */
+        public void onRequestAvailableSpeeds(@NonNull String iAppServiceId) {
+        }
+
+        /**
          * This is called when {@link TvInteractiveAppService.Session#requestStartRecording(Uri)}
          * is called.
          *
@@ -1041,6 +1211,50 @@
 
         /**
          * This is called when
+         * {@link TvInteractiveAppService.Session#requestScheduleRecording(String, Uri, Uri, Bundle)}
+         * is called.
+         *
+         * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @param inputId The ID of the TV input for the given channel.
+         * @param channelUri The URI of a channel to be recorded.
+         * @param programUri The URI of the TV program to be recorded.
+         * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
+         *            name, i.e. prefixed with a package name you own, so that different developers
+         *            will not create conflicting keys.
+         * @see android.media.tv.TvRecordingClient#tune(String, Uri, Bundle)
+         * @see android.media.tv.TvRecordingClient#startRecording(Uri)
+         * @hide
+         */
+        public void onRequestScheduleRecording(@NonNull String iAppServiceId,
+                @NonNull String inputId, @NonNull Uri channelUri, @NonNull Uri programUri,
+                @NonNull Bundle params) {
+        }
+
+        /**
+         * This is called when
+         * {@link TvInteractiveAppService.Session#requestScheduleRecording(String, Uri, long, long, int, Bundle)}
+         * is called.
+         *
+         * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @param inputId The ID of the TV input for the given channel.
+         * @param channelUri The URI of a channel to be recorded.
+         * @param startTime The start time of the recording in milliseconds since epoch.
+         * @param duration The duration of the recording in milliseconds.
+         * @param repeatDays The repeated days. 0 if not repeated.
+         * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
+         *            name, i.e. prefixed with a package name you own, so that different developers
+         *            will not create conflicting keys.
+         * @see android.media.tv.TvRecordingClient#tune(String, Uri, Bundle)
+         * @see android.media.tv.TvRecordingClient#startRecording(Uri)
+         * @hide
+         */
+        public void onRequestScheduleRecording(@NonNull String iAppServiceId,
+                @NonNull String inputId, @NonNull Uri channelUri, long startTime, long duration,
+                int repeatDays, @NonNull Bundle params) {
+        }
+
+        /**
+         * This is called when
          * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, byte[])} is
          * called.
          *
@@ -1453,6 +1667,34 @@
         }
 
         @Override
+        public void onRequestTimeShiftMode(Session session) {
+            if (DEBUG) {
+                Log.d(TAG, "onRequestTimeShiftMode");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onRequestTimeShiftMode - session not created");
+                return;
+            }
+            if (mCallback != null) {
+                mCallback.onRequestTimeShiftMode(mIAppServiceId);
+            }
+        }
+
+        @Override
+        public void onRequestAvailableSpeeds(Session session) {
+            if (DEBUG) {
+                Log.d(TAG, "onRequestAvailableSpeeds");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onRequestAvailableSpeeds - session not created");
+                return;
+            }
+            if (mCallback != null) {
+                mCallback.onRequestAvailableSpeeds(mIAppServiceId);
+            }
+        }
+
+        @Override
         public void onRequestStartRecording(Session session, Uri programUri) {
             if (DEBUG) {
                 Log.d(TAG, "onRequestStartRecording");
@@ -1496,6 +1738,22 @@
         }
 
         @Override
+        public void onRequestScheduleRecording(Session session, @NonNull String inputId,
+                @NonNull Uri channelUri, Uri progarmUri, @NonNull Bundle params) {
+            if (DEBUG) {
+                Log.d(TAG, "onRequestScheduleRecording");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onRequestScheduleRecording - session not created");
+                return;
+            }
+            if (mCallback != null) {
+                mCallback.onRequestScheduleRecording(mIAppServiceId, inputId, channelUri,
+                        progarmUri, params);
+            }
+        }
+
+        @Override
         public void onRequestTvRecordingInfo(Session session,
                 String recordingId) {
             if (DEBUG) {
@@ -1525,6 +1783,22 @@
             }
         }
 
+        public void onRequestScheduleRecording(Session session, @NonNull String inputId,
+                @NonNull Uri channelUri, long startTime, long duration, int repeatDays,
+                @NonNull Bundle params) {
+            if (DEBUG) {
+                Log.d(TAG, "onRequestScheduleRecording");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onRequestScheduleRecording - session not created");
+                return;
+            }
+            if (mCallback != null) {
+                mCallback.onRequestScheduleRecording(mIAppServiceId, inputId, channelUri, startTime,
+                        duration, repeatDays, params);
+            }
+        }
+
         @Override
         public void onRequestSigning(
                 Session session, String id, String algorithm, String alias, byte[] data) {
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 0928bef..404ca01 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -93,6 +93,7 @@
 import android.util.SparseArray;
 import android.view.InputChannel;
 import android.view.Surface;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
@@ -103,7 +104,9 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.IoThread;
 import com.android.server.SystemService;
+
 import dalvik.annotation.optimization.NeverCompile;
+
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
@@ -2081,6 +2084,26 @@
         }
 
         @Override
+        public void timeShiftSetMode(IBinder sessionToken, int mode, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "timeShiftSetMode");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
+                                .timeShiftSetMode(mode);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slog.e(TAG, "error in timeShiftSetMode", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void timeShiftEnablePositionTracking(IBinder sessionToken, boolean enable,
                 int userId) {
             final int callingUid = Binder.getCallingUid();
@@ -3654,6 +3677,57 @@
         }
 
         @Override
+        public void onCueingMessageAvailability(boolean available) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "onCueingMessageAvailability(" + available + ")");
+                }
+                if (mSessionState.session == null || mSessionState.client == null) {
+                    return;
+                }
+                try {
+                    mSessionState.client.onCueingMessageAvailability(available, mSessionState.seq);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error in onCueingMessageAvailability", e);
+                }
+            }
+        }
+
+        @Override
+        public void onTimeShiftMode(int mode) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "onTimeShiftMode(" + mode + ")");
+                }
+                if (mSessionState.session == null || mSessionState.client == null) {
+                    return;
+                }
+                try {
+                    mSessionState.client.onTimeShiftMode(mode, mSessionState.seq);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error in onTimeShiftMode", e);
+                }
+            }
+        }
+
+        @Override
+        public void onAvailableSpeeds(float[] speeds) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "onAvailableSpeeds(" + Arrays.toString(speeds) + ")");
+                }
+                if (mSessionState.session == null || mSessionState.client == null) {
+                    return;
+                }
+                try {
+                    mSessionState.client.onAvailableSpeeds(speeds, mSessionState.seq);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error in onAvailableSpeeds", e);
+                }
+            }
+        }
+
+        @Override
         public void onTuned(Uri channelUri) {
             synchronized (mLock) {
                 if (DEBUG) {
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index d4f2f2d..f829449 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -1486,6 +1486,56 @@
         }
 
         @Override
+        public void sendTimeShiftMode(IBinder sessionToken, int mode, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "sendTimeShiftMode(mode=%d)", mode);
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "sendTimeShiftMode");
+            SessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).sendTimeShiftMode(mode);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in sendTimeShiftMode", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void sendAvailableSpeeds(IBinder sessionToken, float[] speeds, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "sendAvailableSpeeds(speeds=%s)", Arrays.toString(speeds));
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "sendAvailableSpeeds");
+            SessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).sendAvailableSpeeds(speeds);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in sendAvailableSpeeds", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void sendTvRecordingInfo(IBinder sessionToken, TvRecordingInfo recordingInfo,
                 int userId) {
             if (DEBUG) {
@@ -1701,6 +1751,151 @@
         }
 
         @Override
+        public void notifyRecordingConnectionFailed(
+                IBinder sessionToken, String recordingId, String inputId, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "notifyRecordingConnectionFailed(recordingId=%s, inputId=%s)",
+                        recordingId, inputId);
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(
+                    Binder.getCallingPid(), callingUid, userId,
+                    "notifyRecordingConnectionFailed");
+            SessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState =
+                                getSessionStateLocked(sessionToken, callingUid, resolvedUserId);
+                        getSessionLocked(sessionState).notifyRecordingConnectionFailed(
+                                recordingId, inputId);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyRecordingConnectionFailed", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void notifyRecordingDisconnected(
+                IBinder sessionToken, String recordingId, String inputId, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "notifyRecordingDisconnected(recordingId=%s, inputId=%s)",
+                        recordingId, inputId);
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(
+                    Binder.getCallingPid(), callingUid, userId,
+                    "notifyRecordingDisconnected");
+            SessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState =
+                                getSessionStateLocked(sessionToken, callingUid, resolvedUserId);
+                        getSessionLocked(sessionState).notifyRecordingDisconnected(
+                                recordingId, inputId);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyRecordingDisconnected", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void notifyRecordingTuned(
+                IBinder sessionToken, String recordingId, Uri channelUri, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "notifyRecordingTuned(recordingId=%s, channelUri=%s)",
+                        recordingId, channelUri.toString());
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(
+                    Binder.getCallingPid(), callingUid, userId,
+                    "notifyRecordingTuned");
+            SessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState =
+                                getSessionStateLocked(sessionToken, callingUid, resolvedUserId);
+                        getSessionLocked(sessionState).notifyRecordingTuned(
+                                recordingId, channelUri);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyRecordingTuned", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void notifyRecordingError(
+                IBinder sessionToken, String recordingId, int err, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "notifyRecordingError(recordingId=%s, err=%d)",
+                        recordingId, err);
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(
+                    Binder.getCallingPid(), callingUid, userId,
+                    "notifyRecordingError");
+            SessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState =
+                                getSessionStateLocked(sessionToken, callingUid, resolvedUserId);
+                        getSessionLocked(sessionState).notifyRecordingError(
+                                recordingId, err);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyRecordingError", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void notifyRecordingScheduled(
+                IBinder sessionToken, String recordingId, String requestId, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "notifyRecordingScheduled(recordingId=%s, requestId=%s)",
+                        recordingId, requestId);
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(
+                    Binder.getCallingPid(), callingUid, userId,
+                    "notifyRecordingScheduled");
+            SessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState =
+                                getSessionStateLocked(sessionToken, callingUid, resolvedUserId);
+                        getSessionLocked(sessionState).notifyRecordingScheduled(
+                                recordingId, requestId);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyRecordingScheduled", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void setSurface(IBinder sessionToken, Surface surface, int userId) {
             final int callingUid = Binder.getCallingUid();
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
@@ -2602,6 +2797,40 @@
         }
 
         @Override
+        public void onRequestTimeShiftMode() {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onRequestTimeShiftMode");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onRequestTimeShiftMode(mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onRequestTimeShiftMode", e);
+                }
+            }
+        }
+
+        @Override
+        public void onRequestAvailableSpeeds() {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onRequestAvailableSpeeds");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onRequestAvailableSpeeds(mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onRequestAvailableSpeeds", e);
+                }
+            }
+        }
+
+        @Override
         public void onRequestStartRecording(Uri programUri) {
             synchronized (mLock) {
                 if (DEBUG) {
@@ -2636,6 +2865,44 @@
         }
 
         @Override
+        public void onRequestScheduleRecording(
+                String inputId, Uri channelUri, Uri programUri, Bundle params) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onRequestScheduleRecording");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onRequestScheduleRecording(
+                            inputId, channelUri, programUri, params, mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onRequestScheduleRecording", e);
+                }
+            }
+        }
+
+        @Override
+        public void onRequestScheduleRecording2(String inputId, Uri channelUri, long start,
+                long duration, int repeat, Bundle params) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onRequestScheduleRecording2");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onRequestScheduleRecording2(inputId, channelUri, start,
+                            duration, repeat, params, mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onRequestScheduleRecording2", e);
+                }
+            }
+        }
+
+        @Override
         public void onSetTvRecordingInfo(String recordingId, TvRecordingInfo recordingInfo) {
             synchronized (mLock) {
                 if (DEBUG) {