blob: 5ceb992fe463cd0984b6a0f8a6afec5c24aeb385 [file] [log] [blame]
Jae Seo39570912014-02-20 18:23:25 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.tv;
18
19import android.app.ActivityManager;
20import android.content.BroadcastReceiver;
21import android.content.ComponentName;
Jae Seo31dc634be2014-04-15 17:40:23 -070022import android.content.ContentResolver;
23import android.content.ContentUris;
24import android.content.ContentValues;
Jae Seo39570912014-02-20 18:23:25 -080025import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.content.ServiceConnection;
29import android.content.pm.PackageManager;
30import android.content.pm.ResolveInfo;
31import android.content.pm.ServiceInfo;
Jae Seo31dc634be2014-04-15 17:40:23 -070032import android.database.Cursor;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090033import android.graphics.Rect;
Jae Seo39570912014-02-20 18:23:25 -080034import android.net.Uri;
35import android.os.Binder;
Jae Seo31dc634be2014-04-15 17:40:23 -070036import android.os.Handler;
Jae Seo39570912014-02-20 18:23:25 -080037import android.os.IBinder;
Jae Seo31dc634be2014-04-15 17:40:23 -070038import android.os.Looper;
39import android.os.Message;
Jae Seo39570912014-02-20 18:23:25 -080040import android.os.Process;
41import android.os.RemoteException;
42import android.os.UserHandle;
Jae Seo31dc634be2014-04-15 17:40:23 -070043import android.provider.TvContract;
Jae Seo39570912014-02-20 18:23:25 -080044import android.tv.ITvInputClient;
45import android.tv.ITvInputManager;
46import android.tv.ITvInputService;
47import android.tv.ITvInputServiceCallback;
48import android.tv.ITvInputSession;
49import android.tv.ITvInputSessionCallback;
50import android.tv.TvInputInfo;
51import android.tv.TvInputService;
Jae Seo39570912014-02-20 18:23:25 -080052import android.util.Log;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090053import android.util.Slog;
Jae Seo39570912014-02-20 18:23:25 -080054import android.util.SparseArray;
55import android.view.Surface;
56
57import com.android.internal.content.PackageMonitor;
Jae Seo31dc634be2014-04-15 17:40:23 -070058import com.android.internal.os.SomeArgs;
59import com.android.server.IoThread;
Jae Seo39570912014-02-20 18:23:25 -080060import com.android.server.SystemService;
61
62import java.util.ArrayList;
63import java.util.HashMap;
64import java.util.List;
65import java.util.Map;
66
67/** This class provides a system service that manages television inputs. */
68public final class TvInputManagerService extends SystemService {
69 // STOPSHIP: Turn debugging off.
70 private static final boolean DEBUG = true;
71 private static final String TAG = "TvInputManagerService";
72
73 private final Context mContext;
74
Jae Seo31dc634be2014-04-15 17:40:23 -070075 private final ContentResolver mContentResolver;
76
Jae Seo39570912014-02-20 18:23:25 -080077 // A global lock.
78 private final Object mLock = new Object();
79
80 // ID of the current user.
81 private int mCurrentUserId = UserHandle.USER_OWNER;
82
83 // A map from user id to UserState.
84 private final SparseArray<UserState> mUserStates = new SparseArray<UserState>();
85
Jae Seo31dc634be2014-04-15 17:40:23 -070086 private final Handler mLogHandler;
87
Jae Seo39570912014-02-20 18:23:25 -080088 public TvInputManagerService(Context context) {
89 super(context);
Jae Seo31dc634be2014-04-15 17:40:23 -070090
Jae Seo39570912014-02-20 18:23:25 -080091 mContext = context;
Jae Seo31dc634be2014-04-15 17:40:23 -070092 mContentResolver = context.getContentResolver();
93 mLogHandler = new LogHandler(IoThread.get().getLooper());
94
Jae Seo39570912014-02-20 18:23:25 -080095 registerBroadcastReceivers();
Jae Seo31dc634be2014-04-15 17:40:23 -070096
Jae Seo39570912014-02-20 18:23:25 -080097 synchronized (mLock) {
98 mUserStates.put(mCurrentUserId, new UserState());
99 buildTvInputListLocked(mCurrentUserId);
100 }
101 }
102
103 @Override
104 public void onStart() {
105 publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());
106 }
107
108 private void registerBroadcastReceivers() {
109 PackageMonitor monitor = new PackageMonitor() {
110 @Override
111 public void onSomePackagesChanged() {
112 synchronized (mLock) {
113 buildTvInputListLocked(mCurrentUserId);
114 }
115 }
116 };
117 monitor.register(mContext, null, UserHandle.ALL, true);
118
119 IntentFilter intentFilter = new IntentFilter();
120 intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
121 intentFilter.addAction(Intent.ACTION_USER_REMOVED);
122 mContext.registerReceiverAsUser(new BroadcastReceiver() {
123 @Override
124 public void onReceive(Context context, Intent intent) {
125 String action = intent.getAction();
126 if (Intent.ACTION_USER_SWITCHED.equals(action)) {
127 switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
128 } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
129 removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
130 }
131 }
132 }, UserHandle.ALL, intentFilter, null, null);
133 }
134
135 private void buildTvInputListLocked(int userId) {
136 UserState userState = getUserStateLocked(userId);
137 userState.inputList.clear();
138
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900139 if (DEBUG) Slog.d(TAG, "buildTvInputList");
Jae Seo39570912014-02-20 18:23:25 -0800140 PackageManager pm = mContext.getPackageManager();
141 List<ResolveInfo> services = pm.queryIntentServices(
142 new Intent(TvInputService.SERVICE_INTERFACE), PackageManager.GET_SERVICES);
143 for (ResolveInfo ri : services) {
144 ServiceInfo si = ri.serviceInfo;
145 if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900146 Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
Jae Seo39570912014-02-20 18:23:25 -0800147 + android.Manifest.permission.BIND_TV_INPUT);
148 continue;
149 }
150 TvInputInfo info = new TvInputInfo(ri);
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900151 if (DEBUG) Slog.d(TAG, "add " + info.getId());
Jae Seo39570912014-02-20 18:23:25 -0800152 userState.inputList.add(info);
153 }
154 }
155
156 private void switchUser(int userId) {
157 synchronized (mLock) {
158 if (mCurrentUserId == userId) {
159 return;
160 }
161 // final int oldUserId = mCurrentUserId;
162 // TODO: Release services and sessions in the old user state, if needed.
163 mCurrentUserId = userId;
164
165 UserState userState = mUserStates.get(userId);
166 if (userState == null) {
167 userState = new UserState();
168 }
169 mUserStates.put(userId, userState);
170 buildTvInputListLocked(userId);
171 }
172 }
173
174 private void removeUser(int userId) {
175 synchronized (mLock) {
Jae Seob06cb882014-04-09 12:08:17 -0700176 UserState userState = mUserStates.get(userId);
177 if (userState == null) {
178 return;
179 }
Jae Seo39570912014-02-20 18:23:25 -0800180 // Release created sessions.
Jae Seo39570912014-02-20 18:23:25 -0800181 for (SessionState state : userState.sessionStateMap.values()) {
182 if (state.session != null) {
183 try {
184 state.session.release();
185 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900186 Slog.e(TAG, "error in release", e);
Jae Seo39570912014-02-20 18:23:25 -0800187 }
188 }
189 }
190 userState.sessionStateMap.clear();
191
192 // Unregister all callbacks and unbind all services.
193 for (ServiceState serviceState : userState.serviceStateMap.values()) {
194 if (serviceState.callback != null) {
195 try {
196 serviceState.service.unregisterCallback(serviceState.callback);
197 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900198 Slog.e(TAG, "error in unregisterCallback", e);
Jae Seo39570912014-02-20 18:23:25 -0800199 }
200 }
201 serviceState.clients.clear();
202 mContext.unbindService(serviceState.connection);
203 }
204 userState.serviceStateMap.clear();
205
206 mUserStates.remove(userId);
207 }
208 }
209
210 private UserState getUserStateLocked(int userId) {
211 UserState userState = mUserStates.get(userId);
212 if (userState == null) {
213 throw new IllegalStateException("User state not found for user ID " + userId);
214 }
215 return userState;
216 }
217
218 private ServiceState getServiceStateLocked(ComponentName name, int userId) {
219 UserState userState = getUserStateLocked(userId);
220 ServiceState serviceState = userState.serviceStateMap.get(name);
221 if (serviceState == null) {
Sungsoo Lim7de5e232014-04-12 16:51:27 +0900222 throw new IllegalStateException("Service state not found for " + name + " (userId="
223 + userId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800224 }
225 return serviceState;
226 }
227
228 private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
229 UserState userState = getUserStateLocked(userId);
230 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
231 if (sessionState == null) {
232 throw new IllegalArgumentException("Session state not found for token " + sessionToken);
233 }
234 // Only the application that requested this session or the system can access it.
235 if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) {
236 throw new SecurityException("Illegal access to the session with token " + sessionToken
237 + " from uid " + callingUid);
238 }
239 ITvInputSession session = sessionState.session;
240 if (session == null) {
241 throw new IllegalStateException("Session not yet created for token " + sessionToken);
242 }
243 return session;
244 }
245
246 private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
247 String methodName) {
248 return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
249 false, methodName, null);
250 }
251
252 private void updateServiceConnectionLocked(ComponentName name, int userId) {
253 UserState userState = getUserStateLocked(userId);
254 ServiceState serviceState = userState.serviceStateMap.get(name);
255 if (serviceState == null) {
256 return;
257 }
Sungsoo Lim7de5e232014-04-12 16:51:27 +0900258 boolean isStateEmpty = serviceState.clients.isEmpty()
259 && serviceState.sessionTokens.isEmpty();
Jae Seo39570912014-02-20 18:23:25 -0800260 if (serviceState.service == null && !isStateEmpty && userId == mCurrentUserId) {
261 // This means that the service is not yet connected but its state indicates that we
262 // have pending requests. Then, connect the service.
263 if (serviceState.bound) {
264 // We have already bound to the service so we don't try to bind again until after we
265 // unbind later on.
266 return;
267 }
268 if (DEBUG) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900269 Slog.d(TAG, "bindServiceAsUser(name=" + name.getClassName() + ", userId=" + userId
Jae Seo39570912014-02-20 18:23:25 -0800270 + ")");
271 }
272 Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(name);
273 mContext.bindServiceAsUser(i, serviceState.connection, Context.BIND_AUTO_CREATE,
274 new UserHandle(userId));
275 serviceState.bound = true;
276 } else if (serviceState.service != null && isStateEmpty) {
277 // This means that the service is already connected but its state indicates that we have
278 // nothing to do with it. Then, disconnect the service.
279 if (DEBUG) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900280 Slog.d(TAG, "unbindService(name=" + name.getClassName() + ")");
Jae Seo39570912014-02-20 18:23:25 -0800281 }
282 mContext.unbindService(serviceState.connection);
283 userState.serviceStateMap.remove(name);
284 }
285 }
286
287 private void createSessionInternalLocked(ITvInputService service, final IBinder sessionToken,
Sungsoo Lim7de5e232014-04-12 16:51:27 +0900288 final int userId) {
289 final SessionState sessionState =
290 getUserStateLocked(userId).sessionStateMap.get(sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800291 if (DEBUG) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900292 Slog.d(TAG, "createSessionInternalLocked(name=" + sessionState.name.getClassName()
Jae Seo39570912014-02-20 18:23:25 -0800293 + ")");
294 }
295 // Set up a callback to send the session token.
296 ITvInputSessionCallback callback = new ITvInputSessionCallback.Stub() {
297 @Override
298 public void onSessionCreated(ITvInputSession session) {
299 if (DEBUG) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900300 Slog.d(TAG, "onSessionCreated(name=" + sessionState.name.getClassName() + ")");
Jae Seo39570912014-02-20 18:23:25 -0800301 }
302 synchronized (mLock) {
303 sessionState.session = session;
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900304 if (session == null) {
305 removeSessionStateLocked(sessionToken, userId);
306 sendSessionTokenToClientLocked(sessionState.client, sessionState.name, null,
307 sessionState.seq, userId);
308 } else {
309 sendSessionTokenToClientLocked(sessionState.client, sessionState.name,
310 sessionToken, sessionState.seq, userId);
311 }
Jae Seo39570912014-02-20 18:23:25 -0800312 }
313 }
314 };
315
316 // Create a session. When failed, send a null token immediately.
317 try {
318 service.createSession(callback);
319 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900320 Slog.e(TAG, "error in createSession", e);
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900321 removeSessionStateLocked(sessionToken, userId);
Jae Seo39570912014-02-20 18:23:25 -0800322 sendSessionTokenToClientLocked(sessionState.client, sessionState.name, null,
323 sessionState.seq, userId);
324 }
325 }
326
327 private void sendSessionTokenToClientLocked(ITvInputClient client, ComponentName name,
328 IBinder sessionToken, int seq, int userId) {
329 try {
330 client.onSessionCreated(name, sessionToken, seq);
331 } catch (RemoteException exception) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900332 Slog.e(TAG, "error in onSessionCreated", exception);
Jae Seo39570912014-02-20 18:23:25 -0800333 }
334
335 if (sessionToken == null) {
336 // This means that the session creation failed. We might want to disconnect the service.
337 updateServiceConnectionLocked(name, userId);
338 }
339 }
340
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900341 private void removeSessionStateLocked(IBinder sessionToken, int userId) {
342 // Remove the session state from the global session state map of the current user.
343 UserState userState = getUserStateLocked(userId);
344 SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
345
Jae Seo31dc634be2014-04-15 17:40:23 -0700346 // Close the open log entry, if any.
347 if (sessionState.logUri != null) {
348 SomeArgs args = SomeArgs.obtain();
349 args.arg1 = sessionState.logUri;
350 args.arg2 = System.currentTimeMillis();
351 mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args).sendToTarget();
352 }
353
Sungsoo Lim7de5e232014-04-12 16:51:27 +0900354 // Also remove the session token from the session token list of the current service.
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900355 ServiceState serviceState = userState.serviceStateMap.get(sessionState.name);
356 if (serviceState != null) {
Sungsoo Lim7de5e232014-04-12 16:51:27 +0900357 serviceState.sessionTokens.remove(sessionToken);
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900358 }
359 updateServiceConnectionLocked(sessionState.name, userId);
360 }
361
Jae Seo39570912014-02-20 18:23:25 -0800362 private final class BinderService extends ITvInputManager.Stub {
363 @Override
364 public List<TvInputInfo> getTvInputList(int userId) {
365 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
366 Binder.getCallingUid(), userId, "getTvInputList");
367 final long identity = Binder.clearCallingIdentity();
368 try {
369 synchronized (mLock) {
370 UserState userState = getUserStateLocked(resolvedUserId);
371 return new ArrayList<TvInputInfo>(userState.inputList);
372 }
373 } finally {
374 Binder.restoreCallingIdentity(identity);
375 }
376 }
377
378 @Override
379 public boolean getAvailability(final ITvInputClient client, final ComponentName name,
380 int userId) {
381 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
382 Binder.getCallingUid(), userId, "getAvailability");
383 final long identity = Binder.clearCallingIdentity();
384 try {
385 synchronized (mLock) {
386 UserState userState = getUserStateLocked(resolvedUserId);
387 ServiceState serviceState = userState.serviceStateMap.get(name);
388 if (serviceState != null) {
389 // We already know the status of this input service. Return the cached
390 // status.
391 return serviceState.available;
392 }
393 }
394 } finally {
395 Binder.restoreCallingIdentity(identity);
396 }
397 return false;
398 }
399
400 @Override
401 public void registerCallback(final ITvInputClient client, final ComponentName name,
402 int userId) {
403 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
404 Binder.getCallingUid(), userId, "registerCallback");
405 final long identity = Binder.clearCallingIdentity();
406 try {
407 synchronized (mLock) {
408 // Create a new service callback and add it to the callback map of the current
409 // service.
410 UserState userState = getUserStateLocked(resolvedUserId);
411 ServiceState serviceState = userState.serviceStateMap.get(name);
412 if (serviceState == null) {
Jae Seo31dc634be2014-04-15 17:40:23 -0700413 serviceState = new ServiceState(resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -0800414 userState.serviceStateMap.put(name, serviceState);
415 }
416 IBinder iBinder = client.asBinder();
417 if (!serviceState.clients.contains(iBinder)) {
418 serviceState.clients.add(iBinder);
419 }
420 if (serviceState.service != null) {
421 if (serviceState.callback != null) {
422 // We already handled.
423 return;
424 }
425 serviceState.callback = new ServiceCallback(resolvedUserId);
426 try {
427 serviceState.service.registerCallback(serviceState.callback);
428 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900429 Slog.e(TAG, "error in registerCallback", e);
Jae Seo39570912014-02-20 18:23:25 -0800430 }
431 } else {
432 updateServiceConnectionLocked(name, resolvedUserId);
433 }
434 }
435 } finally {
436 Binder.restoreCallingIdentity(identity);
437 }
438 }
439
440 @Override
441 public void unregisterCallback(ITvInputClient client, ComponentName name, int userId) {
442 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
443 Binder.getCallingUid(), userId, "unregisterCallback");
444 final long identity = Binder.clearCallingIdentity();
445 try {
446 synchronized (mLock) {
447 UserState userState = getUserStateLocked(resolvedUserId);
448 ServiceState serviceState = userState.serviceStateMap.get(name);
449 if (serviceState == null) {
450 return;
451 }
452
453 // Remove this client from the client list and unregister the callback.
454 serviceState.clients.remove(client.asBinder());
455 if (!serviceState.clients.isEmpty()) {
456 // We have other clients who want to keep the callback. Do this later.
457 return;
458 }
459 if (serviceState.service == null || serviceState.callback == null) {
460 return;
461 }
462 try {
463 serviceState.service.unregisterCallback(serviceState.callback);
464 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900465 Slog.e(TAG, "error in unregisterCallback", e);
Jae Seo39570912014-02-20 18:23:25 -0800466 } finally {
467 serviceState.callback = null;
468 updateServiceConnectionLocked(name, resolvedUserId);
469 }
470 }
471 } finally {
472 Binder.restoreCallingIdentity(identity);
473 }
474 }
475
476 @Override
477 public void createSession(final ITvInputClient client, final ComponentName name,
478 int seq, int userId) {
479 final int callingUid = Binder.getCallingUid();
480 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
481 userId, "createSession");
482 final long identity = Binder.clearCallingIdentity();
483 try {
484 synchronized (mLock) {
485 // Create a new session token and a session state.
486 IBinder sessionToken = new Binder();
487 SessionState sessionState = new SessionState(name, client, seq, callingUid);
488 sessionState.session = null;
489
490 // Add them to the global session state map of the current user.
491 UserState userState = getUserStateLocked(resolvedUserId);
492 userState.sessionStateMap.put(sessionToken, sessionState);
493
494 // Also, add them to the session state map of the current service.
495 ServiceState serviceState = userState.serviceStateMap.get(name);
496 if (serviceState == null) {
Jae Seo31dc634be2014-04-15 17:40:23 -0700497 serviceState = new ServiceState(resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -0800498 userState.serviceStateMap.put(name, serviceState);
499 }
Sungsoo Lim7de5e232014-04-12 16:51:27 +0900500 serviceState.sessionTokens.add(sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800501
502 if (serviceState.service != null) {
503 createSessionInternalLocked(serviceState.service, sessionToken,
Sungsoo Lim7de5e232014-04-12 16:51:27 +0900504 resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -0800505 } else {
506 updateServiceConnectionLocked(name, resolvedUserId);
507 }
508 }
509 } finally {
510 Binder.restoreCallingIdentity(identity);
511 }
512 }
513
514 @Override
515 public void releaseSession(IBinder sessionToken, int userId) {
516 final int callingUid = Binder.getCallingUid();
517 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
518 userId, "releaseSession");
519 final long identity = Binder.clearCallingIdentity();
520 try {
521 synchronized (mLock) {
522 // Release the session.
523 try {
524 getSessionLocked(sessionToken, callingUid, resolvedUserId).release();
525 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900526 Slog.e(TAG, "error in release", e);
Jae Seo39570912014-02-20 18:23:25 -0800527 }
528
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900529 removeSessionStateLocked(sessionToken, resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -0800530 }
531 } finally {
532 Binder.restoreCallingIdentity(identity);
533 }
534 }
535
536 @Override
537 public void setSurface(IBinder sessionToken, Surface surface, int userId) {
538 final int callingUid = Binder.getCallingUid();
539 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
540 userId, "setSurface");
541 final long identity = Binder.clearCallingIdentity();
542 try {
543 synchronized (mLock) {
544 try {
545 getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface(
546 surface);
547 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900548 Slog.e(TAG, "error in setSurface", e);
Jae Seo39570912014-02-20 18:23:25 -0800549 }
550 }
551 } finally {
552 Binder.restoreCallingIdentity(identity);
553 }
554 }
555
556 @Override
557 public void setVolume(IBinder sessionToken, float volume, int userId) {
558 final int callingUid = Binder.getCallingUid();
559 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
560 userId, "setVolume");
561 final long identity = Binder.clearCallingIdentity();
562 try {
563 synchronized (mLock) {
564 try {
565 getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume(
566 volume);
567 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900568 Slog.e(TAG, "error in setVolume", e);
Jae Seo39570912014-02-20 18:23:25 -0800569 }
570 }
571 } finally {
572 Binder.restoreCallingIdentity(identity);
573 }
574 }
575
576 @Override
577 public void tune(IBinder sessionToken, final Uri channelUri, int userId) {
578 final int callingUid = Binder.getCallingUid();
579 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
580 userId, "tune");
581 final long identity = Binder.clearCallingIdentity();
582 try {
583 synchronized (mLock) {
Jae Seo39570912014-02-20 18:23:25 -0800584 try {
585 getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri);
Jae Seo31dc634be2014-04-15 17:40:23 -0700586
587 long currentTime = System.currentTimeMillis();
588 long channelId = ContentUris.parseId(channelUri);
589
590 // Close the open log entry first, if any.
591 UserState userState = getUserStateLocked(resolvedUserId);
592 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
593 if (sessionState.logUri != null) {
594 SomeArgs args = SomeArgs.obtain();
595 args.arg1 = sessionState.logUri;
596 args.arg2 = currentTime;
597 mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args)
598 .sendToTarget();
599 }
600
601 // Create a log entry and fill it later.
602 ContentValues values = new ContentValues();
603 values.put(TvContract.WatchedPrograms.WATCH_START_TIME_UTC_MILLIS,
604 currentTime);
605 values.put(TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS, 0);
606 values.put(TvContract.WatchedPrograms.CHANNEL_ID, channelId);
607
608 sessionState.logUri = mContentResolver.insert(
609 TvContract.WatchedPrograms.CONTENT_URI, values);
610 SomeArgs args = SomeArgs.obtain();
611 args.arg1 = sessionState.logUri;
612 args.arg2 = ContentUris.parseId(channelUri);
613 args.arg3 = currentTime;
614 mLogHandler.obtainMessage(LogHandler.MSG_OPEN_ENTRY, args).sendToTarget();
Jae Seo39570912014-02-20 18:23:25 -0800615 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900616 Slog.e(TAG, "error in tune", e);
Jae Seo39570912014-02-20 18:23:25 -0800617 return;
618 }
619 }
620 } finally {
621 Binder.restoreCallingIdentity(identity);
622 }
623 }
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900624
625 @Override
626 public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
627 int userId) {
628 final int callingUid = Binder.getCallingUid();
629 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
630 userId, "createOverlayView");
631 final long identity = Binder.clearCallingIdentity();
632 try {
633 synchronized (mLock) {
634 try {
635 getSessionLocked(sessionToken, callingUid, resolvedUserId)
636 .createOverlayView(windowToken, frame);
637 } catch (RemoteException e) {
638 Slog.e(TAG, "error in createOverlayView", e);
639 }
640 }
641 } finally {
642 Binder.restoreCallingIdentity(identity);
643 }
644 }
645
646 @Override
647 public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
648 final int callingUid = Binder.getCallingUid();
649 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
650 userId, "relayoutOverlayView");
651 final long identity = Binder.clearCallingIdentity();
652 try {
653 synchronized (mLock) {
654 try {
655 getSessionLocked(sessionToken, callingUid, resolvedUserId)
656 .relayoutOverlayView(frame);
657 } catch (RemoteException e) {
658 Slog.e(TAG, "error in relayoutOverlayView", e);
659 }
660 }
661 } finally {
662 Binder.restoreCallingIdentity(identity);
663 }
664 }
665
666 @Override
667 public void removeOverlayView(IBinder sessionToken, int userId) {
668 final int callingUid = Binder.getCallingUid();
669 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
670 userId, "removeOverlayView");
671 final long identity = Binder.clearCallingIdentity();
672 try {
673 synchronized (mLock) {
674 try {
675 getSessionLocked(sessionToken, callingUid, resolvedUserId)
676 .removeOverlayView();
677 } catch (RemoteException e) {
678 Slog.e(TAG, "error in removeOverlayView", e);
679 }
680 }
681 } finally {
682 Binder.restoreCallingIdentity(identity);
683 }
684 }
Jae Seo39570912014-02-20 18:23:25 -0800685 }
686
687 private static final class UserState {
688 // A list of all known TV inputs on the system.
689 private final List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
690
691 // A mapping from the name of a TV input service to its state.
692 private final Map<ComponentName, ServiceState> serviceStateMap =
693 new HashMap<ComponentName, ServiceState>();
694
695 // A mapping from the token of a TV input session to its state.
696 private final Map<IBinder, SessionState> sessionStateMap =
697 new HashMap<IBinder, SessionState>();
698 }
699
700 private final class ServiceState {
701 private final List<IBinder> clients = new ArrayList<IBinder>();
Sungsoo Lim7de5e232014-04-12 16:51:27 +0900702 private final List<IBinder> sessionTokens = new ArrayList<IBinder>();
Jae Seo39570912014-02-20 18:23:25 -0800703 private final ServiceConnection connection;
704
705 private ITvInputService service;
706 private ServiceCallback callback;
707 private boolean bound;
708 private boolean available;
709
Jae Seo31dc634be2014-04-15 17:40:23 -0700710 private ServiceState(int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800711 this.connection = new InputServiceConnection(userId);
712 }
713 }
714
715 private static final class SessionState {
716 private final ComponentName name;
717 private final ITvInputClient client;
718 private final int seq;
719 private final int callingUid;
720
721 private ITvInputSession session;
Jae Seo31dc634be2014-04-15 17:40:23 -0700722 private Uri logUri;
Jae Seo39570912014-02-20 18:23:25 -0800723
724 private SessionState(ComponentName name, ITvInputClient client, int seq, int callingUid) {
725 this.name = name;
726 this.client = client;
727 this.seq = seq;
728 this.callingUid = callingUid;
729 }
730 }
731
732 private final class InputServiceConnection implements ServiceConnection {
733 private final int mUserId;
734
735 private InputServiceConnection(int userId) {
736 mUserId = userId;
737 }
738
739 @Override
740 public void onServiceConnected(ComponentName name, IBinder service) {
741 if (DEBUG) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900742 Slog.d(TAG, "onServiceConnected(name=" + name.getClassName() + ")");
Jae Seo39570912014-02-20 18:23:25 -0800743 }
744 synchronized (mLock) {
745 ServiceState serviceState = getServiceStateLocked(name, mUserId);
746 serviceState.service = ITvInputService.Stub.asInterface(service);
747
748 // Register a callback, if we need to.
749 if (!serviceState.clients.isEmpty() && serviceState.callback == null) {
750 serviceState.callback = new ServiceCallback(mUserId);
751 try {
752 serviceState.service.registerCallback(serviceState.callback);
753 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900754 Slog.e(TAG, "error in registerCallback", e);
Jae Seo39570912014-02-20 18:23:25 -0800755 }
756 }
757
758 // And create sessions, if any.
Sungsoo Lim7de5e232014-04-12 16:51:27 +0900759 for (IBinder sessionToken : serviceState.sessionTokens) {
760 createSessionInternalLocked(serviceState.service, sessionToken, mUserId);
Jae Seo39570912014-02-20 18:23:25 -0800761 }
762 }
763 }
764
765 @Override
766 public void onServiceDisconnected(ComponentName name) {
767 if (DEBUG) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900768 Slog.d(TAG, "onServiceDisconnected(name=" + name.getClassName() + ")");
Jae Seo39570912014-02-20 18:23:25 -0800769 }
770 }
771 }
772
773 private final class ServiceCallback extends ITvInputServiceCallback.Stub {
774 private final int mUserId;
775
776 ServiceCallback(int userId) {
777 mUserId = userId;
778 }
779
780 @Override
781 public void onAvailabilityChanged(ComponentName name, boolean isAvailable)
782 throws RemoteException {
783 if (DEBUG) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900784 Slog.d(TAG, "onAvailabilityChanged(name=" + name.getClassName() + ", isAvailable="
Jae Seo39570912014-02-20 18:23:25 -0800785 + isAvailable + ")");
786 }
787 synchronized (mLock) {
788 ServiceState serviceState = getServiceStateLocked(name, mUserId);
789 serviceState.available = isAvailable;
790 for (IBinder iBinder : serviceState.clients) {
791 ITvInputClient client = ITvInputClient.Stub.asInterface(iBinder);
792 client.onAvailabilityChanged(name, isAvailable);
793 }
794 }
795 }
796 }
Jae Seo31dc634be2014-04-15 17:40:23 -0700797
798 private final class LogHandler extends Handler {
799 private static final int MSG_OPEN_ENTRY = 1;
800 private static final int MSG_UPDATE_ENTRY = 2;
801 private static final int MSG_CLOSE_ENTRY = 3;
802
803 public LogHandler(Looper looper) {
804 super(looper);
805 }
806
807 @Override
808 public void handleMessage(Message msg) {
809 switch (msg.what) {
810 case MSG_OPEN_ENTRY: {
811 SomeArgs args = (SomeArgs) msg.obj;
812 Uri uri = (Uri) args.arg1;
813 long channelId = (long) args.arg2;
814 long time = (long) args.arg3;
815 onOpenEntry(uri, channelId, time);
816 args.recycle();
817 return;
818 }
819 case MSG_UPDATE_ENTRY: {
820 SomeArgs args = (SomeArgs) msg.obj;
821 Uri uri = (Uri) args.arg1;
822 long channelId = (long) args.arg2;
823 long time = (long) args.arg3;
824 onUpdateEntry(uri, channelId, time);
825 args.recycle();
826 return;
827 }
828 case MSG_CLOSE_ENTRY: {
829 SomeArgs args = (SomeArgs) msg.obj;
830 Uri uri = (Uri) args.arg1;
831 long time = (long) args.arg2;
832 onCloseEntry(uri, time);
833 args.recycle();
834 return;
835 }
836 default: {
837 Log.w(TAG, "Unhandled message code: " + msg.what);
838 return;
839 }
840 }
841 }
842
843 private void onOpenEntry(Uri uri, long channelId, long watchStarttime) {
844 String[] projection = {
845 TvContract.Programs.TITLE,
846 TvContract.Programs.START_TIME_UTC_MILLIS,
847 TvContract.Programs.END_TIME_UTC_MILLIS,
848 TvContract.Programs.DESCRIPTION
849 };
850 String selection = TvContract.Programs.CHANNEL_ID + "=? AND "
851 + TvContract.Programs.START_TIME_UTC_MILLIS + "<=? AND "
852 + TvContract.Programs.END_TIME_UTC_MILLIS + ">?";
853 String[] selectionArgs = {
854 String.valueOf(channelId),
855 String.valueOf(watchStarttime),
856 String.valueOf(watchStarttime)
857 };
858 String sortOrder = TvContract.Programs.START_TIME_UTC_MILLIS + " ASC";
859 Cursor cursor = null;
860 try {
861 cursor = mContentResolver.query(TvContract.Programs.CONTENT_URI, projection,
862 selection, selectionArgs, sortOrder);
863 if (cursor != null && cursor.moveToNext()) {
864 ContentValues values = new ContentValues();
865 values.put(TvContract.WatchedPrograms.TITLE, cursor.getString(0));
866 values.put(TvContract.WatchedPrograms.START_TIME_UTC_MILLIS, cursor.getLong(1));
867 long endTime = cursor.getLong(2);
868 values.put(TvContract.WatchedPrograms.END_TIME_UTC_MILLIS, endTime);
869 values.put(TvContract.WatchedPrograms.DESCRIPTION, cursor.getString(3));
870 mContentResolver.update(uri, values, null, null);
871
872 // Schedule an update when the current program ends.
873 SomeArgs args = SomeArgs.obtain();
874 args.arg1 = uri;
875 args.arg2 = channelId;
876 args.arg3 = endTime;
877 Message msg = obtainMessage(LogHandler.MSG_UPDATE_ENTRY, args);
878 sendMessageDelayed(msg, endTime - System.currentTimeMillis());
879 }
880 } finally {
881 if (cursor != null) {
882 cursor.close();
883 }
884 }
885 }
886
887 private void onUpdateEntry(Uri uri, long channelId, long time) {
888 String[] projection = {
889 TvContract.WatchedPrograms.WATCH_START_TIME_UTC_MILLIS,
890 TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS,
891 TvContract.WatchedPrograms.TITLE,
892 TvContract.WatchedPrograms.START_TIME_UTC_MILLIS,
893 TvContract.WatchedPrograms.END_TIME_UTC_MILLIS,
894 TvContract.WatchedPrograms.DESCRIPTION
895 };
896 Cursor cursor = null;
897 try {
898 cursor = mContentResolver.query(uri, projection, null, null, null);
899 if (cursor != null && cursor.moveToNext()) {
900 long watchStartTime = cursor.getLong(0);
901 long watchEndTime = cursor.getLong(1);
902 String title = cursor.getString(2);
903 long startTime = cursor.getLong(3);
904 long endTime = cursor.getLong(4);
905 String description = cursor.getString(5);
906
907 // Do nothing if the current log entry is already closed.
908 if (watchEndTime > 0) {
909 return;
910 }
911
912 // The current program has just ended. Create a (complete) log entry off the
913 // current entry.
914 ContentValues values = new ContentValues();
915 values.put(TvContract.WatchedPrograms.WATCH_START_TIME_UTC_MILLIS,
916 watchStartTime);
917 values.put(TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS, time);
918 values.put(TvContract.WatchedPrograms.CHANNEL_ID, channelId);
919 values.put(TvContract.WatchedPrograms.TITLE, title);
920 values.put(TvContract.WatchedPrograms.START_TIME_UTC_MILLIS, startTime);
921 values.put(TvContract.WatchedPrograms.END_TIME_UTC_MILLIS, endTime);
922 values.put(TvContract.WatchedPrograms.DESCRIPTION, description);
923 mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
924 }
925 } finally {
926 if (cursor != null) {
927 cursor.close();
928 }
929 }
930 // Re-open the current log entry with the next program information.
931 onOpenEntry(uri, channelId, time);
932 }
933
934 private void onCloseEntry(Uri uri, long watchEndTime) {
935 ContentValues values = new ContentValues();
936 values.put(TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS, watchEndTime);
937 mContentResolver.update(uri, values, null, null);
938 }
939 }
Jae Seo39570912014-02-20 18:23:25 -0800940}