diff options
Diffstat (limited to 'android/media/tv/TvRecordingClient.java')
-rw-r--r-- | android/media/tv/TvRecordingClient.java | 405 |
1 files changed, 405 insertions, 0 deletions
diff --git a/android/media/tv/TvRecordingClient.java b/android/media/tv/TvRecordingClient.java new file mode 100644 index 00000000..5aadeb6e --- /dev/null +++ b/android/media/tv/TvRecordingClient.java @@ -0,0 +1,405 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.Context; +import android.media.tv.TvInputManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; + +import java.util.ArrayDeque; +import java.util.Queue; + +/** + * The public interface object used to interact with a specific TV input service for TV program + * recording. + */ +public class TvRecordingClient { + private static final String TAG = "TvRecordingClient"; + private static final boolean DEBUG = false; + + private final RecordingCallback mCallback; + private final Handler mHandler; + + private final TvInputManager mTvInputManager; + private TvInputManager.Session mSession; + private MySessionCallback mSessionCallback; + + private boolean mIsRecordingStarted; + private boolean mIsTuned; + private final Queue<Pair<String, Bundle>> mPendingAppPrivateCommands = new ArrayDeque<>(); + + /** + * Creates a new TvRecordingClient object. + * + * @param context The application context to create a TvRecordingClient with. + * @param tag A short name for debugging purposes. + * @param callback The callback to receive recording status changes. + * @param handler The handler to invoke the callback on. + */ + public TvRecordingClient(Context context, String tag, @NonNull RecordingCallback callback, + Handler handler) { + mCallback = callback; + mHandler = handler == null ? new Handler(Looper.getMainLooper()) : handler; + mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE); + } + + /** + * 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 + * session, this method throws an exception. + * + * <p>The application may call this method before starting or after stopping recording, but not + * during recording. + * + * <p>The recording session will respond by calling + * {@link RecordingCallback#onTuned(Uri)} if the tune request was fulfilled, or + * {@link RecordingCallback#onError(int)} otherwise. + * + * @param inputId The ID of the TV input for the given channel. + * @param channelUri The URI of a channel. + * @throws IllegalStateException If recording is already started. + */ + public void tune(String inputId, Uri channelUri) { + tune(inputId, channelUri, null); + } + + /** + * 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 + * session, this method throws an exception. This can be used to provide domain-specific + * features that are only known between certain client and their TV inputs. + * + * <p>The application may call this method before starting or after stopping recording, but not + * during recording. + * + * <p>The recording session will respond by calling + * {@link RecordingCallback#onTuned(Uri)} if the tune request was fulfilled, or + * {@link RecordingCallback#onError(int)} otherwise. + * + * @param inputId The ID of the TV input for the given channel. + * @param channelUri The URI of a channel. + * @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. + * @throws IllegalStateException If recording is already started. + */ + public void tune(String inputId, Uri channelUri, Bundle params) { + if (DEBUG) Log.d(TAG, "tune(" + channelUri + ")"); + if (TextUtils.isEmpty(inputId)) { + throw new IllegalArgumentException("inputId cannot be null or an empty string"); + } + if (mIsRecordingStarted) { + throw new IllegalStateException("tune failed - recording already started"); + } + if (mSessionCallback != null && TextUtils.equals(mSessionCallback.mInputId, inputId)) { + if (mSession != null) { + mSession.tune(channelUri, params); + } else { + mSessionCallback.mChannelUri = channelUri; + mSessionCallback.mConnectionParams = params; + } + } else { + resetInternal(); + mSessionCallback = new MySessionCallback(inputId, channelUri, params); + if (mTvInputManager != null) { + mTvInputManager.createRecordingSession(inputId, mSessionCallback, mHandler); + } + } + } + + /** + * Releases the resources in the current recording session immediately. This may be called at + * any time, however if the session is already released, it does nothing. + */ + public void release() { + if (DEBUG) Log.d(TAG, "release()"); + resetInternal(); + } + + private void resetInternal() { + mSessionCallback = null; + mPendingAppPrivateCommands.clear(); + if (mSession != null) { + mSession.release(); + mSession = null; + } + } + + /** + * Starts TV program recording in the current recording session. Recording is expected to start + * immediately when this method is called. If the current recording session has not yet tuned to + * any channel, this method throws an exception. + * + * <p>The application may supply the URI for a TV program for filling in program specific data + * fields in the {@link android.media.tv.TvContract.RecordedPrograms} table. + * A non-null {@code programUri} implies the started recording should be of that specific + * program, whereas null {@code programUri} does not impose such a requirement and the + * recording can span across multiple TV programs. In either case, the application must call + * {@link TvRecordingClient#stopRecording()} to stop the recording. + * + * <p>The recording session will respond by calling {@link RecordingCallback#onError(int)} if + * the start request cannot be fulfilled. + * + * @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. + */ + public void startRecording(@Nullable Uri programUri) { + if (!mIsTuned) { + throw new IllegalStateException("startRecording failed - not yet tuned"); + } + if (mSession != null) { + mSession.startRecording(programUri); + mIsRecordingStarted = true; + } + } + + /** + * Stops TV program recording in the current recording session. Recording is expected to stop + * immediately when this method is called. If recording has not yet started in the current + * recording session, this method does nothing. + * + * <p>The recording session is expected to create a new data entry in the + * {@link android.media.tv.TvContract.RecordedPrograms} table that describes the newly + * recorded program and pass the URI to that entry through to + * {@link RecordingCallback#onRecordingStopped(Uri)}. + * If the stop request cannot be fulfilled, the recording session will respond by calling + * {@link RecordingCallback#onError(int)}. + */ + public void stopRecording() { + if (!mIsRecordingStarted) { + Log.w(TAG, "stopRecording failed - recording not yet started"); + } + if (mSession != null) { + mSession.stopRecording(); + } + } + + /** + * Sends a private command to the underlying TV input. This can be used to provide + * domain-specific features that are only known between certain clients and their TV inputs. + * + * @param action The name of the private command to send. This <em>must</em> be a scoped name, + * i.e. prefixed with a package name you own, so that different developers will not + * create conflicting commands. + * @param data An optional bundle to send with the command. + */ + public void sendAppPrivateCommand(@NonNull String action, Bundle data) { + if (TextUtils.isEmpty(action)) { + throw new IllegalArgumentException("action cannot be null or an empty string"); + } + if (mSession != null) { + mSession.sendAppPrivateCommand(action, data); + } else { + Log.w(TAG, "sendAppPrivateCommand - session not yet created (action \"" + action + + "\" pending)"); + mPendingAppPrivateCommands.add(Pair.create(action, data)); + } + } + + /** + * Callback used to receive various status updates on the + * {@link android.media.tv.TvInputService.RecordingSession} + */ + public abstract static class RecordingCallback { + /** + * This is called when an error occurred while establishing a connection to the recording + * session for the corresponding TV input. + * + * @param inputId The ID of the TV input bound to the current TvRecordingClient. + */ + public void onConnectionFailed(String inputId) { + } + + /** + * This is called when the connection to the current recording session is lost. + * + * @param inputId The ID of the TV input bound to the current TvRecordingClient. + */ + public void onDisconnected(String inputId) { + } + + /** + * This is called when the recording session has been tuned to the given channel and is + * ready to start recording. + * + * @param channelUri The URI of a channel. + */ + public void onTuned(Uri channelUri) { + } + + /** + * This is called when the current recording session has stopped recording and created a + * new data entry in the {@link TvContract.RecordedPrograms} table that describes the newly + * recorded program. + * + * @param recordedProgramUri The URI for the newly recorded program. + */ + public void onRecordingStopped(Uri recordedProgramUri) { + } + + /** + * 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 error The error code. Should be one of the followings. + * <ul> + * <li>{@link TvInputManager#RECORDING_ERROR_UNKNOWN} + * <li>{@link TvInputManager#RECORDING_ERROR_INSUFFICIENT_SPACE} + * <li>{@link TvInputManager#RECORDING_ERROR_RESOURCE_BUSY} + * </ul> + */ + public void onError(@TvInputManager.RecordingError int error) { + } + + /** + * This is invoked when a custom event from the bound TV input is sent to this client. + * + * @param inputId The ID of the TV input bound to this client. + * @param eventType The type of the event. + * @param eventArgs Optional arguments of the event. + * @hide + */ + @SystemApi + public void onEvent(String inputId, String eventType, Bundle eventArgs) { + } + } + + private class MySessionCallback extends TvInputManager.SessionCallback { + final String mInputId; + Uri mChannelUri; + Bundle mConnectionParams; + + MySessionCallback(String inputId, Uri channelUri, Bundle connectionParams) { + mInputId = inputId; + mChannelUri = channelUri; + mConnectionParams = connectionParams; + } + + @Override + public void onSessionCreated(TvInputManager.Session session) { + if (DEBUG) { + Log.d(TAG, "onSessionCreated()"); + } + if (this != mSessionCallback) { + Log.w(TAG, "onSessionCreated - session already created"); + // This callback is obsolete. + if (session != null) { + session.release(); + } + return; + } + mSession = session; + if (session != null) { + // Sends the pending app private commands. + for (Pair<String, Bundle> command : mPendingAppPrivateCommands) { + mSession.sendAppPrivateCommand(command.first, command.second); + } + mPendingAppPrivateCommands.clear(); + mSession.tune(mChannelUri, mConnectionParams); + } else { + mSessionCallback = null; + if (mCallback != null) { + mCallback.onConnectionFailed(mInputId); + } + } + } + + @Override + void onTuned(TvInputManager.Session session, Uri channelUri) { + if (DEBUG) { + Log.d(TAG, "onTuned()"); + } + if (this != mSessionCallback) { + Log.w(TAG, "onTuned - session not created"); + return; + } + mIsTuned = true; + mCallback.onTuned(channelUri); + } + + @Override + public void onSessionReleased(TvInputManager.Session session) { + if (DEBUG) { + Log.d(TAG, "onSessionReleased()"); + } + if (this != mSessionCallback) { + Log.w(TAG, "onSessionReleased - session not created"); + return; + } + mIsTuned = false; + mIsRecordingStarted = false; + mSessionCallback = null; + mSession = null; + if (mCallback != null) { + mCallback.onDisconnected(mInputId); + } + } + + @Override + public void onRecordingStopped(TvInputManager.Session session, Uri recordedProgramUri) { + if (DEBUG) { + Log.d(TAG, "onRecordingStopped(recordedProgramUri= " + recordedProgramUri + ")"); + } + if (this != mSessionCallback) { + Log.w(TAG, "onRecordingStopped - session not created"); + return; + } + mIsRecordingStarted = false; + mCallback.onRecordingStopped(recordedProgramUri); + } + + @Override + public void onError(TvInputManager.Session session, int error) { + if (DEBUG) { + Log.d(TAG, "onError(error=" + error + ")"); + } + if (this != mSessionCallback) { + Log.w(TAG, "onError - session not created"); + return; + } + mCallback.onError(error); + } + + @Override + public void onSessionEvent(TvInputManager.Session session, String eventType, + Bundle eventArgs) { + if (DEBUG) { + Log.d(TAG, "onSessionEvent(eventType=" + eventType + ", eventArgs=" + eventArgs + + ")"); + } + if (this != mSessionCallback) { + Log.w(TAG, "onSessionEvent - session not created"); + return; + } + if (mCallback != null) { + mCallback.onEvent(mInputId, eventType, eventArgs); + } + } + } +} |