summaryrefslogtreecommitdiff
path: root/media/java
diff options
context:
space:
mode:
authorKyeongkab.Nam <Kyeongkab.Nam@sony.com>2020-12-18 17:23:13 +0900
committerHenry Fang <quxiangfang@google.com>2021-01-15 01:33:26 +0000
commit5c92bdb2d6ce6fb14f2ba8d6361abd7579dbabe2 (patch)
tree423cef7416409437c0945d9b78f48b0331834ef5 /media/java
parentda79da08f0145fc6b8ed0688067120db4cbf0839 (diff)
Add pause and resume recoding APIs
When a program is inserted during recoding(ex. Flash News), some applications want to pause that temporary. Add pause and resume recording feature so that it permits to tune on pause status while recoding. Bug: 172029807 Test: run cts -m CtsTvTestCases -t android.media.tv.cts.TvInputServiceTest Change-Id: Ib11ae5d3eddcd01dbd48efa58fb2d4011040b210 Merged-In: Ib11ae5d3eddcd01dbd48efa58fb2d4011040b210
Diffstat (limited to 'media/java')
-rw-r--r--media/java/android/media/tv/ITvInputManager.aidl2
-rw-r--r--media/java/android/media/tv/ITvInputSession.aidl2
-rw-r--r--media/java/android/media/tv/ITvInputSessionWrapper.java20
-rw-r--r--media/java/android/media/tv/TvInputInfo.java40
-rw-r--r--media/java/android/media/tv/TvInputManager.java34
-rwxr-xr-xmedia/java/android/media/tv/TvInputService.java38
-rw-r--r--media/java/android/media/tv/TvRecordingClient.java135
7 files changed, 263 insertions, 8 deletions
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index 1fbb67260895..5d7fdff70f5c 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -91,6 +91,8 @@ interface ITvInputManager {
// For the recording session
void startRecording(in IBinder sessionToken, in Uri programUri, in Bundle params, int userId);
void stopRecording(in IBinder sessionToken, int userId);
+ void pauseRecording(in IBinder sessionToken, in Bundle params, int userId);
+ void resumeRecording(in IBinder sessionToken, in Bundle params, int userId);
// For TV input hardware binding
List<TvInputHardwareInfo> getHardwareList();
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index 24b87d50b33e..158cf211d9f0 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -58,4 +58,6 @@ oneway interface ITvInputSession {
// For the recording session
void startRecording(in Uri programUri, in Bundle params);
void stopRecording();
+ void pauseRecording(in Bundle params);
+ void resumeRecording(in Bundle params);
}
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index e89d33d70d5c..abccf8da9cfc 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -68,6 +68,8 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
private static final int DO_TIME_SHIFT_ENABLE_POSITION_TRACKING = 19;
private static final int DO_START_RECORDING = 20;
private static final int DO_STOP_RECORDING = 21;
+ private static final int DO_PAUSE_RECORDING = 22;
+ private static final int DO_RESUME_RECORDING = 23;
private final boolean mIsRecordingSession;
private final HandlerCaller mCaller;
@@ -224,6 +226,14 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
mTvInputRecordingSessionImpl.stopRecording();
break;
}
+ case DO_PAUSE_RECORDING: {
+ mTvInputRecordingSessionImpl.pauseRecording((Bundle) msg.obj);
+ break;
+ }
+ case DO_RESUME_RECORDING: {
+ mTvInputRecordingSessionImpl.resumeRecording((Bundle) msg.obj);
+ break;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
break;
@@ -363,6 +373,16 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_STOP_RECORDING));
}
+ @Override
+ public void pauseRecording(@Nullable Bundle params) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_PAUSE_RECORDING, params));
+ }
+
+ @Override
+ public void resumeRecording(@Nullable Bundle params) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_RESUME_RECORDING, params));
+ }
+
private final class TvInputEventReceiver extends InputEventReceiver {
public TvInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 195ad5bc10f9..54cb2bff5566 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -143,6 +143,7 @@ public final class TvInputInfo implements Parcelable {
// Attributes from XML meta data.
private final String mSetupActivity;
private final boolean mCanRecord;
+ private final boolean mCanPauseRecording;
private final int mTunerCount;
// Attributes specific to HDMI
@@ -264,8 +265,8 @@ public final class TvInputInfo implements Parcelable {
private TvInputInfo(ResolveInfo service, String id, int type, boolean isHardwareInput,
CharSequence label, int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected,
- String setupActivity, boolean canRecord, int tunerCount, HdmiDeviceInfo hdmiDeviceInfo,
- boolean isConnectedToHdmiSwitch,
+ String setupActivity, boolean canRecord, boolean canPauseRecording, int tunerCount,
+ HdmiDeviceInfo hdmiDeviceInfo, boolean isConnectedToHdmiSwitch,
@HdmiAddressRelativePosition int hdmiConnectionRelativePosition, String parentId,
Bundle extras) {
mService = service;
@@ -279,6 +280,7 @@ public final class TvInputInfo implements Parcelable {
mIconDisconnected = iconDisconnected;
mSetupActivity = setupActivity;
mCanRecord = canRecord;
+ mCanPauseRecording = canPauseRecording;
mTunerCount = tunerCount;
mHdmiDeviceInfo = hdmiDeviceInfo;
mIsConnectedToHdmiSwitch = isConnectedToHdmiSwitch;
@@ -386,6 +388,14 @@ public final class TvInputInfo implements Parcelable {
}
/**
+ * Returns {@code true} if this TV input can pause recording TV programs,
+ * {@code false} otherwise.
+ */
+ public boolean canPauseRecording() {
+ return mCanPauseRecording;
+ }
+
+ /**
* Returns domain-specific extras associated with this TV input.
*/
public Bundle getExtras() {
@@ -571,6 +581,7 @@ public final class TvInputInfo implements Parcelable {
&& Objects.equals(mIconDisconnected, obj.mIconDisconnected)
&& TextUtils.equals(mSetupActivity, obj.mSetupActivity)
&& mCanRecord == obj.mCanRecord
+ && mCanPauseRecording == obj.mCanPauseRecording
&& mTunerCount == obj.mTunerCount
&& Objects.equals(mHdmiDeviceInfo, obj.mHdmiDeviceInfo)
&& mIsConnectedToHdmiSwitch == obj.mIsConnectedToHdmiSwitch
@@ -606,6 +617,7 @@ public final class TvInputInfo implements Parcelable {
dest.writeParcelable(mIconDisconnected, flags);
dest.writeString(mSetupActivity);
dest.writeByte(mCanRecord ? (byte) 1 : 0);
+ dest.writeByte(mCanPauseRecording ? (byte) 1 : 0);
dest.writeInt(mTunerCount);
dest.writeParcelable(mHdmiDeviceInfo, flags);
dest.writeByte(mIsConnectedToHdmiSwitch ? (byte) 1 : 0);
@@ -648,6 +660,7 @@ public final class TvInputInfo implements Parcelable {
mIconDisconnected = in.readParcelable(null);
mSetupActivity = in.readString();
mCanRecord = in.readByte() == 1;
+ mCanPauseRecording = in.readByte() == 1;
mTunerCount = in.readInt();
mHdmiDeviceInfo = in.readParcelable(null);
mIsConnectedToHdmiSwitch = in.readByte() == 1;
@@ -695,6 +708,7 @@ public final class TvInputInfo implements Parcelable {
private Icon mIconDisconnected;
private String mSetupActivity;
private Boolean mCanRecord;
+ private Boolean mCanPauseRecording;
private Integer mTunerCount;
private TvInputHardwareInfo mTvInputHardwareInfo;
private HdmiDeviceInfo mHdmiDeviceInfo;
@@ -879,6 +893,18 @@ public final class TvInputInfo implements Parcelable {
}
/**
+ * Sets whether this TV input can pause recording TV programs or not.
+ *
+ * @param canPauseRecording Whether this TV input can pause recording TV programs.
+ * @return This Builder object to allow for chaining of calls to builder methods.
+ */
+ @NonNull
+ public Builder setCanPauseRecording(boolean canPauseRecording) {
+ this.mCanPauseRecording = canPauseRecording;
+ return this;
+ }
+
+ /**
* Sets domain-specific extras associated with this TV input.
*
* @param extras Domain-specific extras associated with this TV input. Keys <em>must</em> be
@@ -927,7 +953,9 @@ public final class TvInputInfo implements Parcelable {
parseServiceMetadata(type);
return new TvInputInfo(mResolveInfo, id, type, isHardwareInput, mLabel, mLabelResId,
mIcon, mIconStandby, mIconDisconnected, mSetupActivity,
- mCanRecord == null ? false : mCanRecord, mTunerCount == null ? 0 : mTunerCount,
+ mCanRecord == null ? false : mCanRecord,
+ mCanPauseRecording == null ? false : mCanPauseRecording,
+ mTunerCount == null ? 0 : mTunerCount,
mHdmiDeviceInfo, isConnectedToHdmiSwitch, hdmiConnectionRelativePosition,
mParentId, mExtras);
}
@@ -997,6 +1025,12 @@ public final class TvInputInfo implements Parcelable {
mTunerCount = sa.getInt(
com.android.internal.R.styleable.TvInputService_tunerCount, 1);
}
+ if (mCanPauseRecording == null) {
+ mCanPauseRecording = sa.getBoolean(
+ com.android.internal.R.styleable.TvInputService_canPauseRecording,
+ false);
+ }
+
sa.recycle();
} catch (IOException | XmlPullParserException e) {
throw new IllegalStateException("Failed reading meta-data for " + si.packageName, e);
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 98a01a4cb449..6341dc263efd 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -2476,6 +2476,40 @@ public final class TvInputManager {
}
/**
+ * Pauses TV program recording in the current recording session.
+ *
+ * @param params A set of extra parameters which might be handled with this event.
+ */
+ void pauseRecording(@NonNull Bundle params) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.pauseRecording(mToken, params, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Resumes TV program recording in the current recording session.
+ *
+ * @param params A set of extra parameters which might be handled with this event.
+ */
+ void resumeRecording(@NonNull Bundle params) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.resumeRecording(mToken, params, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Calls {@link TvInputService.Session#appPrivateCommand(String, Bundle)
* TvInputService.Session.appPrivateCommand()} on the current TvView.
*
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index abbf4780bcc1..0fe9d504b951 100755
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -1852,6 +1852,28 @@ public abstract class TvInputService extends Service {
/**
+ * Called when the application requests to pause TV program recording. Recording must pause
+ * immediately when this method is called.
+ *
+ * If the pause request cannot be fulfilled, the session must call
+ * {@link #notifyError(int)}.
+ *
+ * @param params Domain-specific data for recording request.
+ */
+ public void onPauseRecording(@NonNull Bundle params) { }
+
+ /**
+ * Called when the application requests to resume TV program recording. Recording must
+ * resume immediately when this method is called.
+ *
+ * If the resume request cannot be fulfilled, the session must call
+ * {@link #notifyError(int)}.
+ *
+ * @param params Domain-specific data for recording request.
+ */
+ public void onResumeRecording(@NonNull Bundle params) { }
+
+ /**
* Called when the application requests to release all the resources held by this recording
* session.
*/
@@ -1903,6 +1925,22 @@ public abstract class TvInputService extends Service {
}
/**
+ * Calls {@link #onPauseRecording(Bundle)}.
+ *
+ */
+ void pauseRecording(@NonNull Bundle params) {
+ onPauseRecording(params);
+ }
+
+ /**
+ * Calls {@link #onResumeRecording(Bundle)}.
+ *
+ */
+ void resumeRecording(@NonNull Bundle params) {
+ onResumeRecording(params);
+ }
+
+ /**
* Calls {@link #onAppPrivateCommand(String, Bundle)}.
*/
void appPrivateCommand(String action, Bundle data) {
diff --git a/media/java/android/media/tv/TvRecordingClient.java b/media/java/android/media/tv/TvRecordingClient.java
index 23fadac8a72b..180e2bd6845b 100644
--- a/media/java/android/media/tv/TvRecordingClient.java
+++ b/media/java/android/media/tv/TvRecordingClient.java
@@ -30,6 +30,7 @@ import android.util.Log;
import android.util.Pair;
import java.util.ArrayDeque;
+import java.util.Objects;
import java.util.Queue;
/**
@@ -49,6 +50,8 @@ public class TvRecordingClient {
private boolean mIsRecordingStarted;
private boolean mIsTuned;
+ private boolean mIsPaused;
+ private boolean mIsRecordingStopping;
private final Queue<Pair<String, Bundle>> mPendingAppPrivateCommands = new ArrayDeque<>();
/**
@@ -113,17 +116,22 @@ public class TvRecordingClient {
if (TextUtils.isEmpty(inputId)) {
throw new IllegalArgumentException("inputId cannot be null or an empty string");
}
- if (mIsRecordingStarted) {
+ if (mIsRecordingStarted && !mIsPaused) {
throw new IllegalStateException("tune failed - recording already started");
}
if (mSessionCallback != null && TextUtils.equals(mSessionCallback.mInputId, inputId)) {
if (mSession != null) {
+ mSessionCallback.mChannelUri = channelUri;
mSession.tune(channelUri, params);
} else {
mSessionCallback.mChannelUri = channelUri;
mSessionCallback.mConnectionParams = params;
}
+ mIsTuned = false;
} else {
+ if (mIsPaused) {
+ throw new IllegalStateException("tune failed - inputId is changed during pause");
+ }
resetInternal();
mSessionCallback = new MySessionCallback(inputId, channelUri, params);
if (mTvInputManager != null) {
@@ -148,6 +156,8 @@ public class TvRecordingClient {
mSession.release();
mIsTuned = false;
mIsRecordingStarted = false;
+ mIsPaused = false;
+ mIsRecordingStopping = false;
mSession = null;
}
}
@@ -169,7 +179,8 @@ public class TvRecordingClient {
*
* @param programUri The URI for the TV program to record, built by
* {@link TvContract#buildProgramUri(long)}. Can be {@code null}.
- * @throws IllegalStateException If {@link #tune} request hasn't been handled yet.
+ * @throws IllegalStateException If {@link #tune} request hasn't been handled yet or during
+ * pause.
*/
public void startRecording(@Nullable Uri programUri) {
startRecording(programUri, Bundle.EMPTY);
@@ -195,11 +206,16 @@ public class TvRecordingClient {
* @param params Domain-specific data for this 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.
- * @throws IllegalStateException If {@link #tune} request hasn't been handled yet.
+ * @throws IllegalStateException If {@link #tune} request hasn't been handled yet or during
+ * pause.
*/
public void startRecording(@Nullable Uri programUri, @NonNull Bundle params) {
- if (!mIsTuned) {
- throw new IllegalStateException("startRecording failed - not yet tuned");
+ if (mIsRecordingStopping || !mIsTuned || mIsPaused) {
+ throw new IllegalStateException("startRecording failed -"
+ + "recording not yet stopped or not yet tuned or paused");
+ }
+ if (mIsRecordingStarted) {
+ Log.w(TAG, "startRecording failed - recording already started");
}
if (mSession != null) {
mSession.startRecording(programUri, params);
@@ -225,6 +241,103 @@ public class TvRecordingClient {
}
if (mSession != null) {
mSession.stopRecording();
+ if (mIsRecordingStarted) {
+ mIsRecordingStopping = true;
+ }
+ }
+ }
+
+ /**
+ * Pause TV program recording in the current recording session. Recording is expected to pause
+ * immediately when this method is called. If recording has not yet started in the current
+ * recording session, this method does nothing.
+ *
+ * <p>In pause status, the application can tune during recording. To continue recording,
+ * please call {@link TvRecordingClient#resumeRecording()} to resume instead of
+ * {@link TvRecordingClient#startRecording(Uri)}. Application can stop
+ * the recording with {@link TvRecordingClient#stopRecording()} in recording pause status.
+ *
+ * <p>If the pause request cannot be fulfilled, the recording session will respond by calling
+ * {@link RecordingCallback#onError(int)}.
+ */
+ public void pauseRecording() {
+ pauseRecording(Bundle.EMPTY);
+ }
+
+ /**
+ * Pause TV program recording in the current recording session. Recording is expected to pause
+ * immediately when this method is called. If recording has not yet started in the current
+ * recording session, this method does nothing.
+ *
+ * <p>In pause status, the application can tune during recording. To continue recording,
+ * please call {@link TvRecordingClient#resumeRecording()} to resume instead of
+ * {@link TvRecordingClient#startRecording(Uri)}. Application can stop
+ * the recording with {@link TvRecordingClient#stopRecording()} in recording pause status.
+ *
+ * <p>If the pause request cannot be fulfilled, the recording session will respond by calling
+ * {@link RecordingCallback#onError(int)}.
+ *
+ * @param params Domain-specific data for this request.
+ */
+ public void pauseRecording(@NonNull Bundle params) {
+ if (!mIsRecordingStarted || mIsRecordingStopping) {
+ throw new IllegalStateException(
+ "pauseRecording failed - recording not yet started or stopping");
+ }
+ TvInputInfo info = mTvInputManager.getTvInputInfo(mSessionCallback.mInputId);
+ if (info == null || !info.canPauseRecording()) {
+ throw new UnsupportedOperationException(
+ "pauseRecording failed - operation not supported");
+ }
+ if (mIsPaused) {
+ Log.w(TAG, "pauseRecording failed - recording already paused");
+ }
+ if (mSession != null) {
+ mSession.pauseRecording(params);
+ mIsPaused = true;
+ }
+ }
+
+ /**
+ * Resume TV program recording only in recording pause status in the current recording session.
+ * Recording is expected to resume immediately when this method is called. If recording has not
+ * yet paused in the current recording session, this method does nothing.
+ *
+ * <p>When record is resumed, the recording is continue and can not re-tune. Application can
+ * stop the recording with {@link TvRecordingClient#stopRecording()} after record resumed.
+ *
+ * <p>If the pause request cannot be fulfilled, the recording session will respond by calling
+ * {@link RecordingCallback#onError(int)}.
+ */
+ public void resumeRecording() {
+ resumeRecording(Bundle.EMPTY);
+ }
+
+ /**
+ * Resume TV program recording only in recording pause status in the current recording session.
+ * Recording is expected to resume immediately when this method is called. If recording has not
+ * yet paused in the current recording session, this method does nothing.
+ *
+ * <p>When record is resumed, the recording is continues and can not re-tune. Application can
+ * stop the recording with {@link TvRecordingClient#stopRecording()} after record resumed.
+ *
+ * <p>If the resume request cannot be fulfilled, the recording session will respond by calling
+ * {@link RecordingCallback#onError(int)}.
+ *
+ * @param params Domain-specific data for this request.
+ */
+ public void resumeRecording(@NonNull Bundle params) {
+ if (!mIsRecordingStarted || mIsRecordingStopping || !mIsTuned) {
+ throw new IllegalStateException(
+ "resumeRecording failed - recording not yet started or stopping or "
+ + "not yet tuned");
+ }
+ if (!mIsPaused) {
+ Log.w(TAG, "resumeRecording failed - recording not yet paused");
+ }
+ if (mSession != null) {
+ mSession.resumeRecording(params);
+ mIsPaused = false;
}
}
@@ -367,6 +480,10 @@ public class TvRecordingClient {
Log.w(TAG, "onTuned - session not created");
return;
}
+ if (mIsTuned || !Objects.equals(mChannelUri, channelUri)) {
+ Log.w(TAG, "onTuned - already tuned or not yet tuned to last channel");
+ return;
+ }
mIsTuned = true;
mCallback.onTuned(channelUri);
}
@@ -382,6 +499,8 @@ public class TvRecordingClient {
}
mIsTuned = false;
mIsRecordingStarted = false;
+ mIsPaused = false;
+ mIsRecordingStopping = false;
mSessionCallback = null;
mSession = null;
if (mCallback != null) {
@@ -398,7 +517,13 @@ public class TvRecordingClient {
Log.w(TAG, "onRecordingStopped - session not created");
return;
}
+ if (!mIsRecordingStarted) {
+ Log.w(TAG, "onRecordingStopped - recording not yet started");
+ return;
+ }
mIsRecordingStarted = false;
+ mIsPaused = false;
+ mIsRecordingStopping = false;
mCallback.onRecordingStopped(recordedProgramUri);
}