summaryrefslogtreecommitdiffstats
path: root/core/java/android/tv/TvInputManager.java
blob: 0b6ab6427adeca28c7981519cd774d5d67641684 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
/*
 * Copyright (C) 2014 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.tv;

import android.content.ComponentName;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.util.SparseArray;
import android.view.Surface;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * Central system API to the overall TV input framework (TIF) architecture, which arbitrates
 * interaction between applications and the selected TV inputs.
 */
public final class TvInputManager {
    private static final String TAG = "TvInputManager";

    private final ITvInputManager mService;

    // A mapping from an input to the list of its TvInputListenerRecords.
    private final Map<ComponentName, List<TvInputListenerRecord>> mTvInputListenerRecordsMap =
            new HashMap<ComponentName, List<TvInputListenerRecord>>();

    // A mapping from the sequence number of a session to its SessionCreateCallbackRecord.
    private final SparseArray<SessionCreateCallbackRecord> mSessionCreateCallbackRecordMap =
            new SparseArray<SessionCreateCallbackRecord>();

    // A sequence number for the next session to be created. Should be protected by a lock
    // {@code mSessionCreateCallbackRecordMap}.
    private int mNextSeq;

    private final ITvInputClient mClient;

    private final int mUserId;

    /**
     * Interface used to receive the created session.
     */
    public interface SessionCreateCallback {
        /**
         * This is called after {@link TvInputManager#createSession} has been processed.
         *
         * @param session A {@link TvInputManager.Session} instance created. This can be
         *            {@code null} if the creation request failed.
         */
        void onSessionCreated(Session session);
    }

    private static final class SessionCreateCallbackRecord {
        private final SessionCreateCallback mSessionCreateCallback;
        private final Handler mHandler;

        public SessionCreateCallbackRecord(SessionCreateCallback sessionCreateCallback,
                Handler handler) {
            mSessionCreateCallback = sessionCreateCallback;
            mHandler = handler;
        }

        public void postSessionCreated(final Session session) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    mSessionCreateCallback.onSessionCreated(session);
                }
            });
        }
    }

    /**
     * Interface used to monitor status of the TV input.
     */
    public abstract static class TvInputListener {
        /**
         * This is called when the availability status of a given TV input is changed.
         *
         * @param name {@link ComponentName} of {@link android.app.Service} that implements the
         *            given TV input.
         * @param isAvailable {@code true} if the given TV input is available to show TV programs.
         *            {@code false} otherwise.
         */
        public void onAvailabilityChanged(ComponentName name, boolean isAvailable) {
        }
    }

    private static final class TvInputListenerRecord {
        private final TvInputListener mListener;
        private final Handler mHandler;

        public TvInputListenerRecord(TvInputListener listener, Handler handler) {
            mListener = listener;
            mHandler = handler;
        }

        public TvInputListener getListener() {
            return mListener;
        }

        public void postAvailabilityChanged(final ComponentName name, final boolean isAvailable) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    mListener.onAvailabilityChanged(name, isAvailable);
                }
            });
        }
    }

    /**
     * @hide
     */
    public TvInputManager(ITvInputManager service, int userId) {
        mService = service;
        mUserId = userId;
        mClient = new ITvInputClient.Stub() {
            @Override
            public void onSessionCreated(ComponentName name, IBinder token, int seq) {
                synchronized (mSessionCreateCallbackRecordMap) {
                    SessionCreateCallbackRecord record = mSessionCreateCallbackRecordMap.get(seq);
                    mSessionCreateCallbackRecordMap.delete(seq);
                    if (record == null) {
                        Log.e(TAG, "Callback not found for " + token);
                        return;
                    }
                    Session session = null;
                    if (token != null) {
                        session = new Session(name, token, mService, mUserId);
                    }
                    record.postSessionCreated(session);
                }
            }

            @Override
            public void onAvailabilityChanged(ComponentName name, boolean isAvailable) {
                synchronized (mTvInputListenerRecordsMap) {
                    List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(name);
                    if (records == null) {
                        // Silently ignore - no listener is registered yet.
                        return;
                    }
                    int recordsCount = records.size();
                    for (int i = 0; i < recordsCount; i++) {
                        records.get(i).postAvailabilityChanged(name, isAvailable);
                    }
                }
            }
        };
    }

    /**
     * Returns the complete list of TV inputs on the system.
     *
     * @return List of {@link TvInputInfo} for each TV input that describes its meta information.
     */
    public List<TvInputInfo> getTvInputList() {
        try {
            return mService.getTvInputList(mUserId);
        } catch (RemoteException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Returns the availability of a given TV input.
     *
     * @param name {@link ComponentName} of {@link android.app.Service} that implements the given TV
     *            input.
     * @throws IllegalArgumentException if the argument is {@code null}.
     * @throws IllegalStateException If there is no {@link TvInputListener} registered on the given
     *             TV input.
     */
    public boolean getAvailability(ComponentName name) {
        if (name == null) {
            throw new IllegalArgumentException("name cannot be null");
        }
        synchronized (mTvInputListenerRecordsMap) {
            List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(name);
            if (records == null || records.size() == 0) {
                throw new IllegalStateException("At least one listener should be registered.");
            }
        }
        try {
            return mService.getAvailability(mClient, name, mUserId);
        } catch (RemoteException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Registers a {@link TvInputListener} for a given TV input.
     *
     * @param name {@link ComponentName} of {@link android.app.Service} that implements the given TV
     *            input.
     * @param listener a listener used to monitor status of the given TV input.
     * @param handler a {@link Handler} that the status change will be delivered to.
     * @throws IllegalArgumentException if any of the arguments is {@code null}.
     */
    public void registerListener(ComponentName name, TvInputListener listener, Handler handler) {
        if (name == null) {
            throw new IllegalArgumentException("name cannot be null");
        }
        if (listener == null) {
            throw new IllegalArgumentException("listener cannot be null");
        }
        if (handler == null) {
            throw new IllegalArgumentException("handler cannot be null");
        }
        synchronized (mTvInputListenerRecordsMap) {
            List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(name);
            if (records == null) {
                records = new ArrayList<TvInputListenerRecord>();
                mTvInputListenerRecordsMap.put(name, records);
                try {
                    mService.registerCallback(mClient, name, mUserId);
                } catch (RemoteException e) {
                    throw new RuntimeException(e);
                }
            }
            records.add(new TvInputListenerRecord(listener, handler));
        }
    }

    /**
     * Unregisters the existing {@link TvInputListener} for a given TV input.
     *
     * @param name {@link ComponentName} of {@link android.app.Service} that implements the given TV
     *            input.
     * @param listener the existing listener to remove for the given TV input.
     * @throws IllegalArgumentException if any of the arguments is {@code null}.
     */
    public void unregisterListener(ComponentName name, final TvInputListener listener) {
        if (name == null) {
            throw new IllegalArgumentException("name cannot be null");
        }
        if (listener == null) {
            throw new IllegalArgumentException("listener cannot be null");
        }
        synchronized (mTvInputListenerRecordsMap) {
            List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(name);
            if (records == null) {
                Log.e(TAG, "No listener found for " + name.getClassName());
                return;
            }
            for (Iterator<TvInputListenerRecord> it = records.iterator(); it.hasNext();) {
                TvInputListenerRecord record = it.next();
                if (record.getListener() == listener) {
                    it.remove();
                }
            }
            if (records.isEmpty()) {
                try {
                    mService.unregisterCallback(mClient, name, mUserId);
                } catch (RemoteException e) {
                    throw new RuntimeException(e);
                } finally {
                    mTvInputListenerRecordsMap.remove(name);
                }
            }
        }
    }

    /**
     * Creates a {@link TvInputSession} interface for a given TV input.
     * <p>
     * The number of sessions that can be created at the same time is limited by the capability of
     * the given TV input.
     * </p>
     *
     * @param name {@link ComponentName} of {@link android.app.Service} that implements the given TV
     *            input.
     * @param callback a callback used to receive the created session.
     * @param handler a {@link Handler} that the session creation will be delivered to.
     * @throws IllegalArgumentException if any of the arguments is {@code null}.
     */
    public void createSession(ComponentName name, final SessionCreateCallback callback,
            Handler handler) {
        if (name == null) {
            throw new IllegalArgumentException("name cannot be null");
        }
        if (callback == null) {
            throw new IllegalArgumentException("callback cannot be null");
        }
        if (handler == null) {
            throw new IllegalArgumentException("handler cannot be null");
        }
        SessionCreateCallbackRecord record = new SessionCreateCallbackRecord(callback, handler);
        synchronized (mSessionCreateCallbackRecordMap) {
            int seq = mNextSeq++;
            mSessionCreateCallbackRecordMap.put(seq, record);
            try {
                mService.createSession(mClient, name, seq, mUserId);
            } catch (RemoteException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /** The Session provides the per-session functionality of TV inputs. */
    public static final class Session {
        private final ITvInputManager mService;
        private final IBinder mToken;
        private final int mUserId;

        /** @hide */
        private Session(ComponentName name, IBinder token, ITvInputManager service, int userId) {
            mToken = token;
            mService = service;
            mUserId = userId;
        }

        /**
         * Releases this session.
         */
        public void release() {
            try {
                mService.releaseSession(mToken, mUserId);
            } catch (RemoteException e) {
                throw new RuntimeException(e);
            }
        }

        /**
         * Sets the {@link android.view.Surface} for this session.
         *
         * @param surface A {@link android.view.Surface} used to render video.
         */
        public void setSurface(Surface surface) {
            // surface can be null.
            try {
                mService.setSurface(mToken, surface, mUserId);
            } catch (RemoteException e) {
                throw new RuntimeException(e);
            }
        }

        /**
         * Sets the relative volume of this session to handle a change of audio focus.
         *
         * @param volume A volume value between 0.0f to 1.0f.
         * @throws IllegalArgumentException if the volume value is out of range.
         */
        public void setVolume(float volume) {
            try {
                if (volume < 0.0f || volume > 1.0f) {
                    throw new IllegalArgumentException("volume should be between 0.0f and 1.0f");
                }
                mService.setVolume(mToken, volume, mUserId);
            } catch (RemoteException e) {
                throw new RuntimeException(e);
            }
        }

        /**
         * Tunes to a given channel.
         *
         * @param channelUri The URI of a channel.
         * @throws IllegalArgumentException if the argument is {@code null}.
         */
        public void tune(Uri channelUri) {
            if (channelUri == null) {
                throw new IllegalArgumentException("channelUri cannot be null");
            }
            try {
                mService.tune(mToken, channelUri, mUserId);
            } catch (RemoteException e) {
                throw new RuntimeException(e);
            }
        }
    }
}