1a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo/*
2a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo * Copyright (C) 2016 The Android Open Source Project
3a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo *
4a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo * Licensed under the Apache License, Version 2.0 (the "License");
5a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo * you may not use this file except in compliance with the License.
6a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo * You may obtain a copy of the License at
7a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo *
8a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo *      http://www.apache.org/licenses/LICENSE-2.0
9a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo *
10a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo * Unless required by applicable law or agreed to in writing, software
11a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo * distributed under the License is distributed on an "AS IS" BASIS,
12a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo * See the License for the specific language governing permissions and
14a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo * limitations under the License.
15a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo */
16a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
17a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seopackage android.media.tv;
18a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
19a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seoimport android.annotation.NonNull;
204eee6a73e476cd2d82a69f3a535628901047f140Jae Seoimport android.annotation.Nullable;
21a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seoimport android.annotation.SystemApi;
22a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seoimport android.content.Context;
23e3c11e842937f50f54c9d82363f33338dc9e261bJae Seoimport android.media.tv.TvInputManager;
24a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seoimport android.net.Uri;
25a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seoimport android.os.Bundle;
26a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seoimport android.os.Handler;
27a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seoimport android.os.Looper;
28a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seoimport android.text.TextUtils;
29a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seoimport android.util.Log;
30a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seoimport android.util.Pair;
31a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
32a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seoimport java.util.ArrayDeque;
33a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seoimport java.util.Queue;
34a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
35a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo/**
36a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo * The public interface object used to interact with a specific TV input service for TV program
37a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo * recording.
38a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo */
39a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seopublic class TvRecordingClient {
40a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    private static final String TAG = "TvRecordingClient";
41a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    private static final boolean DEBUG = false;
42a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
43a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    private final RecordingCallback mCallback;
44a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    private final Handler mHandler;
45a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
46a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    private final TvInputManager mTvInputManager;
47a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    private TvInputManager.Session mSession;
48a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    private MySessionCallback mSessionCallback;
49a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
50e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo    private boolean mIsRecordingStarted;
51e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo    private boolean mIsTuned;
52a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    private final Queue<Pair<String, Bundle>> mPendingAppPrivateCommands = new ArrayDeque<>();
53a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
54a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    /**
55a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     * Creates a new TvRecordingClient object.
56a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     *
57e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * @param context The application context to create a TvRecordingClient with.
58a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     * @param tag A short name for debugging purposes.
59a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     * @param callback The callback to receive recording status changes.
60a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     * @param handler The handler to invoke the callback on.
61a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     */
62a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    public TvRecordingClient(Context context, String tag, @NonNull RecordingCallback callback,
63a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            Handler handler) {
64a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        mCallback = callback;
65a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        mHandler = handler == null ? new Handler(Looper.getMainLooper()) : handler;
66a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
67a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    }
68a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
69a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    /**
70e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * Tunes to a given channel for TV program recording. The first tune request will create a new
7125c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo     * recording session for the corresponding TV input and establish a connection between the
72e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * application and the session. If recording has already started in the current recording
73e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * session, this method throws an exception.
74e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     *
75e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * <p>The application may call this method before starting or after stopping recording, but not
76e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * during recording.
77a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     *
78a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     * <p>The recording session will respond by calling
79b55c7517ba4b2c2959a0bc4d37536e7e3c8283c9Dongwon Kang     * {@link RecordingCallback#onTuned(Uri)} if the tune request was fulfilled, or
80e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * {@link RecordingCallback#onError(int)} otherwise.
81a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     *
82a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     * @param inputId The ID of the TV input for the given channel.
83a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     * @param channelUri The URI of a channel.
84b55c7517ba4b2c2959a0bc4d37536e7e3c8283c9Dongwon Kang     * @throws IllegalStateException If recording is already started.
85a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     */
86e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo    public void tune(String inputId, Uri channelUri) {
87e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo        tune(inputId, channelUri, null);
88a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    }
89a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
90a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    /**
91e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * Tunes to a given channel for TV program recording. The first tune request will create a new
9225c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo     * recording session for the corresponding TV input and establish a connection between the
93e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * application and the session. If recording has already started in the current recording
94d48d029da43babf265fccbf5d84a06b4b275f72cJiabin     * session, this method throws an exception. This can be used to provide domain-specific
95d48d029da43babf265fccbf5d84a06b4b275f72cJiabin     * features that are only known between certain client and their TV inputs.
96e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     *
97e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * <p>The application may call this method before starting or after stopping recording, but not
98e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * during recording.
99a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     *
100a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     * <p>The recording session will respond by calling
101d48d029da43babf265fccbf5d84a06b4b275f72cJiabin     * {@link RecordingCallback#onTuned(Uri)} if the tune request was fulfilled, or
102e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * {@link RecordingCallback#onError(int)} otherwise.
103a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     *
104a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     * @param inputId The ID of the TV input for the given channel.
105a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     * @param channelUri The URI of a channel.
106d48d029da43babf265fccbf5d84a06b4b275f72cJiabin     * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
107d48d029da43babf265fccbf5d84a06b4b275f72cJiabin     *            name, i.e. prefixed with a package name you own, so that different developers will
108d48d029da43babf265fccbf5d84a06b4b275f72cJiabin     *            not create conflicting keys.
109b55c7517ba4b2c2959a0bc4d37536e7e3c8283c9Dongwon Kang     * @throws IllegalStateException If recording is already started.
110a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     */
111e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo    public void tune(String inputId, Uri channelUri, Bundle params) {
112e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo        if (DEBUG) Log.d(TAG, "tune(" + channelUri + ")");
113a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        if (TextUtils.isEmpty(inputId)) {
114a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            throw new IllegalArgumentException("inputId cannot be null or an empty string");
115a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        }
116e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo        if (mIsRecordingStarted) {
117e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo            throw new IllegalStateException("tune failed - recording already started");
118e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo        }
119a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        if (mSessionCallback != null && TextUtils.equals(mSessionCallback.mInputId, inputId)) {
120a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            if (mSession != null) {
121e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo                mSession.tune(channelUri, params);
122a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            } else {
123a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                mSessionCallback.mChannelUri = channelUri;
124a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                mSessionCallback.mConnectionParams = params;
125a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            }
126a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        } else {
127a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            resetInternal();
128a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            mSessionCallback = new MySessionCallback(inputId, channelUri, params);
129a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            if (mTvInputManager != null) {
130a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                mTvInputManager.createRecordingSession(inputId, mSessionCallback, mHandler);
131a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            }
132a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        }
133a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    }
134a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
135a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    /**
136e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * Releases the resources in the current recording session immediately. This may be called at
137e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * any time, however if the session is already released, it does nothing.
138a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     */
139e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo    public void release() {
140e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo        if (DEBUG) Log.d(TAG, "release()");
141a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        resetInternal();
142a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    }
143a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
144a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    private void resetInternal() {
145a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        mSessionCallback = null;
146a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        mPendingAppPrivateCommands.clear();
147a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        if (mSession != null) {
148a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            mSession.release();
149a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            mSession = null;
150a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        }
151a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    }
152a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
153a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    /**
154e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * Starts TV program recording in the current recording session. Recording is expected to start
155e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * immediately when this method is called. If the current recording session has not yet tuned to
156e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * any channel, this method throws an exception.
157a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     *
158b55c7517ba4b2c2959a0bc4d37536e7e3c8283c9Dongwon Kang     * <p>The application may supply the URI for a TV program for filling in program specific data
159b55c7517ba4b2c2959a0bc4d37536e7e3c8283c9Dongwon Kang     * fields in the {@link android.media.tv.TvContract.RecordedPrograms} table.
1600cb5244e52590214ddc16dd5fc1030b5baf04726Dongwon Kang     * A non-null {@code programUri} implies the started recording should be of that specific
161308fe5741f05062d20664afb40e9735cf3458510Dongwon Kang     * program, whereas null {@code programUri} does not impose such a requirement and the
162e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * recording can span across multiple TV programs. In either case, the application must call
163e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * {@link TvRecordingClient#stopRecording()} to stop the recording.
1644eee6a73e476cd2d82a69f3a535628901047f140Jae Seo     *
165e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * <p>The recording session will respond by calling {@link RecordingCallback#onError(int)} if
166e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * the start request cannot be fulfilled.
1674eee6a73e476cd2d82a69f3a535628901047f140Jae Seo     *
168b55c7517ba4b2c2959a0bc4d37536e7e3c8283c9Dongwon Kang     * @param programUri The URI for the TV program to record, built by
169e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     *            {@link TvContract#buildProgramUri(long)}. Can be {@code null}.
170b55c7517ba4b2c2959a0bc4d37536e7e3c8283c9Dongwon Kang     * @throws IllegalStateException If {@link #tune} request hasn't been handled yet.
171a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     */
172b55c7517ba4b2c2959a0bc4d37536e7e3c8283c9Dongwon Kang    public void startRecording(@Nullable Uri programUri) {
173e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo        if (!mIsTuned) {
174e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo            throw new IllegalStateException("startRecording failed - not yet tuned");
175e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo        }
176a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        if (mSession != null) {
177b55c7517ba4b2c2959a0bc4d37536e7e3c8283c9Dongwon Kang            mSession.startRecording(programUri);
178e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo            mIsRecordingStarted = true;
179a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        }
180a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    }
181a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
182a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    /**
183e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * Stops TV program recording in the current recording session. Recording is expected to stop
184e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * immediately when this method is called. If recording has not yet started in the current
185e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * recording session, this method does nothing.
186a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     *
187e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * <p>The recording session is expected to create a new data entry in the
188e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * {@link android.media.tv.TvContract.RecordedPrograms} table that describes the newly
189e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * recorded program and pass the URI to that entry through to
190e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * {@link RecordingCallback#onRecordingStopped(Uri)}.
191e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * If the stop request cannot be fulfilled, the recording session will respond by calling
192e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo     * {@link RecordingCallback#onError(int)}.
193a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     */
194a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    public void stopRecording() {
195e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo        if (!mIsRecordingStarted) {
196e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo            Log.w(TAG, "stopRecording failed - recording not yet started");
197e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo        }
198a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        if (mSession != null) {
199a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            mSession.stopRecording();
200a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        }
201a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    }
202a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
203a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    /**
204f714e62c12d99d816d70d09da60b6885a1368cefDongwon Kang     * Sends a private command to the underlying TV input. This can be used to provide
205f714e62c12d99d816d70d09da60b6885a1368cefDongwon Kang     * domain-specific features that are only known between certain clients and their TV inputs.
206a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     *
207a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     * @param action The name of the private command to send. This <em>must</em> be a scoped name,
208a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     *            i.e. prefixed with a package name you own, so that different developers will not
209a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     *            create conflicting commands.
210a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     * @param data An optional bundle to send with the command.
211a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     */
212a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    public void sendAppPrivateCommand(@NonNull String action, Bundle data) {
213a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        if (TextUtils.isEmpty(action)) {
214a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            throw new IllegalArgumentException("action cannot be null or an empty string");
215a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        }
216a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        if (mSession != null) {
217a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            mSession.sendAppPrivateCommand(action, data);
218a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        } else {
219a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            Log.w(TAG, "sendAppPrivateCommand - session not yet created (action \"" + action
220a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                    + "\" pending)");
221a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            mPendingAppPrivateCommands.add(Pair.create(action, data));
222a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        }
223a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    }
224a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
225a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    /**
226a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     * Callback used to receive various status updates on the
227a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     * {@link android.media.tv.TvInputService.RecordingSession}
228a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo     */
229397b1447ab4533f8cb87512689524f8d860bb0a5Dongwon Kang    public abstract static class RecordingCallback {
230a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        /**
23125c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo         * This is called when an error occurred while establishing a connection to the recording
23225c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo         * session for the corresponding TV input.
23325c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo         *
23425c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo         * @param inputId The ID of the TV input bound to the current TvRecordingClient.
23525c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo         */
23625c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo        public void onConnectionFailed(String inputId) {
23725c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo        }
23825c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo
23925c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo        /**
24025c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo         * This is called when the connection to the current recording session is lost.
24125c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo         *
24225c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo         * @param inputId The ID of the TV input bound to the current TvRecordingClient.
24325c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo         */
24425c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo        public void onDisconnected(String inputId) {
24525c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo        }
24625c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo
24725c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo        /**
248e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo         * This is called when the recording session has been tuned to the given channel and is
249e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo         * ready to start recording.
250b55c7517ba4b2c2959a0bc4d37536e7e3c8283c9Dongwon Kang         *
251b55c7517ba4b2c2959a0bc4d37536e7e3c8283c9Dongwon Kang         * @param channelUri The URI of a channel.
252a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo         */
253b55c7517ba4b2c2959a0bc4d37536e7e3c8283c9Dongwon Kang        public void onTuned(Uri channelUri) {
254a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        }
255a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
256a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        /**
257e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo         * This is called when the current recording session has stopped recording and created a
258e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo         * new data entry in the {@link TvContract.RecordedPrograms} table that describes the newly
259e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo         * recorded program.
260a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo         *
261e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo         * @param recordedProgramUri The URI for the newly recorded program.
262a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo         */
263a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        public void onRecordingStopped(Uri recordedProgramUri) {
264a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        }
265a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
266a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        /**
267e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo         * This is called when an issue has occurred. It may be called at any time after the current
268e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo         * recording session is created until it is released.
269a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo         *
270a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo         * @param error The error code. Should be one of the followings.
271a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo         * <ul>
272a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo         * <li>{@link TvInputManager#RECORDING_ERROR_UNKNOWN}
273a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo         * <li>{@link TvInputManager#RECORDING_ERROR_INSUFFICIENT_SPACE}
274a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo         * <li>{@link TvInputManager#RECORDING_ERROR_RESOURCE_BUSY}
275a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo         * </ul>
276a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo         */
277a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        public void onError(@TvInputManager.RecordingError int error) {
278a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        }
279a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
280a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        /**
281a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo         * This is invoked when a custom event from the bound TV input is sent to this client.
282a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo         *
283a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo         * @param inputId The ID of the TV input bound to this client.
284a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo         * @param eventType The type of the event.
285a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo         * @param eventArgs Optional arguments of the event.
286a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo         * @hide
287a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo         */
288a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        @SystemApi
289a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        public void onEvent(String inputId, String eventType, Bundle eventArgs) {
290a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        }
291a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    }
292a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
293a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    private class MySessionCallback extends TvInputManager.SessionCallback {
294a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        final String mInputId;
295a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        Uri mChannelUri;
296a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        Bundle mConnectionParams;
297a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
298a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        MySessionCallback(String inputId, Uri channelUri, Bundle connectionParams) {
299a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            mInputId = inputId;
300a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            mChannelUri = channelUri;
301a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            mConnectionParams = connectionParams;
302a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        }
303a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
304a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        @Override
305a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        public void onSessionCreated(TvInputManager.Session session) {
306a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            if (DEBUG) {
307a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                Log.d(TAG, "onSessionCreated()");
308a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            }
309a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            if (this != mSessionCallback) {
310a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                Log.w(TAG, "onSessionCreated - session already created");
311a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                // This callback is obsolete.
312a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                if (session != null) {
313a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                    session.release();
314a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                }
315a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                return;
316a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            }
317a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            mSession = session;
318a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            if (session != null) {
319a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                // Sends the pending app private commands.
320a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                for (Pair<String, Bundle> command : mPendingAppPrivateCommands) {
321a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                    mSession.sendAppPrivateCommand(command.first, command.second);
322a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                }
323a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                mPendingAppPrivateCommands.clear();
324e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo                mSession.tune(mChannelUri, mConnectionParams);
325a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            } else {
326a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                mSessionCallback = null;
32725c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo                if (mCallback != null) {
32825c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo                    mCallback.onConnectionFailed(mInputId);
32925c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo                }
330a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            }
331a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        }
332a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
333a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        @Override
334b55c7517ba4b2c2959a0bc4d37536e7e3c8283c9Dongwon Kang        void onTuned(TvInputManager.Session session, Uri channelUri) {
33582621b6c09bc2e76e9fd52b921cfd2f5166c0945Youngsang Cho            if (DEBUG) {
336e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo                Log.d(TAG, "onTuned()");
33782621b6c09bc2e76e9fd52b921cfd2f5166c0945Youngsang Cho            }
33882621b6c09bc2e76e9fd52b921cfd2f5166c0945Youngsang Cho            if (this != mSessionCallback) {
339e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo                Log.w(TAG, "onTuned - session not created");
34082621b6c09bc2e76e9fd52b921cfd2f5166c0945Youngsang Cho                return;
34182621b6c09bc2e76e9fd52b921cfd2f5166c0945Youngsang Cho            }
342e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo            mIsTuned = true;
343b55c7517ba4b2c2959a0bc4d37536e7e3c8283c9Dongwon Kang            mCallback.onTuned(channelUri);
34482621b6c09bc2e76e9fd52b921cfd2f5166c0945Youngsang Cho        }
34582621b6c09bc2e76e9fd52b921cfd2f5166c0945Youngsang Cho
34682621b6c09bc2e76e9fd52b921cfd2f5166c0945Youngsang Cho        @Override
347a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        public void onSessionReleased(TvInputManager.Session session) {
348a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            if (DEBUG) {
349a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                Log.d(TAG, "onSessionReleased()");
350a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            }
351a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            if (this != mSessionCallback) {
352a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                Log.w(TAG, "onSessionReleased - session not created");
353a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                return;
354a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            }
355e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo            mIsTuned = false;
356e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo            mIsRecordingStarted = false;
35725c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo            mSessionCallback = null;
35825c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo            mSession = null;
35925c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo            if (mCallback != null) {
36025c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo                mCallback.onDisconnected(mInputId);
36125c9c5edab42d6c9e9e0469ab04fb7ff87704d1cJae Seo            }
362a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        }
363a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
364a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        @Override
365a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        public void onRecordingStopped(TvInputManager.Session session, Uri recordedProgramUri) {
366a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            if (DEBUG) {
3674eee6a73e476cd2d82a69f3a535628901047f140Jae Seo                Log.d(TAG, "onRecordingStopped(recordedProgramUri= " + recordedProgramUri + ")");
368a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            }
369a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            if (this != mSessionCallback) {
370a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                Log.w(TAG, "onRecordingStopped - session not created");
371a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                return;
372a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            }
373e3c11e842937f50f54c9d82363f33338dc9e261bJae Seo            mIsRecordingStarted = false;
374a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            mCallback.onRecordingStopped(recordedProgramUri);
375a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        }
376a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
377a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        @Override
378a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        public void onError(TvInputManager.Session session, int error) {
379a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            if (DEBUG) {
3804eee6a73e476cd2d82a69f3a535628901047f140Jae Seo                Log.d(TAG, "onError(error=" + error + ")");
381a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            }
382a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            if (this != mSessionCallback) {
383a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                Log.w(TAG, "onError - session not created");
384a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                return;
385a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            }
386a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            mCallback.onError(error);
387a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        }
388a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo
389a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        @Override
390a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        public void onSessionEvent(TvInputManager.Session session, String eventType,
391a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                Bundle eventArgs) {
392a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            if (DEBUG) {
3934eee6a73e476cd2d82a69f3a535628901047f140Jae Seo                Log.d(TAG, "onSessionEvent(eventType=" + eventType + ", eventArgs=" + eventArgs
3944eee6a73e476cd2d82a69f3a535628901047f140Jae Seo                        + ")");
395a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            }
396a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            if (this != mSessionCallback) {
397a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                Log.w(TAG, "onSessionEvent - session not created");
398a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                return;
399a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            }
400a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            if (mCallback != null) {
401a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo                mCallback.onEvent(mInputId, eventType, eventArgs);
402a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo            }
403a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo        }
404a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo    }
405a826d0172aae5e91d633ffe606059a2355fbf7e5Jae Seo}
406