Merge "Add pause and resume recoding APIs"

This commit is contained in:
Henry Fang
2021-01-15 17:53:46 +00:00
committed by Gerrit Code Review
11 changed files with 319 additions and 8 deletions

View File

@@ -385,6 +385,7 @@ package android {
field public static final int calendarViewShown = 16843596; // 0x101034c
field public static final int calendarViewStyle = 16843613; // 0x101035d
field public static final int canControlMagnification = 16844039; // 0x1010507
field public static final int canPauseRecording = 16844311; // 0x1010617
field public static final int canPerformGestures = 16844045; // 0x101050d
field public static final int canRecord = 16844060; // 0x101051c
field @Deprecated public static final int canRequestEnhancedWebAccessibility = 16843736; // 0x10103d8
@@ -24265,6 +24266,7 @@ package android.media.tv {
}
public final class TvInputInfo implements android.os.Parcelable {
method public boolean canPauseRecording();
method public boolean canRecord();
method @Deprecated public android.content.Intent createSettingsIntent();
method public android.content.Intent createSetupIntent();
@@ -24298,6 +24300,7 @@ package android.media.tv {
public static final class TvInputInfo.Builder {
ctor public TvInputInfo.Builder(android.content.Context, android.content.ComponentName);
method public android.media.tv.TvInputInfo build();
method @NonNull public android.media.tv.TvInputInfo.Builder setCanPauseRecording(boolean);
method public android.media.tv.TvInputInfo.Builder setCanRecord(boolean);
method public android.media.tv.TvInputInfo.Builder setExtras(android.os.Bundle);
method public android.media.tv.TvInputInfo.Builder setTunerCount(int);
@@ -24389,7 +24392,9 @@ package android.media.tv {
method public void notifyRecordingStopped(android.net.Uri);
method public void notifyTuned(android.net.Uri);
method public void onAppPrivateCommand(@NonNull String, android.os.Bundle);
method public void onPauseRecording(@NonNull android.os.Bundle);
method public abstract void onRelease();
method public void onResumeRecording(@NonNull android.os.Bundle);
method public abstract void onStartRecording(@Nullable android.net.Uri);
method public void onStartRecording(@Nullable android.net.Uri, @NonNull android.os.Bundle);
method public abstract void onStopRecording();
@@ -24439,7 +24444,11 @@ package android.media.tv {
public class TvRecordingClient {
ctor public TvRecordingClient(android.content.Context, String, @NonNull android.media.tv.TvRecordingClient.RecordingCallback, android.os.Handler);
method public void pauseRecording();
method public void pauseRecording(@NonNull android.os.Bundle);
method public void release();
method public void resumeRecording();
method public void resumeRecording(@NonNull android.os.Bundle);
method public void sendAppPrivateCommand(@NonNull String, android.os.Bundle);
method public void startRecording(@Nullable android.net.Uri);
method public void startRecording(@Nullable android.net.Uri, @NonNull android.os.Bundle);

View File

@@ -8968,6 +8968,11 @@
changed at runtime by calling
{@link android.media.tv.TvInputManager#updateTvInputInfo(android.media.tv.TvInputInfo)}. -->
<attr name="tunerCount" format="integer" />
<!-- Attribute whether the TV input service can pause recording programs.
This value can be changed at runtime by calling
{@link android.media.tv.TvInputManager#updateTvInputInfo(android.media.tv.TvInputInfo)}
. -->
<attr name="canPauseRecording" format="boolean" />
</declare-styleable>
<!-- Attributes that can be used with <code>rating-system-definition</code> tags inside of the

View File

@@ -3043,6 +3043,7 @@
=============================================================== -->
<public-group type="attr" first-id="0x01010617">
<public name="canPauseRecording" />
<!-- attribute definitions go here -->
</public-group>

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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;
@@ -385,6 +387,14 @@ public final class TvInputInfo implements Parcelable {
return mCanRecord;
}
/**
* 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.
*/
@@ -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;
@@ -878,6 +892,18 @@ public final class TvInputInfo implements Parcelable {
return this;
}
/**
* 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.
*
@@ -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);

View File

@@ -2475,6 +2475,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.

View File

@@ -1851,6 +1851,28 @@ public abstract class TvInputService extends Service {
public abstract void onStopRecording();
/**
* 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.
@@ -1902,6 +1924,22 @@ public abstract class TvInputService extends Service {
onStopRecording();
}
/**
* 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)}.
*/

View File

@@ -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);
}

View File

@@ -20,6 +20,7 @@ import static android.media.AudioManager.DEVICE_NONE;
import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
@@ -1727,6 +1728,46 @@ public final class TvInputManagerService extends SystemService {
}
}
@Override
public void pauseRecording(IBinder sessionToken, @NonNull Bundle params, int userId) {
final int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
userId, "pauseRecording");
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
getSessionLocked(sessionToken, callingUid, resolvedUserId)
.pauseRecording(params);
} catch (RemoteException | SessionNotFoundException e) {
Slog.e(TAG, "error in pauseRecording", e);
}
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public void resumeRecording(IBinder sessionToken, @NonNull Bundle params, int userId) {
final int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
userId, "resumeRecording");
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
getSessionLocked(sessionToken, callingUid, resolvedUserId)
.resumeRecording(params);
} catch (RemoteException | SessionNotFoundException e) {
Slog.e(TAG, "error in resumeRecording", e);
}
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)