blob: 59722472bbf8c5f0a4d2bcb71c095019a3945b0c [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
Wonsik Kim969167d2014-06-24 16:33:17 +090019import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
Wonsik Kime92f8572014-08-12 18:30:24 +090020import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
Wonsik Kim969167d2014-06-24 16:33:17 +090021
Jae Seo39570912014-02-20 18:23:25 -080022import android.app.ActivityManager;
23import android.content.BroadcastReceiver;
24import android.content.ComponentName;
Jae Seo5c80ad22014-06-12 19:52:58 -070025import android.content.ContentProviderOperation;
26import android.content.ContentProviderResult;
Jae Seo31dc634be2014-04-15 17:40:23 -070027import android.content.ContentResolver;
28import android.content.ContentUris;
29import android.content.ContentValues;
Jae Seo39570912014-02-20 18:23:25 -080030import android.content.Context;
31import android.content.Intent;
32import android.content.IntentFilter;
Jae Seo5c80ad22014-06-12 19:52:58 -070033import android.content.OperationApplicationException;
Jae Seo39570912014-02-20 18:23:25 -080034import android.content.ServiceConnection;
Jae Seo9c165d62014-08-25 14:39:26 -070035import android.content.pm.ActivityInfo;
Jae Seo39570912014-02-20 18:23:25 -080036import android.content.pm.PackageManager;
37import android.content.pm.ResolveInfo;
38import android.content.pm.ServiceInfo;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090039import android.graphics.Rect;
Wonsik Kime92f8572014-08-12 18:30:24 +090040import android.hardware.hdmi.HdmiControlManager;
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090041import android.hardware.hdmi.HdmiDeviceInfo;
Jae Seod5cc4a22014-05-30 16:57:43 -070042import android.media.tv.ITvInputClient;
43import android.media.tv.ITvInputHardware;
44import android.media.tv.ITvInputHardwareCallback;
45import android.media.tv.ITvInputManager;
Wonsik Kim969167d2014-06-24 16:33:17 +090046import android.media.tv.ITvInputManagerCallback;
Jae Seod5cc4a22014-05-30 16:57:43 -070047import android.media.tv.ITvInputService;
48import android.media.tv.ITvInputServiceCallback;
49import android.media.tv.ITvInputSession;
50import android.media.tv.ITvInputSessionCallback;
Jae Seo783645e2014-07-28 17:30:50 +090051import android.media.tv.TvContentRating;
Jae Seo9c165d62014-08-25 14:39:26 -070052import android.media.tv.TvContentRatingSystemInfo;
Jae Seod5cc4a22014-05-30 16:57:43 -070053import android.media.tv.TvContract;
54import android.media.tv.TvInputHardwareInfo;
55import android.media.tv.TvInputInfo;
Jae Seo9c165d62014-08-25 14:39:26 -070056import android.media.tv.TvInputManager;
Jae Seod5cc4a22014-05-30 16:57:43 -070057import android.media.tv.TvInputService;
Terry Heoc086a3d2014-06-18 14:26:44 +090058import android.media.tv.TvStreamConfig;
Dongwon Kang1f213912014-07-02 18:35:08 +090059import android.media.tv.TvTrackInfo;
Jae Seo39570912014-02-20 18:23:25 -080060import android.net.Uri;
61import android.os.Binder;
Youngsang Cho832860f2014-05-21 20:54:03 +090062import android.os.Bundle;
Jae Seo31dc634be2014-04-15 17:40:23 -070063import android.os.Handler;
Jae Seo39570912014-02-20 18:23:25 -080064import android.os.IBinder;
Jae Seo31dc634be2014-04-15 17:40:23 -070065import android.os.Looper;
66import android.os.Message;
Jae Seo39570912014-02-20 18:23:25 -080067import android.os.Process;
68import android.os.RemoteException;
69import android.os.UserHandle;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090070import android.util.Slog;
Jae Seo39570912014-02-20 18:23:25 -080071import android.util.SparseArray;
Jae Seo6a6059a2014-04-17 21:35:29 -070072import android.view.InputChannel;
Jae Seo39570912014-02-20 18:23:25 -080073import android.view.Surface;
74
75import com.android.internal.content.PackageMonitor;
Jae Seo31dc634be2014-04-15 17:40:23 -070076import com.android.internal.os.SomeArgs;
Jaewan Kime14c3f42014-06-27 13:47:48 +090077import com.android.internal.util.IndentingPrintWriter;
Jae Seo31dc634be2014-04-15 17:40:23 -070078import com.android.server.IoThread;
Jae Seo39570912014-02-20 18:23:25 -080079import com.android.server.SystemService;
80
Chulwoo Leee7bb7d62014-05-27 14:10:37 +090081import org.xmlpull.v1.XmlPullParserException;
82
Jaewan Kime14c3f42014-06-27 13:47:48 +090083import java.io.FileDescriptor;
Chulwoo Leee7bb7d62014-05-27 14:10:37 +090084import java.io.IOException;
Jaewan Kime14c3f42014-06-27 13:47:48 +090085import java.io.PrintWriter;
Jae Seo39570912014-02-20 18:23:25 -080086import java.util.ArrayList;
Chulwoo Lee19ba61a2014-09-03 00:59:35 +090087import java.util.Arrays;
Jae Seo39570912014-02-20 18:23:25 -080088import java.util.HashMap;
Jae Seo5c80ad22014-06-12 19:52:58 -070089import java.util.HashSet;
Wonsik Kim187423c2014-06-25 14:12:48 +090090import java.util.Iterator;
Jae Seo39570912014-02-20 18:23:25 -080091import java.util.List;
92import java.util.Map;
Jae Seo5c80ad22014-06-12 19:52:58 -070093import java.util.Set;
Jae Seo39570912014-02-20 18:23:25 -080094
95/** This class provides a system service that manages television inputs. */
96public final class TvInputManagerService extends SystemService {
Jae Seoee2ec052014-09-14 10:30:05 -070097 private static final boolean DEBUG = false;
Jae Seo39570912014-02-20 18:23:25 -080098 private static final String TAG = "TvInputManagerService";
99
100 private final Context mContext;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000101 private final TvInputHardwareManager mTvInputHardwareManager;
Jae Seo39570912014-02-20 18:23:25 -0800102
Jae Seo31dc634be2014-04-15 17:40:23 -0700103 private final ContentResolver mContentResolver;
104
Jae Seo39570912014-02-20 18:23:25 -0800105 // A global lock.
106 private final Object mLock = new Object();
107
108 // ID of the current user.
109 private int mCurrentUserId = UserHandle.USER_OWNER;
110
111 // A map from user id to UserState.
112 private final SparseArray<UserState> mUserStates = new SparseArray<UserState>();
113
Jae Seo7eb75df2014-08-08 22:20:48 -0700114 private final WatchLogHandler mWatchLogHandler;
Jae Seo31dc634be2014-04-15 17:40:23 -0700115
Jae Seo39570912014-02-20 18:23:25 -0800116 public TvInputManagerService(Context context) {
117 super(context);
Jae Seo31dc634be2014-04-15 17:40:23 -0700118
Jae Seo39570912014-02-20 18:23:25 -0800119 mContext = context;
Jae Seo31dc634be2014-04-15 17:40:23 -0700120 mContentResolver = context.getContentResolver();
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900121 mWatchLogHandler = new WatchLogHandler(mContentResolver, IoThread.get().getLooper());
Jae Seo31dc634be2014-04-15 17:40:23 -0700122
Wonsik Kim187423c2014-06-25 14:12:48 +0900123 mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
Jae Seo31dc634be2014-04-15 17:40:23 -0700124
Jae Seo39570912014-02-20 18:23:25 -0800125 synchronized (mLock) {
Jae Seo783645e2014-07-28 17:30:50 +0900126 mUserStates.put(mCurrentUserId, new UserState(mContext, mCurrentUserId));
Jae Seo39570912014-02-20 18:23:25 -0800127 }
128 }
129
130 @Override
131 public void onStart() {
132 publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());
133 }
134
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900135 @Override
136 public void onBootPhase(int phase) {
137 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
138 registerBroadcastReceivers();
Wonsik Kim187423c2014-06-25 14:12:48 +0900139 } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900140 synchronized (mLock) {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900141 buildTvInputListLocked(mCurrentUserId, null);
Jae Seo9c165d62014-08-25 14:39:26 -0700142 buildTvContentRatingSystemListLocked(mCurrentUserId);
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900143 }
144 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900145 mTvInputHardwareManager.onBootPhase(phase);
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900146 }
147
Jae Seo39570912014-02-20 18:23:25 -0800148 private void registerBroadcastReceivers() {
149 PackageMonitor monitor = new PackageMonitor() {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900150 private void buildTvInputList(String[] packages) {
151 synchronized (mLock) {
152 buildTvInputListLocked(getChangingUserId(), packages);
153 buildTvContentRatingSystemListLocked(getChangingUserId());
154 }
155 }
156
157 @Override
158 public void onPackageUpdateFinished(String packageName, int uid) {
159 if (DEBUG) Slog.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")");
160 // This callback is invoked when the TV input is reinstalled.
161 // In this case, isReplacing() always returns true.
162 buildTvInputList(new String[] { packageName });
163 }
164
165 @Override
166 public void onPackagesAvailable(String[] packages) {
167 if (DEBUG) {
168 Slog.d(TAG, "onPackagesAvailable(packages=" + Arrays.toString(packages) + ")");
169 }
170 // This callback is invoked when the media on which some packages exist become
171 // available.
172 if (isReplacing()) {
173 buildTvInputList(packages);
174 }
175 }
176
177 @Override
178 public void onPackagesUnavailable(String[] packages) {
179 // This callback is invoked when the media on which some packages exist become
180 // unavailable.
181 if (DEBUG) {
182 Slog.d(TAG, "onPackagesUnavailable(packages=" + Arrays.toString(packages)
183 + ")");
184 }
185 if (isReplacing()) {
186 buildTvInputList(packages);
187 }
188 }
189
Jae Seo39570912014-02-20 18:23:25 -0800190 @Override
191 public void onSomePackagesChanged() {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900192 // TODO: Use finer-grained methods(e.g. onPackageAdded, onPackageRemoved) to manage
193 // the TV inputs.
Dongwon Kang426c9a42014-08-26 17:39:21 +0900194 if (DEBUG) Slog.d(TAG, "onSomePackagesChanged()");
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900195 if (isReplacing()) {
196 if (DEBUG) Slog.d(TAG, "Skipped building TV input list due to replacing");
197 // When the package is updated, buildTvInputListLocked is called in other
198 // methods instead.
199 return;
Jae Seo39570912014-02-20 18:23:25 -0800200 }
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900201 buildTvInputList(null);
Jae Seo39570912014-02-20 18:23:25 -0800202 }
Jae Seo5c80ad22014-06-12 19:52:58 -0700203
204 @Override
Dongwon Kang31a8f842015-04-08 18:26:23 -0700205 public boolean onPackageChanged(String packageName, int uid, String[] components) {
206 // The input list needs to be updated in any cases, regardless of whether
207 // it happened to the whole package or a specific component. Returning true so that
208 // the update can be handled in {@link #onSomePackagesChanged}.
209 return true;
210 }
211
212 @Override
Jae Seo5c80ad22014-06-12 19:52:58 -0700213 public void onPackageRemoved(String packageName, int uid) {
214 synchronized (mLock) {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900215 UserState userState = getUserStateLocked(getChangingUserId());
Wonsik Kim969167d2014-06-24 16:33:17 +0900216 if (!userState.packageSet.contains(packageName)) {
Jae Seo5c80ad22014-06-12 19:52:58 -0700217 // Not a TV input package.
218 return;
219 }
220 }
221
222 ArrayList<ContentProviderOperation> operations =
223 new ArrayList<ContentProviderOperation>();
224
225 String selection = TvContract.BaseTvColumns.COLUMN_PACKAGE_NAME + "=?";
226 String[] selectionArgs = { packageName };
227
228 operations.add(ContentProviderOperation.newDelete(TvContract.Channels.CONTENT_URI)
229 .withSelection(selection, selectionArgs).build());
230 operations.add(ContentProviderOperation.newDelete(TvContract.Programs.CONTENT_URI)
231 .withSelection(selection, selectionArgs).build());
232 operations.add(ContentProviderOperation
233 .newDelete(TvContract.WatchedPrograms.CONTENT_URI)
234 .withSelection(selection, selectionArgs).build());
235
236 ContentProviderResult[] results = null;
237 try {
238 results = mContentResolver.applyBatch(TvContract.AUTHORITY, operations);
239 } catch (RemoteException | OperationApplicationException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700240 Slog.e(TAG, "error in applyBatch", e);
Jae Seo5c80ad22014-06-12 19:52:58 -0700241 }
242
243 if (DEBUG) {
244 Slog.d(TAG, "onPackageRemoved(packageName=" + packageName + ", uid=" + uid
245 + ")");
246 Slog.d(TAG, "results=" + results);
247 }
248 }
Jae Seo39570912014-02-20 18:23:25 -0800249 };
250 monitor.register(mContext, null, UserHandle.ALL, true);
251
252 IntentFilter intentFilter = new IntentFilter();
253 intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
254 intentFilter.addAction(Intent.ACTION_USER_REMOVED);
255 mContext.registerReceiverAsUser(new BroadcastReceiver() {
256 @Override
257 public void onReceive(Context context, Intent intent) {
258 String action = intent.getAction();
259 if (Intent.ACTION_USER_SWITCHED.equals(action)) {
260 switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
261 } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
262 removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
263 }
264 }
265 }, UserHandle.ALL, intentFilter, null, null);
266 }
267
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900268 private static boolean hasHardwarePermission(PackageManager pm, ComponentName component) {
Wonsik Kim187423c2014-06-25 14:12:48 +0900269 return pm.checkPermission(android.Manifest.permission.TV_INPUT_HARDWARE,
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900270 component.getPackageName()) == PackageManager.PERMISSION_GRANTED;
Wonsik Kim187423c2014-06-25 14:12:48 +0900271 }
272
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900273 private void buildTvInputListLocked(int userId, String[] updatedPackages) {
Jae Seo39570912014-02-20 18:23:25 -0800274 UserState userState = getUserStateLocked(userId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900275 userState.packageSet.clear();
Jae Seo39570912014-02-20 18:23:25 -0800276
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900277 if (DEBUG) Slog.d(TAG, "buildTvInputList");
Jae Seo39570912014-02-20 18:23:25 -0800278 PackageManager pm = mContext.getPackageManager();
279 List<ResolveInfo> services = pm.queryIntentServices(
Chulwoo Leee7bb7d62014-05-27 14:10:37 +0900280 new Intent(TvInputService.SERVICE_INTERFACE),
281 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900282 List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
Jae Seo39570912014-02-20 18:23:25 -0800283 for (ResolveInfo ri : services) {
284 ServiceInfo si = ri.serviceInfo;
285 if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900286 Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
Jae Seo39570912014-02-20 18:23:25 -0800287 + android.Manifest.permission.BIND_TV_INPUT);
288 continue;
289 }
Jae Seo9cc28e52014-08-12 16:45:58 -0700290
291 ComponentName component = new ComponentName(si.packageName, si.name);
292 if (hasHardwarePermission(pm, component)) {
293 ServiceState serviceState = userState.serviceStateMap.get(component);
294 if (serviceState == null) {
295 // We see this hardware TV input service for the first time; we need to
296 // prepare the ServiceState object so that we can connect to the service and
297 // let it add TvInputInfo objects to mInputList if there's any.
298 serviceState = new ServiceState(component, userId);
299 userState.serviceStateMap.put(component, serviceState);
Wonsik Kimf271eac2014-08-30 12:55:10 +0900300 updateServiceConnectionLocked(component, userId);
Wonsik Kim187423c2014-06-25 14:12:48 +0900301 } else {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900302 inputList.addAll(serviceState.inputList);
Jae Seo9cc28e52014-08-12 16:45:58 -0700303 }
304 } else {
305 try {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900306 inputList.add(TvInputInfo.createTvInputInfo(mContext, ri));
Jae Seo9cc28e52014-08-12 16:45:58 -0700307 } catch (XmlPullParserException | IOException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700308 Slog.e(TAG, "failed to load TV input " + si.name, e);
Jae Seo9cc28e52014-08-12 16:45:58 -0700309 continue;
Wonsik Kim969167d2014-06-24 16:33:17 +0900310 }
Chulwoo Leee7bb7d62014-05-27 14:10:37 +0900311 }
Jae Seo9cc28e52014-08-12 16:45:58 -0700312 userState.packageSet.add(si.packageName);
313 }
314
315 Map<String, TvInputState> inputMap = new HashMap<String, TvInputState>();
316 for (TvInputInfo info : inputList) {
Jae Seofea8dd42014-08-26 13:57:41 -0700317 if (DEBUG) {
318 Slog.d(TAG, "add " + info.getId());
319 }
Jae Seo9cc28e52014-08-12 16:45:58 -0700320 TvInputState state = userState.inputMap.get(info.getId());
321 if (state == null) {
322 state = new TvInputState();
323 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900324 state.info = info;
Jae Seo9cc28e52014-08-12 16:45:58 -0700325 inputMap.put(info.getId(), state);
Jae Seo39570912014-02-20 18:23:25 -0800326 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900327
328 for (String inputId : inputMap.keySet()) {
329 if (!userState.inputMap.containsKey(inputId)) {
330 notifyInputAddedLocked(userState, inputId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900331 } else if (updatedPackages != null) {
332 // Notify the package updates
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +0900333 ComponentName component = inputMap.get(inputId).info.getComponent();
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900334 for (String updatedPackage : updatedPackages) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +0900335 if (component.getPackageName().equals(updatedPackage)) {
336 updateServiceConnectionLocked(component, userId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900337 notifyInputUpdatedLocked(userState, inputId);
338 break;
339 }
340 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900341 }
342 }
343
344 for (String inputId : userState.inputMap.keySet()) {
345 if (!inputMap.containsKey(inputId)) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900346 TvInputInfo info = userState.inputMap.get(inputId).info;
Dongwon Kang426c9a42014-08-26 17:39:21 +0900347 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
348 if (serviceState != null) {
349 abortPendingCreateSessionRequestsLocked(serviceState, inputId, userId);
350 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900351 notifyInputRemovedLocked(userState, inputId);
352 }
353 }
354
355 userState.inputMap.clear();
356 userState.inputMap = inputMap;
Jae Seo9c165d62014-08-25 14:39:26 -0700357 }
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900358
Jae Seo9c165d62014-08-25 14:39:26 -0700359 private void buildTvContentRatingSystemListLocked(int userId) {
360 UserState userState = getUserStateLocked(userId);
361 userState.contentRatingSystemList.clear();
362
363 final PackageManager pm = mContext.getPackageManager();
364 Intent intent = new Intent(TvInputManager.ACTION_QUERY_CONTENT_RATING_SYSTEMS);
365 for (ResolveInfo resolveInfo :
366 pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA)) {
367 ActivityInfo receiver = resolveInfo.activityInfo;
368 Bundle metaData = receiver.metaData;
369 if (metaData == null) {
370 continue;
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900371 }
Jae Seo9c165d62014-08-25 14:39:26 -0700372
373 int xmlResId = metaData.getInt(TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS);
374 if (xmlResId == 0) {
375 Slog.w(TAG, "Missing meta-data '"
376 + TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS + "' on receiver "
377 + receiver.packageName + "/" + receiver.name);
378 continue;
379 }
380 userState.contentRatingSystemList.add(
381 TvContentRatingSystemInfo.createTvContentRatingSystemInfo(xmlResId,
382 receiver.applicationInfo));
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900383 }
Jae Seo39570912014-02-20 18:23:25 -0800384 }
385
386 private void switchUser(int userId) {
387 synchronized (mLock) {
388 if (mCurrentUserId == userId) {
389 return;
390 }
391 // final int oldUserId = mCurrentUserId;
392 // TODO: Release services and sessions in the old user state, if needed.
393 mCurrentUserId = userId;
394
395 UserState userState = mUserStates.get(userId);
396 if (userState == null) {
Jae Seo783645e2014-07-28 17:30:50 +0900397 userState = new UserState(mContext, userId);
Jae Seo39570912014-02-20 18:23:25 -0800398 }
399 mUserStates.put(userId, userState);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900400 buildTvInputListLocked(userId, null);
Jae Seo9c165d62014-08-25 14:39:26 -0700401 buildTvContentRatingSystemListLocked(userId);
Jae Seo39570912014-02-20 18:23:25 -0800402 }
403 }
404
405 private void removeUser(int userId) {
406 synchronized (mLock) {
Jae Seob06cb882014-04-09 12:08:17 -0700407 UserState userState = mUserStates.get(userId);
408 if (userState == null) {
409 return;
410 }
Jae Seo39570912014-02-20 18:23:25 -0800411 // Release created sessions.
Jae Seo39570912014-02-20 18:23:25 -0800412 for (SessionState state : userState.sessionStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900413 if (state.session != null) {
Jae Seo39570912014-02-20 18:23:25 -0800414 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900415 state.session.release();
Jae Seo39570912014-02-20 18:23:25 -0800416 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900417 Slog.e(TAG, "error in release", e);
Jae Seo39570912014-02-20 18:23:25 -0800418 }
419 }
420 }
421 userState.sessionStateMap.clear();
422
423 // Unregister all callbacks and unbind all services.
424 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900425 if (serviceState.callback != null) {
Jae Seo39570912014-02-20 18:23:25 -0800426 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900427 serviceState.service.unregisterCallback(serviceState.callback);
Jae Seo39570912014-02-20 18:23:25 -0800428 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900429 Slog.e(TAG, "error in unregisterCallback", e);
Jae Seo39570912014-02-20 18:23:25 -0800430 }
431 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900432 mContext.unbindService(serviceState.connection);
Jae Seo39570912014-02-20 18:23:25 -0800433 }
434 userState.serviceStateMap.clear();
435
Jae Seofea8dd42014-08-26 13:57:41 -0700436 // Clear everything else.
437 userState.inputMap.clear();
438 userState.packageSet.clear();
Jae Seo9c165d62014-08-25 14:39:26 -0700439 userState.contentRatingSystemList.clear();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900440 userState.clientStateMap.clear();
Jae Seofea8dd42014-08-26 13:57:41 -0700441 userState.callbackSet.clear();
442 userState.mainSessionToken = null;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900443
Jae Seo39570912014-02-20 18:23:25 -0800444 mUserStates.remove(userId);
445 }
446 }
447
448 private UserState getUserStateLocked(int userId) {
449 UserState userState = mUserStates.get(userId);
450 if (userState == null) {
451 throw new IllegalStateException("User state not found for user ID " + userId);
452 }
453 return userState;
454 }
455
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900456 private ServiceState getServiceStateLocked(ComponentName component, int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800457 UserState userState = getUserStateLocked(userId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900458 ServiceState serviceState = userState.serviceStateMap.get(component);
Jae Seo39570912014-02-20 18:23:25 -0800459 if (serviceState == null) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900460 throw new IllegalStateException("Service state not found for " + component + " (userId="
Sungsoo Lim7de5e232014-04-12 16:51:27 +0900461 + userId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800462 }
463 return serviceState;
464 }
465
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900466 private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid, int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800467 UserState userState = getUserStateLocked(userId);
468 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
469 if (sessionState == null) {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900470 throw new SessionNotFoundException("Session state not found for token " + sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800471 }
472 // Only the application that requested this session or the system can access it.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900473 if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) {
Jae Seo39570912014-02-20 18:23:25 -0800474 throw new SecurityException("Illegal access to the session with token " + sessionToken
475 + " from uid " + callingUid);
476 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900477 return sessionState;
478 }
479
480 private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900481 return getSessionLocked(getSessionStateLocked(sessionToken, callingUid, userId));
482 }
483
484 private ITvInputSession getSessionLocked(SessionState sessionState) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900485 ITvInputSession session = sessionState.session;
Jae Seo39570912014-02-20 18:23:25 -0800486 if (session == null) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900487 throw new IllegalStateException("Session not yet created for token "
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900488 + sessionState.sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800489 }
490 return session;
491 }
492
493 private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
494 String methodName) {
495 return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
496 false, methodName, null);
497 }
498
Wonsik Kim187423c2014-06-25 14:12:48 +0900499 private static boolean shouldMaintainConnection(ServiceState serviceState) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900500 return !serviceState.sessionTokens.isEmpty() || serviceState.isHardware;
501 // TODO: Find a way to maintain connection to hardware TV input service only when necessary.
Wonsik Kim187423c2014-06-25 14:12:48 +0900502 }
503
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900504 private void updateServiceConnectionLocked(ComponentName component, int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800505 UserState userState = getUserStateLocked(userId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900506 ServiceState serviceState = userState.serviceStateMap.get(component);
Jae Seo39570912014-02-20 18:23:25 -0800507 if (serviceState == null) {
508 return;
509 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900510 if (serviceState.reconnecting) {
511 if (!serviceState.sessionTokens.isEmpty()) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900512 // wait until all the sessions are removed.
513 return;
514 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900515 serviceState.reconnecting = false;
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900516 }
Wonsik Kim187423c2014-06-25 14:12:48 +0900517 boolean maintainConnection = shouldMaintainConnection(serviceState);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900518 if (serviceState.service == null && maintainConnection && userId == mCurrentUserId) {
Jae Seo39570912014-02-20 18:23:25 -0800519 // This means that the service is not yet connected but its state indicates that we
520 // have pending requests. Then, connect the service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900521 if (serviceState.bound) {
Jae Seo39570912014-02-20 18:23:25 -0800522 // We have already bound to the service so we don't try to bind again until after we
523 // unbind later on.
524 return;
525 }
526 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900527 Slog.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800528 }
Sungsoo Limd6672b52014-04-30 10:43:26 +0900529
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900530 Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900531 serviceState.bound = mContext.bindServiceAsUser(
532 i, serviceState.connection, Context.BIND_AUTO_CREATE, new UserHandle(userId));
533 } else if (serviceState.service != null && !maintainConnection) {
Jae Seo39570912014-02-20 18:23:25 -0800534 // This means that the service is already connected but its state indicates that we have
535 // nothing to do with it. Then, disconnect the service.
536 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900537 Slog.d(TAG, "unbindService(service=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -0800538 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900539 mContext.unbindService(serviceState.connection);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900540 userState.serviceStateMap.remove(component);
Jae Seo39570912014-02-20 18:23:25 -0800541 }
542 }
543
Dongwon Kang426c9a42014-08-26 17:39:21 +0900544 private void abortPendingCreateSessionRequestsLocked(ServiceState serviceState,
545 String inputId, int userId) {
546 // Let clients know the create session requests are failed.
547 UserState userState = getUserStateLocked(userId);
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900548 List<SessionState> sessionsToAbort = new ArrayList<>();
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900549 for (IBinder sessionToken : serviceState.sessionTokens) {
Dongwon Kang426c9a42014-08-26 17:39:21 +0900550 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900551 if (sessionState.session == null && (inputId == null
552 || sessionState.info.getId().equals(inputId))) {
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900553 sessionsToAbort.add(sessionState);
Dongwon Kang426c9a42014-08-26 17:39:21 +0900554 }
555 }
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900556 for (SessionState sessionState : sessionsToAbort) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900557 removeSessionStateLocked(sessionState.sessionToken, sessionState.userId);
558 sendSessionTokenToClientLocked(sessionState.client,
559 sessionState.info.getId(), null, null, sessionState.seq);
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900560 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900561 updateServiceConnectionLocked(serviceState.component, userId);
Dongwon Kang426c9a42014-08-26 17:39:21 +0900562 }
563
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900564 private void createSessionInternalLocked(ITvInputService service, IBinder sessionToken,
565 int userId) {
566 UserState userState = getUserStateLocked(userId);
567 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800568 if (DEBUG) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900569 Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.info.getId() + ")");
Jae Seo39570912014-02-20 18:23:25 -0800570 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900571 InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
Jae Seo6a6059a2014-04-17 21:35:29 -0700572
Jae Seo39570912014-02-20 18:23:25 -0800573 // Set up a callback to send the session token.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900574 ITvInputSessionCallback callback = new SessionCallback(sessionState, channels);
Jae Seo39570912014-02-20 18:23:25 -0800575
576 // Create a session. When failed, send a null token immediately.
577 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900578 service.createSession(channels[1], callback, sessionState.info.getId());
Jae Seo39570912014-02-20 18:23:25 -0800579 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900580 Slog.e(TAG, "error in createSession", e);
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900581 removeSessionStateLocked(sessionToken, userId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900582 sendSessionTokenToClientLocked(sessionState.client, sessionState.info.getId(), null,
583 null, sessionState.seq);
Jae Seo39570912014-02-20 18:23:25 -0800584 }
Jae Seo6a6059a2014-04-17 21:35:29 -0700585 channels[1].dispose();
Jae Seo39570912014-02-20 18:23:25 -0800586 }
587
Sungsoo Limd6672b52014-04-30 10:43:26 +0900588 private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId,
Jae Seo5c80ad22014-06-12 19:52:58 -0700589 IBinder sessionToken, InputChannel channel, int seq) {
Jae Seo39570912014-02-20 18:23:25 -0800590 try {
Sungsoo Limd6672b52014-04-30 10:43:26 +0900591 client.onSessionCreated(inputId, sessionToken, channel, seq);
Jae Seofea8dd42014-08-26 13:57:41 -0700592 } catch (RemoteException e) {
593 Slog.e(TAG, "error in onSessionCreated", e);
Jae Seo39570912014-02-20 18:23:25 -0800594 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900595 }
Jae Seo39570912014-02-20 18:23:25 -0800596
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900597 private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900598 SessionState sessionState = null;
599 try {
600 sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
601 if (sessionState.session != null) {
602 UserState userState = getUserStateLocked(userId);
603 if (sessionToken == userState.mainSessionToken) {
604 setMainLocked(sessionToken, false, callingUid, userId);
605 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900606 sessionState.session.release();
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900607 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900608 } catch (RemoteException | SessionNotFoundException e) {
609 Slog.e(TAG, "error in releaseSession", e);
610 } finally {
611 if (sessionState != null) {
612 sessionState.session = null;
613 }
Jae Seo39570912014-02-20 18:23:25 -0800614 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900615 removeSessionStateLocked(sessionToken, userId);
Jae Seo39570912014-02-20 18:23:25 -0800616 }
617
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900618 private void removeSessionStateLocked(IBinder sessionToken, int userId) {
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900619 UserState userState = getUserStateLocked(userId);
Ji-Hwan Leeabca0ee2014-07-24 17:34:19 +0900620 if (sessionToken == userState.mainSessionToken) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900621 if (DEBUG) {
622 Slog.d(TAG, "mainSessionToken=null");
623 }
Ji-Hwan Leeabca0ee2014-07-24 17:34:19 +0900624 userState.mainSessionToken = null;
625 }
626
627 // Remove the session state from the global session state map of the current user.
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900628 SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
629
Chulwoo Lee8d4ded02014-07-10 03:56:39 +0900630 if (sessionState == null) {
631 return;
632 }
633
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900634 // Also remove the session token from the session token list of the current client and
635 // service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900636 ClientState clientState = userState.clientStateMap.get(sessionState.client.asBinder());
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900637 if (clientState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900638 clientState.sessionTokens.remove(sessionToken);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900639 if (clientState.isEmpty()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900640 userState.clientStateMap.remove(sessionState.client.asBinder());
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900641 }
642 }
643
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900644 TvInputInfo info = sessionState.info;
Wonsik Kim187423c2014-06-25 14:12:48 +0900645 if (info != null) {
646 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
647 if (serviceState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900648 serviceState.sessionTokens.remove(sessionToken);
Wonsik Kim187423c2014-06-25 14:12:48 +0900649 }
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900650 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900651 updateServiceConnectionLocked(sessionState.info.getComponent(), userId);
Jae Seo7eb75df2014-08-08 22:20:48 -0700652
653 // Log the end of watch.
654 SomeArgs args = SomeArgs.obtain();
655 args.arg1 = sessionToken;
656 args.arg2 = System.currentTimeMillis();
657 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_END, args).sendToTarget();
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900658 }
659
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900660 private void setMainLocked(IBinder sessionToken, boolean isMain, int callingUid, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900661 try {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900662 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
663 if (sessionState.hardwareSessionToken != null) {
664 sessionState = getSessionStateLocked(sessionState.hardwareSessionToken,
665 Process.SYSTEM_UID, userId);
666 }
667 ServiceState serviceState = getServiceStateLocked(sessionState.info.getComponent(), userId);
668 if (!serviceState.isHardware) {
669 return;
670 }
671 ITvInputSession session = getSessionLocked(sessionState);
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900672 session.setMain(isMain);
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900673 } catch (RemoteException | SessionNotFoundException e) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900674 Slog.e(TAG, "error in setMain", e);
675 }
676 }
677
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900678 private void notifyInputAddedLocked(UserState userState, String inputId) {
679 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700680 Slog.d(TAG, "notifyInputAddedLocked(inputId=" + inputId + ")");
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900681 }
682 for (ITvInputManagerCallback callback : userState.callbackSet) {
683 try {
684 callback.onInputAdded(inputId);
685 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700686 Slog.e(TAG, "failed to report added input to callback", e);
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900687 }
688 }
689 }
690
691 private void notifyInputRemovedLocked(UserState userState, String inputId) {
692 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700693 Slog.d(TAG, "notifyInputRemovedLocked(inputId=" + inputId + ")");
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900694 }
695 for (ITvInputManagerCallback callback : userState.callbackSet) {
696 try {
697 callback.onInputRemoved(inputId);
698 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700699 Slog.e(TAG, "failed to report removed input to callback", e);
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900700 }
701 }
702 }
703
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900704 private void notifyInputUpdatedLocked(UserState userState, String inputId) {
705 if (DEBUG) {
706 Slog.d(TAG, "notifyInputUpdatedLocked(inputId=" + inputId + ")");
707 }
708 for (ITvInputManagerCallback callback : userState.callbackSet) {
709 try {
710 callback.onInputUpdated(inputId);
711 } catch (RemoteException e) {
712 Slog.e(TAG, "failed to report updated input to callback", e);
713 }
714 }
715 }
716
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900717 private void notifyInputStateChangedLocked(UserState userState, String inputId,
Wonsik Kim969167d2014-06-24 16:33:17 +0900718 int state, ITvInputManagerCallback targetCallback) {
719 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700720 Slog.d(TAG, "notifyInputStateChangedLocked(inputId=" + inputId
721 + ", state=" + state + ")");
Wonsik Kim969167d2014-06-24 16:33:17 +0900722 }
723 if (targetCallback == null) {
724 for (ITvInputManagerCallback callback : userState.callbackSet) {
725 try {
726 callback.onInputStateChanged(inputId, state);
727 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700728 Slog.e(TAG, "failed to report state change to callback", e);
Wonsik Kim969167d2014-06-24 16:33:17 +0900729 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900730 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900731 } else {
732 try {
733 targetCallback.onInputStateChanged(inputId, state);
734 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700735 Slog.e(TAG, "failed to report state change to callback", e);
Wonsik Kim969167d2014-06-24 16:33:17 +0900736 }
737 }
738 }
739
740 private void setStateLocked(String inputId, int state, int userId) {
741 UserState userState = getUserStateLocked(userId);
742 TvInputState inputState = userState.inputMap.get(inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900743 ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent());
744 int oldState = inputState.state;
745 inputState.state = state;
746 if (serviceState != null && serviceState.service == null
Wonsik Kim187423c2014-06-25 14:12:48 +0900747 && shouldMaintainConnection(serviceState)) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900748 // We don't notify state change while reconnecting. It should remain disconnected.
749 return;
750 }
751 if (oldState != state) {
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900752 notifyInputStateChangedLocked(userState, inputId, state, null);
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900753 }
754 }
755
Jae Seo39570912014-02-20 18:23:25 -0800756 private final class BinderService extends ITvInputManager.Stub {
757 @Override
758 public List<TvInputInfo> getTvInputList(int userId) {
759 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
760 Binder.getCallingUid(), userId, "getTvInputList");
761 final long identity = Binder.clearCallingIdentity();
762 try {
763 synchronized (mLock) {
764 UserState userState = getUserStateLocked(resolvedUserId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900765 List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
766 for (TvInputState state : userState.inputMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900767 inputList.add(state.info);
Jae Seo39570912014-02-20 18:23:25 -0800768 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900769 return inputList;
Jae Seo39570912014-02-20 18:23:25 -0800770 }
771 } finally {
772 Binder.restoreCallingIdentity(identity);
773 }
Jae Seo39570912014-02-20 18:23:25 -0800774 }
775
776 @Override
Jae Seob3758052014-07-12 19:25:24 -0700777 public TvInputInfo getTvInputInfo(String inputId, int userId) {
778 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
779 Binder.getCallingUid(), userId, "getTvInputInfo");
780 final long identity = Binder.clearCallingIdentity();
781 try {
782 synchronized (mLock) {
783 UserState userState = getUserStateLocked(resolvedUserId);
784 TvInputState state = userState.inputMap.get(inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900785 return state == null ? null : state.info;
Jae Seob3758052014-07-12 19:25:24 -0700786 }
787 } finally {
788 Binder.restoreCallingIdentity(identity);
789 }
790 }
791
792 @Override
Dongwon Kang993f81e2014-11-27 19:34:18 +0900793 public int getTvInputState(String inputId, int userId) {
794 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
795 Binder.getCallingUid(), userId, "getTvInputState");
796 final long identity = Binder.clearCallingIdentity();
797 try {
798 synchronized (mLock) {
799 UserState userState = getUserStateLocked(resolvedUserId);
800 TvInputState state = userState.inputMap.get(inputId);
Jae Seo82fce642015-04-20 15:37:50 -0700801 return state == null ? INPUT_STATE_CONNECTED : state.state;
Dongwon Kang993f81e2014-11-27 19:34:18 +0900802 }
803 } finally {
804 Binder.restoreCallingIdentity(identity);
805 }
806 }
807
808 @Override
Jae Seo9c165d62014-08-25 14:39:26 -0700809 public List<TvContentRatingSystemInfo> getTvContentRatingSystemList(int userId) {
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900810 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
Jae Seo9c165d62014-08-25 14:39:26 -0700811 Binder.getCallingUid(), userId, "getTvContentRatingSystemList");
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900812 final long identity = Binder.clearCallingIdentity();
813 try {
814 synchronized (mLock) {
815 UserState userState = getUserStateLocked(resolvedUserId);
Jae Seo9c165d62014-08-25 14:39:26 -0700816 return userState.contentRatingSystemList;
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900817 }
818 } finally {
819 Binder.restoreCallingIdentity(identity);
820 }
821 }
822
823 @Override
Wonsik Kim969167d2014-06-24 16:33:17 +0900824 public void registerCallback(final ITvInputManagerCallback callback, int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800825 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
826 Binder.getCallingUid(), userId, "registerCallback");
827 final long identity = Binder.clearCallingIdentity();
828 try {
829 synchronized (mLock) {
Jae Seofea8dd42014-08-26 13:57:41 -0700830 final UserState userState = getUserStateLocked(resolvedUserId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900831 userState.callbackSet.add(callback);
Jae Seofea8dd42014-08-26 13:57:41 -0700832 try {
833 callback.asBinder().linkToDeath(new IBinder.DeathRecipient() {
834 @Override
835 public void binderDied() {
836 synchronized (mLock) {
837 if (userState.callbackSet != null) {
838 userState.callbackSet.remove(callback);
839 }
840 }
841 }
842 }, 0);
843 } catch (RemoteException e) {
844 Slog.e(TAG, "client process has already died", e);
845 }
Jae Seo39570912014-02-20 18:23:25 -0800846 }
847 } finally {
848 Binder.restoreCallingIdentity(identity);
849 }
850 }
851
852 @Override
Wonsik Kim969167d2014-06-24 16:33:17 +0900853 public void unregisterCallback(ITvInputManagerCallback callback, int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800854 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
855 Binder.getCallingUid(), userId, "unregisterCallback");
856 final long identity = Binder.clearCallingIdentity();
857 try {
858 synchronized (mLock) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900859 UserState userState = getUserStateLocked(resolvedUserId);
860 userState.callbackSet.remove(callback);
Jae Seo39570912014-02-20 18:23:25 -0800861 }
862 } finally {
863 Binder.restoreCallingIdentity(identity);
864 }
865 }
866
867 @Override
Jae Seo783645e2014-07-28 17:30:50 +0900868 public boolean isParentalControlsEnabled(int userId) {
869 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
870 Binder.getCallingUid(), userId, "isParentalControlsEnabled");
871 final long identity = Binder.clearCallingIdentity();
872 try {
873 synchronized (mLock) {
874 UserState userState = getUserStateLocked(resolvedUserId);
875 return userState.persistentDataStore.isParentalControlsEnabled();
876 }
877 } finally {
878 Binder.restoreCallingIdentity(identity);
879 }
880 }
881
882 @Override
883 public void setParentalControlsEnabled(boolean enabled, int userId) {
884 ensureParentalControlsPermission();
885 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
886 Binder.getCallingUid(), userId, "setParentalControlsEnabled");
887 final long identity = Binder.clearCallingIdentity();
888 try {
889 synchronized (mLock) {
890 UserState userState = getUserStateLocked(resolvedUserId);
891 userState.persistentDataStore.setParentalControlsEnabled(enabled);
892 }
893 } finally {
894 Binder.restoreCallingIdentity(identity);
895 }
896 }
897
898 @Override
899 public boolean isRatingBlocked(String rating, int userId) {
900 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
901 Binder.getCallingUid(), userId, "isRatingBlocked");
902 final long identity = Binder.clearCallingIdentity();
903 try {
904 synchronized (mLock) {
905 UserState userState = getUserStateLocked(resolvedUserId);
906 return userState.persistentDataStore.isRatingBlocked(
907 TvContentRating.unflattenFromString(rating));
908 }
909 } finally {
910 Binder.restoreCallingIdentity(identity);
911 }
912 }
913
914 @Override
915 public List<String> getBlockedRatings(int userId) {
916 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
917 Binder.getCallingUid(), userId, "getBlockedRatings");
918 final long identity = Binder.clearCallingIdentity();
919 try {
920 synchronized (mLock) {
921 UserState userState = getUserStateLocked(resolvedUserId);
922 List<String> ratings = new ArrayList<String>();
923 for (TvContentRating rating
924 : userState.persistentDataStore.getBlockedRatings()) {
925 ratings.add(rating.flattenToString());
926 }
927 return ratings;
928 }
929 } finally {
930 Binder.restoreCallingIdentity(identity);
931 }
932 }
933
934 @Override
935 public void addBlockedRating(String rating, int userId) {
936 ensureParentalControlsPermission();
937 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
938 Binder.getCallingUid(), userId, "addBlockedRating");
939 final long identity = Binder.clearCallingIdentity();
940 try {
941 synchronized (mLock) {
942 UserState userState = getUserStateLocked(resolvedUserId);
943 userState.persistentDataStore.addBlockedRating(
944 TvContentRating.unflattenFromString(rating));
945 }
946 } finally {
947 Binder.restoreCallingIdentity(identity);
948 }
949 }
950
951 @Override
952 public void removeBlockedRating(String rating, int userId) {
953 ensureParentalControlsPermission();
954 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
955 Binder.getCallingUid(), userId, "removeBlockedRating");
956 final long identity = Binder.clearCallingIdentity();
957 try {
958 synchronized (mLock) {
959 UserState userState = getUserStateLocked(resolvedUserId);
960 userState.persistentDataStore.removeBlockedRating(
961 TvContentRating.unflattenFromString(rating));
962 }
963 } finally {
964 Binder.restoreCallingIdentity(identity);
965 }
966 }
967
968 private void ensureParentalControlsPermission() {
Jae Seofc836f62014-08-27 00:47:56 +0000969 if (mContext.checkCallingPermission(
970 android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
971 != PackageManager.PERMISSION_GRANTED) {
972 throw new SecurityException(
973 "The caller does not have parental controls permission");
974 }
Jae Seo783645e2014-07-28 17:30:50 +0900975 }
976
977 @Override
Sungsoo Limd6672b52014-04-30 10:43:26 +0900978 public void createSession(final ITvInputClient client, final String inputId,
Jae Seo39570912014-02-20 18:23:25 -0800979 int seq, int userId) {
980 final int callingUid = Binder.getCallingUid();
981 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
982 userId, "createSession");
983 final long identity = Binder.clearCallingIdentity();
984 try {
985 synchronized (mLock) {
Jae Seo39570912014-02-20 18:23:25 -0800986 UserState userState = getUserStateLocked(resolvedUserId);
Dongwon Kang426c9a42014-08-26 17:39:21 +0900987 TvInputState inputState = userState.inputMap.get(inputId);
988 if (inputState == null) {
989 Slog.w(TAG, "Failed to find input state for inputId=" + inputId);
990 sendSessionTokenToClientLocked(client, inputId, null, null, seq);
991 return;
992 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900993 TvInputInfo info = inputState.info;
Wonsik Kim187423c2014-06-25 14:12:48 +0900994 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
Jae Seo39570912014-02-20 18:23:25 -0800995 if (serviceState == null) {
Wonsik Kim187423c2014-06-25 14:12:48 +0900996 serviceState = new ServiceState(info.getComponent(), resolvedUserId);
997 userState.serviceStateMap.put(info.getComponent(), serviceState);
Jae Seo39570912014-02-20 18:23:25 -0800998 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900999 // Send a null token immediately while reconnecting.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001000 if (serviceState.reconnecting == true) {
Jae Seo5c80ad22014-06-12 19:52:58 -07001001 sendSessionTokenToClientLocked(client, inputId, null, null, seq);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001002 return;
1003 }
1004
1005 // Create a new session token and a session state.
1006 IBinder sessionToken = new Binder();
Wonsik Kim187423c2014-06-25 14:12:48 +09001007 SessionState sessionState = new SessionState(sessionToken, info, client,
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001008 seq, callingUid, resolvedUserId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001009
1010 // Add them to the global session state map of the current user.
1011 userState.sessionStateMap.put(sessionToken, sessionState);
1012
1013 // Also, add them to the session state map of the current service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001014 serviceState.sessionTokens.add(sessionToken);
Jae Seo39570912014-02-20 18:23:25 -08001015
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001016 if (serviceState.service != null) {
1017 createSessionInternalLocked(serviceState.service, sessionToken,
Sungsoo Lim7de5e232014-04-12 16:51:27 +09001018 resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001019 } else {
Wonsik Kim187423c2014-06-25 14:12:48 +09001020 updateServiceConnectionLocked(info.getComponent(), resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001021 }
1022 }
1023 } finally {
1024 Binder.restoreCallingIdentity(identity);
1025 }
1026 }
1027
1028 @Override
1029 public void releaseSession(IBinder sessionToken, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001030 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -07001031 Slog.d(TAG, "releaseSession(sessionToken=" + sessionToken + ")");
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001032 }
Jae Seo39570912014-02-20 18:23:25 -08001033 final int callingUid = Binder.getCallingUid();
1034 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1035 userId, "releaseSession");
1036 final long identity = Binder.clearCallingIdentity();
1037 try {
1038 synchronized (mLock) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001039 releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001040 }
1041 } finally {
1042 Binder.restoreCallingIdentity(identity);
1043 }
1044 }
1045
1046 @Override
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001047 public void setMainSession(IBinder sessionToken, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001048 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -07001049 Slog.d(TAG, "setMainSession(sessionToken=" + sessionToken + ")");
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001050 }
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001051 final int callingUid = Binder.getCallingUid();
1052 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1053 userId, "setMainSession");
1054 final long identity = Binder.clearCallingIdentity();
1055 try {
1056 synchronized (mLock) {
Ji-Hwan Lee982abe62014-07-24 17:11:03 +09001057 UserState userState = getUserStateLocked(resolvedUserId);
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001058 if (userState.mainSessionToken == sessionToken) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001059 return;
1060 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001061 if (DEBUG) {
1062 Slog.d(TAG, "mainSessionToken=" + sessionToken);
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001063 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001064 IBinder oldMainSessionToken = userState.mainSessionToken;
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001065 userState.mainSessionToken = sessionToken;
1066
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001067 // Inform the new main session first.
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001068 // See {@link TvInputService.Session#onSetMain}.
1069 if (sessionToken != null) {
1070 setMainLocked(sessionToken, true, callingUid, userId);
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001071 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001072 if (oldMainSessionToken != null) {
1073 setMainLocked(oldMainSessionToken, false, Process.SYSTEM_UID, userId);
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001074 }
1075 }
1076 } finally {
1077 Binder.restoreCallingIdentity(identity);
1078 }
1079 }
1080
1081 @Override
Jae Seo39570912014-02-20 18:23:25 -08001082 public void setSurface(IBinder sessionToken, Surface surface, int userId) {
1083 final int callingUid = Binder.getCallingUid();
1084 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1085 userId, "setSurface");
1086 final long identity = Binder.clearCallingIdentity();
1087 try {
1088 synchronized (mLock) {
1089 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001090 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1091 resolvedUserId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001092 if (sessionState.hardwareSessionToken == null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001093 getSessionLocked(sessionState).setSurface(surface);
1094 } else {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001095 getSessionLocked(sessionState.hardwareSessionToken,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001096 Process.SYSTEM_UID, resolvedUserId).setSurface(surface);
1097 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001098 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001099 Slog.e(TAG, "error in setSurface", e);
Jae Seo39570912014-02-20 18:23:25 -08001100 }
1101 }
1102 } finally {
Youngsang Chof8362062014-04-30 17:24:20 +09001103 if (surface != null) {
1104 // surface is not used in TvInputManagerService.
1105 surface.release();
1106 }
Jae Seo39570912014-02-20 18:23:25 -08001107 Binder.restoreCallingIdentity(identity);
1108 }
1109 }
1110
1111 @Override
Youngsang Choe821d712014-07-16 14:22:19 -07001112 public void dispatchSurfaceChanged(IBinder sessionToken, int format, int width,
1113 int height, int userId) {
1114 final int callingUid = Binder.getCallingUid();
1115 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1116 userId, "dispatchSurfaceChanged");
1117 final long identity = Binder.clearCallingIdentity();
1118 try {
1119 synchronized (mLock) {
1120 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001121 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1122 resolvedUserId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001123 getSessionLocked(sessionState).dispatchSurfaceChanged(format, width,
1124 height);
1125 if (sessionState.hardwareSessionToken != null) {
1126 getSessionLocked(sessionState.hardwareSessionToken, Process.SYSTEM_UID,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001127 resolvedUserId).dispatchSurfaceChanged(format, width, height);
1128 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001129 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Choe821d712014-07-16 14:22:19 -07001130 Slog.e(TAG, "error in dispatchSurfaceChanged", e);
1131 }
1132 }
1133 } finally {
1134 Binder.restoreCallingIdentity(identity);
1135 }
1136 }
1137
1138 @Override
Jae Seo39570912014-02-20 18:23:25 -08001139 public void setVolume(IBinder sessionToken, float volume, int userId) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001140 final float REMOTE_VOLUME_ON = 1.0f;
1141 final float REMOTE_VOLUME_OFF = 0f;
Jae Seo39570912014-02-20 18:23:25 -08001142 final int callingUid = Binder.getCallingUid();
1143 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1144 userId, "setVolume");
1145 final long identity = Binder.clearCallingIdentity();
1146 try {
1147 synchronized (mLock) {
1148 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001149 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1150 resolvedUserId);
1151 getSessionLocked(sessionState).setVolume(volume);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001152 if (sessionState.hardwareSessionToken != null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001153 // Here, we let the hardware session know only whether volume is on or
1154 // off to prevent that the volume is controlled in the both side.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001155 getSessionLocked(sessionState.hardwareSessionToken,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001156 Process.SYSTEM_UID, resolvedUserId).setVolume((volume > 0.0f)
1157 ? REMOTE_VOLUME_ON : REMOTE_VOLUME_OFF);
1158 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001159 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001160 Slog.e(TAG, "error in setVolume", e);
Jae Seo39570912014-02-20 18:23:25 -08001161 }
1162 }
1163 } finally {
1164 Binder.restoreCallingIdentity(identity);
1165 }
1166 }
1167
1168 @Override
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +09001169 public void tune(IBinder sessionToken, final Uri channelUri, Bundle params, int userId) {
Jae Seo39570912014-02-20 18:23:25 -08001170 final int callingUid = Binder.getCallingUid();
1171 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1172 userId, "tune");
1173 final long identity = Binder.clearCallingIdentity();
1174 try {
1175 synchronized (mLock) {
Jae Seo39570912014-02-20 18:23:25 -08001176 try {
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +09001177 getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(
1178 channelUri, params);
Jae Seoc22d0c02014-08-15 13:03:21 -07001179 if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
Youngsang Cho008f6d42014-07-22 21:29:47 -07001180 // Do not log the watch history for passthrough inputs.
1181 return;
1182 }
Jae Seo31dc634be2014-04-15 17:40:23 -07001183
Jae Seo31dc634be2014-04-15 17:40:23 -07001184 UserState userState = getUserStateLocked(resolvedUserId);
1185 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Jae Seo31dc634be2014-04-15 17:40:23 -07001186
Jae Seo7eb75df2014-08-08 22:20:48 -07001187 // Log the start of watch.
Jae Seo31dc634be2014-04-15 17:40:23 -07001188 SomeArgs args = SomeArgs.obtain();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001189 args.arg1 = sessionState.info.getComponent().getPackageName();
Jae Seo7eb75df2014-08-08 22:20:48 -07001190 args.arg2 = System.currentTimeMillis();
1191 args.arg3 = ContentUris.parseId(channelUri);
1192 args.arg4 = params;
1193 args.arg5 = sessionToken;
1194 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_START, args)
1195 .sendToTarget();
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001196 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001197 Slog.e(TAG, "error in tune", e);
Jae Seo39570912014-02-20 18:23:25 -08001198 return;
1199 }
1200 }
1201 } finally {
1202 Binder.restoreCallingIdentity(identity);
1203 }
1204 }
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001205
1206 @Override
Sungsoo Lim9bf671f2014-07-19 12:59:51 +09001207 public void requestUnblockContent(
1208 IBinder sessionToken, String unblockedRating, int userId) {
Jaewan Kim903d6b72014-07-16 11:28:56 +09001209 final int callingUid = Binder.getCallingUid();
1210 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1211 userId, "unblockContent");
1212 final long identity = Binder.clearCallingIdentity();
1213 try {
1214 synchronized (mLock) {
1215 try {
1216 getSessionLocked(sessionToken, callingUid, resolvedUserId)
Sungsoo Lim9bf671f2014-07-19 12:59:51 +09001217 .requestUnblockContent(unblockedRating);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001218 } catch (RemoteException | SessionNotFoundException e) {
Jae Seofea8dd42014-08-26 13:57:41 -07001219 Slog.e(TAG, "error in requestUnblockContent", e);
Jaewan Kim903d6b72014-07-16 11:28:56 +09001220 }
1221 }
1222 } finally {
1223 Binder.restoreCallingIdentity(identity);
1224 }
1225 }
1226
1227 @Override
Jae Seo2c1c31c2014-07-10 14:57:01 -07001228 public void setCaptionEnabled(IBinder sessionToken, boolean enabled, int userId) {
1229 final int callingUid = Binder.getCallingUid();
1230 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1231 userId, "setCaptionEnabled");
1232 final long identity = Binder.clearCallingIdentity();
1233 try {
1234 synchronized (mLock) {
1235 try {
1236 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1237 .setCaptionEnabled(enabled);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001238 } catch (RemoteException | SessionNotFoundException e) {
Jae Seo2c1c31c2014-07-10 14:57:01 -07001239 Slog.e(TAG, "error in setCaptionEnabled", e);
1240 }
1241 }
1242 } finally {
1243 Binder.restoreCallingIdentity(identity);
1244 }
1245 }
1246
1247 @Override
Jae Seo10d285a2014-07-31 22:46:47 +09001248 public void selectTrack(IBinder sessionToken, int type, String trackId, int userId) {
Dongwon Kang1f213912014-07-02 18:35:08 +09001249 final int callingUid = Binder.getCallingUid();
1250 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1251 userId, "selectTrack");
1252 final long identity = Binder.clearCallingIdentity();
1253 try {
1254 synchronized (mLock) {
1255 try {
1256 getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack(
Jae Seo10d285a2014-07-31 22:46:47 +09001257 type, trackId);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001258 } catch (RemoteException | SessionNotFoundException e) {
Dongwon Kang1f213912014-07-02 18:35:08 +09001259 Slog.e(TAG, "error in selectTrack", e);
1260 }
1261 }
1262 } finally {
1263 Binder.restoreCallingIdentity(identity);
1264 }
1265 }
1266
1267 @Override
Jae Seoa759b112014-07-18 22:16:08 -07001268 public void sendAppPrivateCommand(IBinder sessionToken, String command, Bundle data,
1269 int userId) {
1270 final int callingUid = Binder.getCallingUid();
1271 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1272 userId, "sendAppPrivateCommand");
1273 final long identity = Binder.clearCallingIdentity();
1274 try {
1275 synchronized (mLock) {
1276 try {
1277 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1278 .appPrivateCommand(command, data);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001279 } catch (RemoteException | SessionNotFoundException e) {
Jae Seofea8dd42014-08-26 13:57:41 -07001280 Slog.e(TAG, "error in appPrivateCommand", e);
Jae Seoa759b112014-07-18 22:16:08 -07001281 }
1282 }
1283 } finally {
1284 Binder.restoreCallingIdentity(identity);
1285 }
1286 }
1287
1288 @Override
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001289 public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
1290 int userId) {
1291 final int callingUid = Binder.getCallingUid();
1292 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1293 userId, "createOverlayView");
1294 final long identity = Binder.clearCallingIdentity();
1295 try {
1296 synchronized (mLock) {
1297 try {
1298 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1299 .createOverlayView(windowToken, frame);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001300 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001301 Slog.e(TAG, "error in createOverlayView", e);
1302 }
1303 }
1304 } finally {
1305 Binder.restoreCallingIdentity(identity);
1306 }
1307 }
1308
1309 @Override
1310 public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
1311 final int callingUid = Binder.getCallingUid();
1312 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1313 userId, "relayoutOverlayView");
1314 final long identity = Binder.clearCallingIdentity();
1315 try {
1316 synchronized (mLock) {
1317 try {
1318 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1319 .relayoutOverlayView(frame);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001320 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001321 Slog.e(TAG, "error in relayoutOverlayView", e);
1322 }
1323 }
1324 } finally {
1325 Binder.restoreCallingIdentity(identity);
1326 }
1327 }
1328
1329 @Override
1330 public void removeOverlayView(IBinder sessionToken, int userId) {
1331 final int callingUid = Binder.getCallingUid();
1332 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1333 userId, "removeOverlayView");
1334 final long identity = Binder.clearCallingIdentity();
1335 try {
1336 synchronized (mLock) {
1337 try {
1338 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1339 .removeOverlayView();
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001340 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001341 Slog.e(TAG, "error in removeOverlayView", e);
1342 }
1343 }
1344 } finally {
1345 Binder.restoreCallingIdentity(identity);
1346 }
1347 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001348
1349 @Override
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001350 public void timeShiftPause(IBinder sessionToken, int userId) {
1351 final int callingUid = Binder.getCallingUid();
1352 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1353 userId, "timeShiftPause");
1354 final long identity = Binder.clearCallingIdentity();
1355 try {
1356 synchronized (mLock) {
1357 try {
1358 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1359 .timeShiftPause();
1360 } catch (RemoteException | SessionNotFoundException e) {
1361 Slog.e(TAG, "error in timeShiftPause", e);
1362 }
1363 }
1364 } finally {
1365 Binder.restoreCallingIdentity(identity);
1366 }
1367 }
1368
1369 @Override
1370 public void timeShiftResume(IBinder sessionToken, int userId) {
1371 final int callingUid = Binder.getCallingUid();
1372 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1373 userId, "timeShiftResume");
1374 final long identity = Binder.clearCallingIdentity();
1375 try {
1376 synchronized (mLock) {
1377 try {
1378 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1379 .timeShiftResume();
1380 } catch (RemoteException | SessionNotFoundException e) {
1381 Slog.e(TAG, "error in timeShiftResume", e);
1382 }
1383 }
1384 } finally {
1385 Binder.restoreCallingIdentity(identity);
1386 }
1387 }
1388
1389 @Override
1390 public void timeShiftSeekTo(IBinder sessionToken, long timeMs, int userId) {
1391 final int callingUid = Binder.getCallingUid();
1392 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1393 userId, "timeShiftSeekTo");
1394 final long identity = Binder.clearCallingIdentity();
1395 try {
1396 synchronized (mLock) {
1397 try {
1398 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1399 .timeShiftSeekTo(timeMs);
1400 } catch (RemoteException | SessionNotFoundException e) {
1401 Slog.e(TAG, "error in timeShiftSeekTo", e);
1402 }
1403 }
1404 } finally {
1405 Binder.restoreCallingIdentity(identity);
1406 }
1407 }
1408
1409 @Override
1410 public void timeShiftSetPlaybackRate(IBinder sessionToken, float rate, int audioMode,
1411 int userId) {
1412 final int callingUid = Binder.getCallingUid();
1413 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1414 userId, "timeShiftSetPlaybackRate");
1415 final long identity = Binder.clearCallingIdentity();
1416 try {
1417 synchronized (mLock) {
1418 try {
1419 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1420 .timeShiftSetPlaybackRate(rate, audioMode);
1421 } catch (RemoteException | SessionNotFoundException e) {
1422 Slog.e(TAG, "error in timeShiftSetPlaybackRate", e);
1423 }
1424 }
1425 } finally {
1426 Binder.restoreCallingIdentity(identity);
1427 }
1428 }
1429
1430 @Override
Jae Seo465f0d62015-04-06 18:40:46 -07001431 public void timeShiftEnablePositionTracking(IBinder sessionToken, boolean enable,
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001432 int userId) {
1433 final int callingUid = Binder.getCallingUid();
1434 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
Jae Seo465f0d62015-04-06 18:40:46 -07001435 userId, "timeShiftEnablePositionTracking");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001436 final long identity = Binder.clearCallingIdentity();
1437 try {
1438 synchronized (mLock) {
1439 try {
1440 getSessionLocked(sessionToken, callingUid, resolvedUserId)
Jae Seo465f0d62015-04-06 18:40:46 -07001441 .timeShiftEnablePositionTracking(enable);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001442 } catch (RemoteException | SessionNotFoundException e) {
Jae Seo465f0d62015-04-06 18:40:46 -07001443 Slog.e(TAG, "error in timeShiftEnablePositionTracking", e);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001444 }
1445 }
1446 } finally {
1447 Binder.restoreCallingIdentity(identity);
1448 }
1449 }
1450
1451 @Override
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001452 public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
Wonsik Kim969167d2014-06-24 16:33:17 +09001453 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001454 != PackageManager.PERMISSION_GRANTED) {
1455 return null;
1456 }
1457
1458 final long identity = Binder.clearCallingIdentity();
1459 try {
1460 return mTvInputHardwareManager.getHardwareList();
1461 } finally {
1462 Binder.restoreCallingIdentity(identity);
1463 }
1464 }
1465
1466 @Override
1467 public ITvInputHardware acquireTvInputHardware(int deviceId,
Wonsik Kim969167d2014-06-24 16:33:17 +09001468 ITvInputHardwareCallback callback, TvInputInfo info, int userId)
1469 throws RemoteException {
1470 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001471 != PackageManager.PERMISSION_GRANTED) {
1472 return null;
1473 }
1474
1475 final long identity = Binder.clearCallingIdentity();
1476 final int callingUid = Binder.getCallingUid();
1477 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1478 userId, "acquireTvInputHardware");
1479 try {
1480 return mTvInputHardwareManager.acquireHardware(
Wonsik Kim969167d2014-06-24 16:33:17 +09001481 deviceId, callback, info, callingUid, resolvedUserId);
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001482 } finally {
1483 Binder.restoreCallingIdentity(identity);
1484 }
1485 }
1486
1487 @Override
1488 public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
1489 throws RemoteException {
Wonsik Kim969167d2014-06-24 16:33:17 +09001490 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001491 != PackageManager.PERMISSION_GRANTED) {
1492 return;
1493 }
1494
1495 final long identity = Binder.clearCallingIdentity();
1496 final int callingUid = Binder.getCallingUid();
1497 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1498 userId, "releaseTvInputHardware");
1499 try {
1500 mTvInputHardwareManager.releaseHardware(
1501 deviceId, hardware, callingUid, resolvedUserId);
1502 } finally {
1503 Binder.restoreCallingIdentity(identity);
1504 }
1505 }
Jaewan Kime14c3f42014-06-27 13:47:48 +09001506
1507 @Override
Terry Heoc086a3d2014-06-18 14:26:44 +09001508 public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int userId)
1509 throws RemoteException {
1510 if (mContext.checkCallingPermission(
1511 android.Manifest.permission.CAPTURE_TV_INPUT)
1512 != PackageManager.PERMISSION_GRANTED) {
1513 throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1514 }
1515
1516 final long identity = Binder.clearCallingIdentity();
1517 final int callingUid = Binder.getCallingUid();
1518 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1519 userId, "getAvailableTvStreamConfigList");
1520 try {
1521 return mTvInputHardwareManager.getAvailableTvStreamConfigList(
1522 inputId, callingUid, resolvedUserId);
1523 } finally {
1524 Binder.restoreCallingIdentity(identity);
1525 }
1526 }
1527
1528 @Override
1529 public boolean captureFrame(String inputId, Surface surface, TvStreamConfig config,
1530 int userId)
1531 throws RemoteException {
1532 if (mContext.checkCallingPermission(
1533 android.Manifest.permission.CAPTURE_TV_INPUT)
1534 != PackageManager.PERMISSION_GRANTED) {
1535 throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1536 }
1537
1538 final long identity = Binder.clearCallingIdentity();
1539 final int callingUid = Binder.getCallingUid();
1540 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1541 userId, "captureFrame");
1542 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001543 String hardwareInputId = null;
Terry Heo79124a72014-07-21 15:17:17 +09001544 synchronized (mLock) {
1545 UserState userState = getUserStateLocked(resolvedUserId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001546 if (userState.inputMap.get(inputId) == null) {
Jae Seofea8dd42014-08-26 13:57:41 -07001547 Slog.e(TAG, "input not found for " + inputId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001548 return false;
1549 }
1550 for (SessionState sessionState : userState.sessionStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001551 if (sessionState.info.getId().equals(inputId)
1552 && sessionState.hardwareSessionToken != null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001553 hardwareInputId = userState.sessionStateMap.get(
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001554 sessionState.hardwareSessionToken).info.getId();
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001555 break;
1556 }
1557 }
Terry Heo79124a72014-07-21 15:17:17 +09001558 }
Terry Heoc086a3d2014-06-18 14:26:44 +09001559 return mTvInputHardwareManager.captureFrame(
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001560 (hardwareInputId != null) ? hardwareInputId : inputId,
Terry Heo79124a72014-07-21 15:17:17 +09001561 surface, config, callingUid, resolvedUserId);
Terry Heoc086a3d2014-06-18 14:26:44 +09001562 } finally {
1563 Binder.restoreCallingIdentity(identity);
1564 }
1565 }
1566
1567 @Override
Terry Heodf9f0a32014-08-06 13:53:33 +09001568 public boolean isSingleSessionActive(int userId) throws RemoteException {
1569 final long identity = Binder.clearCallingIdentity();
1570 final int callingUid = Binder.getCallingUid();
1571 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1572 userId, "isSingleSessionActive");
1573 try {
1574 synchronized (mLock) {
1575 UserState userState = getUserStateLocked(resolvedUserId);
1576 if (userState.sessionStateMap.size() == 1) {
1577 return true;
1578 }
1579 else if (userState.sessionStateMap.size() == 2) {
1580 SessionState[] sessionStates = userState.sessionStateMap.values().toArray(
1581 new SessionState[0]);
1582 // Check if there is a wrapper input.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001583 if (sessionStates[0].hardwareSessionToken != null
1584 || sessionStates[1].hardwareSessionToken != null) {
Terry Heodf9f0a32014-08-06 13:53:33 +09001585 return true;
1586 }
1587 }
1588 return false;
1589 }
1590 } finally {
1591 Binder.restoreCallingIdentity(identity);
1592 }
1593 }
1594
1595 @Override
Jae Seo0f8fc342014-07-02 10:47:08 -07001596 @SuppressWarnings("resource")
Jaewan Kime14c3f42014-06-27 13:47:48 +09001597 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1598 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
Jae Seo0f8fc342014-07-02 10:47:08 -07001599 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
Jaewan Kime14c3f42014-06-27 13:47:48 +09001600 != PackageManager.PERMISSION_GRANTED) {
Jae Seo0f8fc342014-07-02 10:47:08 -07001601 pw.println("Permission Denial: can't dump TvInputManager from pid="
1602 + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
Jaewan Kime14c3f42014-06-27 13:47:48 +09001603 return;
1604 }
1605
1606 synchronized (mLock) {
1607 pw.println("User Ids (Current user: " + mCurrentUserId + "):");
1608 pw.increaseIndent();
1609 for (int i = 0; i < mUserStates.size(); i++) {
1610 int userId = mUserStates.keyAt(i);
1611 pw.println(Integer.valueOf(userId));
1612 }
1613 pw.decreaseIndent();
1614
1615 for (int i = 0; i < mUserStates.size(); i++) {
1616 int userId = mUserStates.keyAt(i);
1617 UserState userState = getUserStateLocked(userId);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001618 pw.println("UserState (" + userId + "):");
1619 pw.increaseIndent();
1620
Wonsik Kim969167d2014-06-24 16:33:17 +09001621 pw.println("inputMap: inputId -> TvInputState");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001622 pw.increaseIndent();
Jaewan Kim8e6b51b2014-07-15 13:01:57 +09001623 for (Map.Entry<String, TvInputState> entry: userState.inputMap.entrySet()) {
1624 pw.println(entry.getKey() + ": " + entry.getValue());
Jaewan Kime14c3f42014-06-27 13:47:48 +09001625 }
1626 pw.decreaseIndent();
1627
Wonsik Kim969167d2014-06-24 16:33:17 +09001628 pw.println("packageSet:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001629 pw.increaseIndent();
Wonsik Kim969167d2014-06-24 16:33:17 +09001630 for (String packageName : userState.packageSet) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09001631 pw.println(packageName);
1632 }
1633 pw.decreaseIndent();
1634
1635 pw.println("clientStateMap: ITvInputClient -> ClientState");
1636 pw.increaseIndent();
1637 for (Map.Entry<IBinder, ClientState> entry :
1638 userState.clientStateMap.entrySet()) {
1639 ClientState client = entry.getValue();
1640 pw.println(entry.getKey() + ": " + client);
1641
1642 pw.increaseIndent();
1643
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001644 pw.println("sessionTokens:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001645 pw.increaseIndent();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001646 for (IBinder token : client.sessionTokens) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09001647 pw.println("" + token);
1648 }
1649 pw.decreaseIndent();
1650
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001651 pw.println("clientTokens: " + client.clientToken);
1652 pw.println("userId: " + client.userId);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001653
1654 pw.decreaseIndent();
1655 }
1656 pw.decreaseIndent();
1657
Wonsik Kim187423c2014-06-25 14:12:48 +09001658 pw.println("serviceStateMap: ComponentName -> ServiceState");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001659 pw.increaseIndent();
Wonsik Kim187423c2014-06-25 14:12:48 +09001660 for (Map.Entry<ComponentName, ServiceState> entry :
Jaewan Kime14c3f42014-06-27 13:47:48 +09001661 userState.serviceStateMap.entrySet()) {
1662 ServiceState service = entry.getValue();
1663 pw.println(entry.getKey() + ": " + service);
1664
1665 pw.increaseIndent();
1666
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001667 pw.println("sessionTokens:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001668 pw.increaseIndent();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001669 for (IBinder token : service.sessionTokens) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09001670 pw.println("" + token);
1671 }
1672 pw.decreaseIndent();
1673
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001674 pw.println("service: " + service.service);
1675 pw.println("callback: " + service.callback);
1676 pw.println("bound: " + service.bound);
1677 pw.println("reconnecting: " + service.reconnecting);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001678
1679 pw.decreaseIndent();
1680 }
1681 pw.decreaseIndent();
1682
1683 pw.println("sessionStateMap: ITvInputSession -> SessionState");
1684 pw.increaseIndent();
1685 for (Map.Entry<IBinder, SessionState> entry :
1686 userState.sessionStateMap.entrySet()) {
1687 SessionState session = entry.getValue();
1688 pw.println(entry.getKey() + ": " + session);
1689
1690 pw.increaseIndent();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001691 pw.println("info: " + session.info);
1692 pw.println("client: " + session.client);
1693 pw.println("seq: " + session.seq);
1694 pw.println("callingUid: " + session.callingUid);
1695 pw.println("userId: " + session.userId);
1696 pw.println("sessionToken: " + session.sessionToken);
1697 pw.println("session: " + session.session);
1698 pw.println("logUri: " + session.logUri);
1699 pw.println("hardwareSessionToken: " + session.hardwareSessionToken);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001700 pw.decreaseIndent();
1701 }
1702 pw.decreaseIndent();
1703
Wonsik Kim969167d2014-06-24 16:33:17 +09001704 pw.println("callbackSet:");
1705 pw.increaseIndent();
1706 for (ITvInputManagerCallback callback : userState.callbackSet) {
1707 pw.println(callback.toString());
1708 }
1709 pw.decreaseIndent();
1710
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001711 pw.println("mainSessionToken: " + userState.mainSessionToken);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001712 pw.decreaseIndent();
1713 }
1714 }
1715 }
Jae Seo39570912014-02-20 18:23:25 -08001716 }
1717
Wonsik Kim969167d2014-06-24 16:33:17 +09001718 private static final class UserState {
1719 // A mapping from the TV input id to its TvInputState.
1720 private Map<String, TvInputState> inputMap = new HashMap<String, TvInputState>();
1721
1722 // A set of all TV input packages.
1723 private final Set<String> packageSet = new HashSet<String>();
Jae Seo5c80ad22014-06-12 19:52:58 -07001724
Jae Seo9c165d62014-08-25 14:39:26 -07001725 // A list of all TV content rating systems defined.
1726 private final List<TvContentRatingSystemInfo>
1727 contentRatingSystemList = new ArrayList<TvContentRatingSystemInfo>();
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +09001728
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001729 // A mapping from the token of a client to its state.
1730 private final Map<IBinder, ClientState> clientStateMap =
1731 new HashMap<IBinder, ClientState>();
1732
Jae Seo39570912014-02-20 18:23:25 -08001733 // A mapping from the name of a TV input service to its state.
Wonsik Kim187423c2014-06-25 14:12:48 +09001734 private final Map<ComponentName, ServiceState> serviceStateMap =
1735 new HashMap<ComponentName, ServiceState>();
Jae Seo39570912014-02-20 18:23:25 -08001736
1737 // A mapping from the token of a TV input session to its state.
1738 private final Map<IBinder, SessionState> sessionStateMap =
1739 new HashMap<IBinder, SessionState>();
Wonsik Kim969167d2014-06-24 16:33:17 +09001740
1741 // A set of callbacks.
1742 private final Set<ITvInputManagerCallback> callbackSet =
1743 new HashSet<ITvInputManagerCallback>();
Terry Heo79124a72014-07-21 15:17:17 +09001744
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001745 // The token of a "main" TV input session.
1746 private IBinder mainSessionToken = null;
Jae Seo783645e2014-07-28 17:30:50 +09001747
1748 // Persistent data store for all internal settings maintained by the TV input manager
1749 // service.
1750 private final PersistentDataStore persistentDataStore;
1751
1752 private UserState(Context context, int userId) {
1753 persistentDataStore = new PersistentDataStore(context, userId);
1754 }
Jae Seo39570912014-02-20 18:23:25 -08001755 }
1756
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001757 private final class ClientState implements IBinder.DeathRecipient {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001758 private final List<IBinder> sessionTokens = new ArrayList<IBinder>();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001759
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001760 private IBinder clientToken;
1761 private final int userId;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001762
1763 ClientState(IBinder clientToken, int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001764 this.clientToken = clientToken;
1765 this.userId = userId;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001766 }
1767
1768 public boolean isEmpty() {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001769 return sessionTokens.isEmpty();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001770 }
1771
1772 @Override
1773 public void binderDied() {
1774 synchronized (mLock) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001775 UserState userState = getUserStateLocked(userId);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001776 // DO NOT remove the client state of clientStateMap in this method. It will be
Ji-Hwan Leea65118e2014-07-24 16:30:02 +09001777 // removed in releaseSessionLocked().
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001778 ClientState clientState = userState.clientStateMap.get(clientToken);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001779 if (clientState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001780 while (clientState.sessionTokens.size() > 0) {
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001781 releaseSessionLocked(
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001782 clientState.sessionTokens.get(0), Process.SYSTEM_UID, userId);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001783 }
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001784 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001785 clientToken = null;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001786 }
1787 }
1788 }
1789
Jae Seo39570912014-02-20 18:23:25 -08001790 private final class ServiceState {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001791 private final List<IBinder> sessionTokens = new ArrayList<IBinder>();
1792 private final ServiceConnection connection;
1793 private final ComponentName component;
1794 private final boolean isHardware;
1795 private final List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
Jae Seo39570912014-02-20 18:23:25 -08001796
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001797 private ITvInputService service;
1798 private ServiceCallback callback;
1799 private boolean bound;
1800 private boolean reconnecting;
Jae Seo39570912014-02-20 18:23:25 -08001801
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001802 private ServiceState(ComponentName component, int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001803 this.component = component;
1804 this.connection = new InputServiceConnection(component, userId);
1805 this.isHardware = hasHardwarePermission(mContext.getPackageManager(), component);
1806 }
1807 }
1808
1809 private static final class TvInputState {
1810 // A TvInputInfo object which represents the TV input.
1811 private TvInputInfo info;
1812
1813 // The state of TV input. Connected by default.
1814 private int state = INPUT_STATE_CONNECTED;
1815
1816 @Override
1817 public String toString() {
1818 return "info: " + info + "; state: " + state;
Jae Seo39570912014-02-20 18:23:25 -08001819 }
1820 }
1821
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001822 private final class SessionState implements IBinder.DeathRecipient {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001823 private final TvInputInfo info;
1824 private final ITvInputClient client;
1825 private final int seq;
1826 private final int callingUid;
1827 private final int userId;
1828 private final IBinder sessionToken;
1829 private ITvInputSession session;
1830 private Uri logUri;
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001831 // Not null if this session represents an external device connected to a hardware TV input.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001832 private IBinder hardwareSessionToken;
Jae Seo39570912014-02-20 18:23:25 -08001833
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001834 private SessionState(IBinder sessionToken, TvInputInfo info, ITvInputClient client,
1835 int seq, int callingUid, int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001836 this.sessionToken = sessionToken;
1837 this.info = info;
1838 this.client = client;
1839 this.seq = seq;
1840 this.callingUid = callingUid;
1841 this.userId = userId;
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001842 }
1843
1844 @Override
1845 public void binderDied() {
1846 synchronized (mLock) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001847 session = null;
1848 if (client != null) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001849 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001850 client.onSessionReleased(seq);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001851 } catch(RemoteException e) {
1852 Slog.e(TAG, "error in onSessionReleased", e);
1853 }
1854 }
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001855 // If there are any other sessions based on this session, they should be released.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001856 UserState userState = getUserStateLocked(userId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001857 for (SessionState sessionState : userState.sessionStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001858 if (sessionToken == sessionState.hardwareSessionToken) {
1859 releaseSessionLocked(sessionState.sessionToken, Process.SYSTEM_UID,
1860 userId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001861 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001862 sessionState.client.onSessionReleased(sessionState.seq);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001863 } catch (RemoteException e) {
1864 Slog.e(TAG, "error in onSessionReleased", e);
1865 }
1866 }
1867 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001868 removeSessionStateLocked(sessionToken, userId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001869 }
Jae Seo39570912014-02-20 18:23:25 -08001870 }
1871 }
1872
1873 private final class InputServiceConnection implements ServiceConnection {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001874 private final ComponentName mComponent;
Jae Seo39570912014-02-20 18:23:25 -08001875 private final int mUserId;
1876
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001877 private InputServiceConnection(ComponentName component, int userId) {
1878 mComponent = component;
Jae Seo39570912014-02-20 18:23:25 -08001879 mUserId = userId;
1880 }
1881
1882 @Override
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001883 public void onServiceConnected(ComponentName component, IBinder service) {
Jae Seo39570912014-02-20 18:23:25 -08001884 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001885 Slog.d(TAG, "onServiceConnected(component=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -08001886 }
1887 synchronized (mLock) {
Wonsik Kim969167d2014-06-24 16:33:17 +09001888 UserState userState = getUserStateLocked(mUserId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001889 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001890 serviceState.service = ITvInputService.Stub.asInterface(service);
Jae Seo39570912014-02-20 18:23:25 -08001891
1892 // Register a callback, if we need to.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001893 if (serviceState.isHardware && serviceState.callback == null) {
1894 serviceState.callback = new ServiceCallback(mComponent, mUserId);
Jae Seo39570912014-02-20 18:23:25 -08001895 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001896 serviceState.service.registerCallback(serviceState.callback);
Jae Seo39570912014-02-20 18:23:25 -08001897 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001898 Slog.e(TAG, "error in registerCallback", e);
Jae Seo39570912014-02-20 18:23:25 -08001899 }
1900 }
1901
1902 // And create sessions, if any.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001903 for (IBinder sessionToken : serviceState.sessionTokens) {
1904 createSessionInternalLocked(serviceState.service, sessionToken, mUserId);
Jae Seo39570912014-02-20 18:23:25 -08001905 }
Wonsik Kim969167d2014-06-24 16:33:17 +09001906
Wonsik Kim187423c2014-06-25 14:12:48 +09001907 for (TvInputState inputState : userState.inputMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001908 if (inputState.info.getComponent().equals(component)
Jae Seo82fce642015-04-20 15:37:50 -07001909 && inputState.state != INPUT_STATE_CONNECTED) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001910 notifyInputStateChangedLocked(userState, inputState.info.getId(),
1911 inputState.state, null);
Wonsik Kim187423c2014-06-25 14:12:48 +09001912 }
1913 }
1914
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001915 if (serviceState.isHardware) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001916 List<TvInputHardwareInfo> hardwareInfoList =
1917 mTvInputHardwareManager.getHardwareList();
Wonsik Kim187423c2014-06-25 14:12:48 +09001918 for (TvInputHardwareInfo hardwareInfo : hardwareInfoList) {
1919 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001920 serviceState.service.notifyHardwareAdded(hardwareInfo);
Wonsik Kim187423c2014-06-25 14:12:48 +09001921 } catch (RemoteException e) {
1922 Slog.e(TAG, "error in notifyHardwareAdded", e);
1923 }
1924 }
1925
Jae Seo546c6352014-08-07 11:57:01 -07001926 List<HdmiDeviceInfo> deviceInfoList =
1927 mTvInputHardwareManager.getHdmiDeviceList();
1928 for (HdmiDeviceInfo deviceInfo : deviceInfoList) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001929 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001930 serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001931 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07001932 Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001933 }
1934 }
Wonsik Kim969167d2014-06-24 16:33:17 +09001935 }
Jae Seo39570912014-02-20 18:23:25 -08001936 }
1937 }
1938
1939 @Override
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001940 public void onServiceDisconnected(ComponentName component) {
Jae Seo39570912014-02-20 18:23:25 -08001941 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001942 Slog.d(TAG, "onServiceDisconnected(component=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -08001943 }
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001944 if (!mComponent.equals(component)) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001945 throw new IllegalArgumentException("Mismatched ComponentName: "
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001946 + mComponent + " (expected), " + component + " (actual).");
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001947 }
1948 synchronized (mLock) {
1949 UserState userState = getUserStateLocked(mUserId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001950 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001951 if (serviceState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001952 serviceState.reconnecting = true;
1953 serviceState.bound = false;
1954 serviceState.service = null;
1955 serviceState.callback = null;
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001956
Dongwon Kang426c9a42014-08-26 17:39:21 +09001957 abortPendingCreateSessionRequestsLocked(serviceState, null, mUserId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001958 }
1959 }
Jae Seo39570912014-02-20 18:23:25 -08001960 }
1961 }
1962
1963 private final class ServiceCallback extends ITvInputServiceCallback.Stub {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001964 private final ComponentName mComponent;
Jae Seo39570912014-02-20 18:23:25 -08001965 private final int mUserId;
1966
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001967 ServiceCallback(ComponentName component, int userId) {
1968 mComponent = component;
Jae Seo39570912014-02-20 18:23:25 -08001969 mUserId = userId;
1970 }
1971
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001972 private void ensureHardwarePermission() {
1973 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1974 != PackageManager.PERMISSION_GRANTED) {
1975 throw new SecurityException("The caller does not have hardware permission");
1976 }
1977 }
Wonsik Kim187423c2014-06-25 14:12:48 +09001978
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001979 private void ensureValidInput(TvInputInfo inputInfo) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001980 if (inputInfo.getId() == null || !mComponent.equals(inputInfo.getComponent())) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001981 throw new IllegalArgumentException("Invalid TvInputInfo");
1982 }
1983 }
1984
1985 private void addTvInputLocked(TvInputInfo inputInfo) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001986 ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001987 serviceState.inputList.add(inputInfo);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +09001988 buildTvInputListLocked(mUserId, null);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001989 }
1990
1991 @Override
1992 public void addHardwareTvInput(int deviceId, TvInputInfo inputInfo) {
1993 ensureHardwarePermission();
1994 ensureValidInput(inputInfo);
1995 synchronized (mLock) {
1996 mTvInputHardwareManager.addHardwareTvInput(deviceId, inputInfo);
1997 addTvInputLocked(inputInfo);
1998 }
1999 }
2000
2001 @Override
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09002002 public void addHdmiTvInput(int id, TvInputInfo inputInfo) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002003 ensureHardwarePermission();
2004 ensureValidInput(inputInfo);
2005 synchronized (mLock) {
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09002006 mTvInputHardwareManager.addHdmiTvInput(id, inputInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002007 addTvInputLocked(inputInfo);
Wonsik Kim187423c2014-06-25 14:12:48 +09002008 }
2009 }
2010
2011 @Override
2012 public void removeTvInput(String inputId) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002013 ensureHardwarePermission();
Wonsik Kim187423c2014-06-25 14:12:48 +09002014 synchronized (mLock) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002015 ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002016 boolean removed = false;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002017 for (Iterator<TvInputInfo> it = serviceState.inputList.iterator();
Wonsik Kim187423c2014-06-25 14:12:48 +09002018 it.hasNext(); ) {
2019 if (it.next().getId().equals(inputId)) {
2020 it.remove();
2021 removed = true;
2022 break;
2023 }
2024 }
2025 if (removed) {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +09002026 buildTvInputListLocked(mUserId, null);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002027 mTvInputHardwareManager.removeTvInput(inputId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002028 } else {
Jae Seofea8dd42014-08-26 13:57:41 -07002029 Slog.e(TAG, "failed to remove input " + inputId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002030 }
Jae Seo39570912014-02-20 18:23:25 -08002031 }
2032 }
2033 }
Jae Seo31dc634be2014-04-15 17:40:23 -07002034
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002035 private final class SessionCallback extends ITvInputSessionCallback.Stub {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002036 private final SessionState mSessionState;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002037 private final InputChannel[] mChannels;
2038
2039 SessionCallback(SessionState sessionState, InputChannel[] channels) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002040 mSessionState = sessionState;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002041 mChannels = channels;
2042 }
2043
2044 @Override
2045 public void onSessionCreated(ITvInputSession session, IBinder harewareSessionToken) {
2046 if (DEBUG) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002047 Slog.d(TAG, "onSessionCreated(inputId=" + mSessionState.info.getId() + ")");
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002048 }
2049 synchronized (mLock) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002050 mSessionState.session = session;
2051 mSessionState.hardwareSessionToken = harewareSessionToken;
2052 if (session != null && addSessionTokenToClientStateLocked(session)) {
2053 sendSessionTokenToClientLocked(mSessionState.client,
2054 mSessionState.info.getId(), mSessionState.sessionToken, mChannels[0],
2055 mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002056 } else {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002057 removeSessionStateLocked(mSessionState.sessionToken, mSessionState.userId);
2058 sendSessionTokenToClientLocked(mSessionState.client,
2059 mSessionState.info.getId(), null, null, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002060 }
2061 mChannels[0].dispose();
2062 }
2063 }
2064
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002065 private boolean addSessionTokenToClientStateLocked(ITvInputSession session) {
2066 try {
2067 session.asBinder().linkToDeath(mSessionState, 0);
2068 } catch (RemoteException e) {
2069 Slog.e(TAG, "session process has already died", e);
2070 return false;
2071 }
2072
2073 IBinder clientToken = mSessionState.client.asBinder();
2074 UserState userState = getUserStateLocked(mSessionState.userId);
2075 ClientState clientState = userState.clientStateMap.get(clientToken);
2076 if (clientState == null) {
2077 clientState = new ClientState(clientToken, mSessionState.userId);
2078 try {
2079 clientToken.linkToDeath(clientState, 0);
2080 } catch (RemoteException e) {
2081 Slog.e(TAG, "client process has already died", e);
2082 return false;
2083 }
2084 userState.clientStateMap.put(clientToken, clientState);
2085 }
2086 clientState.sessionTokens.add(mSessionState.sessionToken);
2087 return true;
2088 }
2089
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002090 @Override
2091 public void onChannelRetuned(Uri channelUri) {
2092 synchronized (mLock) {
2093 if (DEBUG) {
2094 Slog.d(TAG, "onChannelRetuned(" + channelUri + ")");
2095 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002096 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002097 return;
2098 }
2099 try {
2100 // TODO: Consider adding this channel change in the watch log. When we do
2101 // that, how we can protect the watch log from malicious tv inputs should
2102 // be addressed. e.g. add a field which represents where the channel change
2103 // originated from.
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002104 mSessionState.client.onChannelRetuned(channelUri, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002105 } catch (RemoteException e) {
2106 Slog.e(TAG, "error in onChannelRetuned", e);
2107 }
2108 }
2109 }
2110
2111 @Override
2112 public void onTracksChanged(List<TvTrackInfo> tracks) {
2113 synchronized (mLock) {
2114 if (DEBUG) {
2115 Slog.d(TAG, "onTracksChanged(" + tracks + ")");
2116 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002117 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002118 return;
2119 }
2120 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002121 mSessionState.client.onTracksChanged(tracks, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002122 } catch (RemoteException e) {
2123 Slog.e(TAG, "error in onTracksChanged", e);
2124 }
2125 }
2126 }
2127
2128 @Override
2129 public void onTrackSelected(int type, String trackId) {
2130 synchronized (mLock) {
2131 if (DEBUG) {
2132 Slog.d(TAG, "onTrackSelected(type=" + type + ", trackId=" + trackId + ")");
2133 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002134 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002135 return;
2136 }
2137 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002138 mSessionState.client.onTrackSelected(type, trackId, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002139 } catch (RemoteException e) {
2140 Slog.e(TAG, "error in onTrackSelected", e);
2141 }
2142 }
2143 }
2144
2145 @Override
2146 public void onVideoAvailable() {
2147 synchronized (mLock) {
2148 if (DEBUG) {
2149 Slog.d(TAG, "onVideoAvailable()");
2150 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002151 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002152 return;
2153 }
2154 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002155 mSessionState.client.onVideoAvailable(mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002156 } catch (RemoteException e) {
2157 Slog.e(TAG, "error in onVideoAvailable", e);
2158 }
2159 }
2160 }
2161
2162 @Override
2163 public void onVideoUnavailable(int reason) {
2164 synchronized (mLock) {
2165 if (DEBUG) {
2166 Slog.d(TAG, "onVideoUnavailable(" + reason + ")");
2167 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002168 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002169 return;
2170 }
2171 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002172 mSessionState.client.onVideoUnavailable(reason, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002173 } catch (RemoteException e) {
2174 Slog.e(TAG, "error in onVideoUnavailable", e);
2175 }
2176 }
2177 }
2178
2179 @Override
2180 public void onContentAllowed() {
2181 synchronized (mLock) {
2182 if (DEBUG) {
2183 Slog.d(TAG, "onContentAllowed()");
2184 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002185 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002186 return;
2187 }
2188 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002189 mSessionState.client.onContentAllowed(mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002190 } catch (RemoteException e) {
2191 Slog.e(TAG, "error in onContentAllowed", e);
2192 }
2193 }
2194 }
2195
2196 @Override
2197 public void onContentBlocked(String rating) {
2198 synchronized (mLock) {
2199 if (DEBUG) {
2200 Slog.d(TAG, "onContentBlocked()");
2201 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002202 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002203 return;
2204 }
2205 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002206 mSessionState.client.onContentBlocked(rating, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002207 } catch (RemoteException e) {
2208 Slog.e(TAG, "error in onContentBlocked", e);
2209 }
2210 }
2211 }
2212
2213 @Override
2214 public void onLayoutSurface(int left, int top, int right, int bottom) {
2215 synchronized (mLock) {
2216 if (DEBUG) {
2217 Slog.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top
2218 + ", right=" + right + ", bottom=" + bottom + ",)");
2219 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002220 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002221 return;
2222 }
2223 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002224 mSessionState.client.onLayoutSurface(left, top, right, bottom,
2225 mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002226 } catch (RemoteException e) {
2227 Slog.e(TAG, "error in onLayoutSurface", e);
2228 }
2229 }
2230 }
2231
2232 @Override
2233 public void onSessionEvent(String eventType, Bundle eventArgs) {
2234 synchronized (mLock) {
2235 if (DEBUG) {
2236 Slog.d(TAG, "onEvent(what=" + eventType + ", data=" + eventArgs + ")");
2237 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002238 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002239 return;
2240 }
2241 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002242 mSessionState.client.onSessionEvent(eventType, eventArgs, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002243 } catch (RemoteException e) {
2244 Slog.e(TAG, "error in onSessionEvent", e);
2245 }
2246 }
2247 }
Dongwon Kang6f0240c2015-03-31 17:56:36 -07002248
2249 @Override
2250 public void onTimeShiftStatusChanged(int status) {
2251 synchronized (mLock) {
2252 if (DEBUG) {
2253 Slog.d(TAG, "onTimeShiftStatusChanged()");
2254 }
2255 if (mSessionState.session == null || mSessionState.client == null) {
2256 return;
2257 }
2258 try {
2259 mSessionState.client.onTimeShiftStatusChanged(status, mSessionState.seq);
2260 } catch (RemoteException e) {
2261 Slog.e(TAG, "error in onTimeShiftStatusChanged", e);
2262 }
2263 }
2264 }
2265
2266 @Override
2267 public void onTimeShiftStartPositionChanged(long timeMs) {
2268 synchronized (mLock) {
2269 if (DEBUG) {
2270 Slog.d(TAG, "onTimeShiftStartPositionChanged()");
2271 }
2272 if (mSessionState.session == null || mSessionState.client == null) {
2273 return;
2274 }
2275 try {
2276 mSessionState.client.onTimeShiftStartPositionChanged(timeMs, mSessionState.seq);
2277 } catch (RemoteException e) {
2278 Slog.e(TAG, "error in onTimeShiftStartPositionChanged", e);
2279 }
2280 }
2281 }
2282
2283 @Override
2284 public void onTimeShiftCurrentPositionChanged(long timeMs) {
2285 synchronized (mLock) {
2286 if (DEBUG) {
2287 Slog.d(TAG, "onTimeShiftCurrentPositionChanged()");
2288 }
2289 if (mSessionState.session == null || mSessionState.client == null) {
2290 return;
2291 }
2292 try {
2293 mSessionState.client.onTimeShiftCurrentPositionChanged(timeMs,
2294 mSessionState.seq);
2295 } catch (RemoteException e) {
2296 Slog.e(TAG, "error in onTimeShiftCurrentPositionChanged", e);
2297 }
2298 }
2299 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002300 }
2301
2302 private static final class WatchLogHandler extends Handler {
Jae Seo7eb75df2014-08-08 22:20:48 -07002303 // There are only two kinds of watch events that can happen on the system:
2304 // 1. The current TV input session is tuned to a new channel.
2305 // 2. The session is released for some reason.
2306 // The former indicates the end of the previous log entry, if any, followed by the start of
2307 // a new entry. The latter indicates the end of the most recent entry for the given session.
2308 // Here the system supplies the database the smallest set of information only that is
2309 // sufficient to consolidate the log entries while minimizing database operations in the
2310 // system service.
2311 private static final int MSG_LOG_WATCH_START = 1;
2312 private static final int MSG_LOG_WATCH_END = 2;
Jae Seo31dc634be2014-04-15 17:40:23 -07002313
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002314 private final ContentResolver mContentResolver;
2315
2316 public WatchLogHandler(ContentResolver contentResolver, Looper looper) {
Jae Seo31dc634be2014-04-15 17:40:23 -07002317 super(looper);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002318 mContentResolver = contentResolver;
Jae Seo31dc634be2014-04-15 17:40:23 -07002319 }
2320
2321 @Override
2322 public void handleMessage(Message msg) {
2323 switch (msg.what) {
Jae Seo7eb75df2014-08-08 22:20:48 -07002324 case MSG_LOG_WATCH_START: {
Jae Seo31dc634be2014-04-15 17:40:23 -07002325 SomeArgs args = (SomeArgs) msg.obj;
Jae Seo7eb75df2014-08-08 22:20:48 -07002326 String packageName = (String) args.arg1;
2327 long watchStartTime = (long) args.arg2;
2328 long channelId = (long) args.arg3;
2329 Bundle tuneParams = (Bundle) args.arg4;
2330 IBinder sessionToken = (IBinder) args.arg5;
2331
2332 ContentValues values = new ContentValues();
2333 values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
2334 values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
2335 watchStartTime);
2336 values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
2337 if (tuneParams != null) {
2338 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS,
2339 encodeTuneParams(tuneParams));
2340 }
2341 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
2342 sessionToken.toString());
2343
2344 mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
Jae Seo31dc634be2014-04-15 17:40:23 -07002345 args.recycle();
2346 return;
2347 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002348 case MSG_LOG_WATCH_END: {
Jae Seo31dc634be2014-04-15 17:40:23 -07002349 SomeArgs args = (SomeArgs) msg.obj;
Jae Seo7eb75df2014-08-08 22:20:48 -07002350 IBinder sessionToken = (IBinder) args.arg1;
2351 long watchEndTime = (long) args.arg2;
2352
2353 ContentValues values = new ContentValues();
2354 values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
2355 watchEndTime);
2356 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
2357 sessionToken.toString());
2358
2359 mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
Jae Seo31dc634be2014-04-15 17:40:23 -07002360 args.recycle();
2361 return;
2362 }
2363 default: {
Jae Seo6a6059a2014-04-17 21:35:29 -07002364 Slog.w(TAG, "Unhandled message code: " + msg.what);
Jae Seo31dc634be2014-04-15 17:40:23 -07002365 return;
2366 }
2367 }
2368 }
2369
Jae Seo7eb75df2014-08-08 22:20:48 -07002370 private String encodeTuneParams(Bundle tuneParams) {
2371 StringBuilder builder = new StringBuilder();
2372 Set<String> keySet = tuneParams.keySet();
2373 Iterator<String> it = keySet.iterator();
2374 while (it.hasNext()) {
2375 String key = it.next();
2376 Object value = tuneParams.get(key);
2377 if (value == null) {
2378 continue;
Jae Seo579befe2014-08-06 19:18:37 -07002379 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002380 builder.append(replaceEscapeCharacters(key));
2381 builder.append("=");
2382 builder.append(replaceEscapeCharacters(value.toString()));
2383 if (it.hasNext()) {
2384 builder.append(", ");
Jae Seo31dc634be2014-04-15 17:40:23 -07002385 }
2386 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002387 return builder.toString();
Jae Seo31dc634be2014-04-15 17:40:23 -07002388 }
2389
Jae Seo7eb75df2014-08-08 22:20:48 -07002390 private String replaceEscapeCharacters(String src) {
2391 final char ESCAPE_CHARACTER = '%';
2392 final String ENCODING_TARGET_CHARACTERS = "%=,";
2393 StringBuilder builder = new StringBuilder();
2394 for (char ch : src.toCharArray()) {
2395 if (ENCODING_TARGET_CHARACTERS.indexOf(ch) >= 0) {
2396 builder.append(ESCAPE_CHARACTER);
Chulwoo Lee8d4ded02014-07-10 03:56:39 +09002397 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002398 builder.append(ch);
Jae Seo31dc634be2014-04-15 17:40:23 -07002399 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002400 return builder.toString();
Jae Seo579befe2014-08-06 19:18:37 -07002401 }
Jae Seo31dc634be2014-04-15 17:40:23 -07002402 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002403
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002404 private final class HardwareListener implements TvInputHardwareManager.Listener {
Wonsik Kim187423c2014-06-25 14:12:48 +09002405 @Override
2406 public void onStateChanged(String inputId, int state) {
Wonsik Kim969167d2014-06-24 16:33:17 +09002407 synchronized (mLock) {
2408 setStateLocked(inputId, state, mCurrentUserId);
2409 }
2410 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002411
2412 @Override
2413 public void onHardwareDeviceAdded(TvInputHardwareInfo info) {
2414 synchronized (mLock) {
2415 UserState userState = getUserStateLocked(mCurrentUserId);
2416 // Broadcast the event to all hardware inputs.
2417 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002418 if (!serviceState.isHardware || serviceState.service == null) continue;
Wonsik Kim187423c2014-06-25 14:12:48 +09002419 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002420 serviceState.service.notifyHardwareAdded(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09002421 } catch (RemoteException e) {
2422 Slog.e(TAG, "error in notifyHardwareAdded", e);
2423 }
2424 }
2425 }
2426 }
2427
2428 @Override
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002429 public void onHardwareDeviceRemoved(TvInputHardwareInfo info) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002430 synchronized (mLock) {
2431 UserState userState = getUserStateLocked(mCurrentUserId);
2432 // Broadcast the event to all hardware inputs.
2433 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002434 if (!serviceState.isHardware || serviceState.service == null) continue;
Wonsik Kim187423c2014-06-25 14:12:48 +09002435 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002436 serviceState.service.notifyHardwareRemoved(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09002437 } catch (RemoteException e) {
2438 Slog.e(TAG, "error in notifyHardwareRemoved", e);
2439 }
2440 }
2441 }
2442 }
2443
2444 @Override
Jae Seo546c6352014-08-07 11:57:01 -07002445 public void onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002446 synchronized (mLock) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002447 UserState userState = getUserStateLocked(mCurrentUserId);
2448 // Broadcast the event to all hardware inputs.
2449 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002450 if (!serviceState.isHardware || serviceState.service == null) continue;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002451 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002452 serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002453 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002454 Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002455 }
2456 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002457 }
2458 }
2459
2460 @Override
Jae Seo546c6352014-08-07 11:57:01 -07002461 public void onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002462 synchronized (mLock) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002463 UserState userState = getUserStateLocked(mCurrentUserId);
2464 // Broadcast the event to all hardware inputs.
2465 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002466 if (!serviceState.isHardware || serviceState.service == null) continue;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002467 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002468 serviceState.service.notifyHdmiDeviceRemoved(deviceInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002469 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002470 Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002471 }
2472 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002473 }
2474 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09002475
2476 @Override
Wonsik Kime92f8572014-08-12 18:30:24 +09002477 public void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo deviceInfo) {
2478 synchronized (mLock) {
2479 Integer state = null;
2480 switch (deviceInfo.getDevicePowerStatus()) {
2481 case HdmiControlManager.POWER_STATUS_ON:
2482 state = INPUT_STATE_CONNECTED;
2483 break;
2484 case HdmiControlManager.POWER_STATUS_STANDBY:
2485 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON:
2486 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY:
2487 state = INPUT_STATE_CONNECTED_STANDBY;
2488 break;
2489 case HdmiControlManager.POWER_STATUS_UNKNOWN:
2490 default:
2491 state = null;
2492 break;
2493 }
2494 if (state != null) {
2495 setStateLocked(inputId, state.intValue(), mCurrentUserId);
2496 }
2497 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09002498 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002499 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09002500
2501 private static class SessionNotFoundException extends IllegalArgumentException {
Dongwon Kangfdce9e52014-12-04 18:08:00 +09002502 public SessionNotFoundException(String name) {
2503 super(name);
2504 }
2505 }
Jae Seo39570912014-02-20 18:23:25 -08002506}