blob: 3262cc6515d073a9487f7a679a81628d4dd0006d [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 +090021import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
Dongwon Kang993f81e2014-11-27 19:34:18 +090022import static android.media.tv.TvInputManager.INPUT_STATE_UNKNOWN;
Wonsik Kim969167d2014-06-24 16:33:17 +090023
Jae Seo39570912014-02-20 18:23:25 -080024import android.app.ActivityManager;
25import android.content.BroadcastReceiver;
26import android.content.ComponentName;
Jae Seo5c80ad22014-06-12 19:52:58 -070027import android.content.ContentProviderOperation;
28import android.content.ContentProviderResult;
Jae Seo31dc634be2014-04-15 17:40:23 -070029import android.content.ContentResolver;
30import android.content.ContentUris;
31import android.content.ContentValues;
Jae Seo39570912014-02-20 18:23:25 -080032import android.content.Context;
33import android.content.Intent;
34import android.content.IntentFilter;
Jae Seo5c80ad22014-06-12 19:52:58 -070035import android.content.OperationApplicationException;
Jae Seo39570912014-02-20 18:23:25 -080036import android.content.ServiceConnection;
Jae Seo9c165d62014-08-25 14:39:26 -070037import android.content.pm.ActivityInfo;
Jae Seo39570912014-02-20 18:23:25 -080038import android.content.pm.PackageManager;
39import android.content.pm.ResolveInfo;
40import android.content.pm.ServiceInfo;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090041import android.graphics.Rect;
Wonsik Kime92f8572014-08-12 18:30:24 +090042import android.hardware.hdmi.HdmiControlManager;
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090043import android.hardware.hdmi.HdmiDeviceInfo;
Jae Seod5cc4a22014-05-30 16:57:43 -070044import android.media.tv.ITvInputClient;
45import android.media.tv.ITvInputHardware;
46import android.media.tv.ITvInputHardwareCallback;
47import android.media.tv.ITvInputManager;
Wonsik Kim969167d2014-06-24 16:33:17 +090048import android.media.tv.ITvInputManagerCallback;
Jae Seod5cc4a22014-05-30 16:57:43 -070049import android.media.tv.ITvInputService;
50import android.media.tv.ITvInputServiceCallback;
51import android.media.tv.ITvInputSession;
52import android.media.tv.ITvInputSessionCallback;
Jae Seo783645e2014-07-28 17:30:50 +090053import android.media.tv.TvContentRating;
Jae Seo9c165d62014-08-25 14:39:26 -070054import android.media.tv.TvContentRatingSystemInfo;
Jae Seod5cc4a22014-05-30 16:57:43 -070055import android.media.tv.TvContract;
56import android.media.tv.TvInputHardwareInfo;
57import android.media.tv.TvInputInfo;
Jae Seo9c165d62014-08-25 14:39:26 -070058import android.media.tv.TvInputManager;
Jae Seod5cc4a22014-05-30 16:57:43 -070059import android.media.tv.TvInputService;
Terry Heoc086a3d2014-06-18 14:26:44 +090060import android.media.tv.TvStreamConfig;
Dongwon Kang1f213912014-07-02 18:35:08 +090061import android.media.tv.TvTrackInfo;
Jae Seo39570912014-02-20 18:23:25 -080062import android.net.Uri;
63import android.os.Binder;
Youngsang Cho832860f2014-05-21 20:54:03 +090064import android.os.Bundle;
Jae Seo31dc634be2014-04-15 17:40:23 -070065import android.os.Handler;
Jae Seo39570912014-02-20 18:23:25 -080066import android.os.IBinder;
Jae Seo31dc634be2014-04-15 17:40:23 -070067import android.os.Looper;
68import android.os.Message;
Jae Seo39570912014-02-20 18:23:25 -080069import android.os.Process;
70import android.os.RemoteException;
71import android.os.UserHandle;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090072import android.util.Slog;
Jae Seo39570912014-02-20 18:23:25 -080073import android.util.SparseArray;
Jae Seo6a6059a2014-04-17 21:35:29 -070074import android.view.InputChannel;
Jae Seo39570912014-02-20 18:23:25 -080075import android.view.Surface;
76
77import com.android.internal.content.PackageMonitor;
Jae Seo31dc634be2014-04-15 17:40:23 -070078import com.android.internal.os.SomeArgs;
Jaewan Kime14c3f42014-06-27 13:47:48 +090079import com.android.internal.util.IndentingPrintWriter;
Jae Seo31dc634be2014-04-15 17:40:23 -070080import com.android.server.IoThread;
Jae Seo39570912014-02-20 18:23:25 -080081import com.android.server.SystemService;
82
Chulwoo Leee7bb7d62014-05-27 14:10:37 +090083import org.xmlpull.v1.XmlPullParserException;
84
Jaewan Kime14c3f42014-06-27 13:47:48 +090085import java.io.FileDescriptor;
Chulwoo Leee7bb7d62014-05-27 14:10:37 +090086import java.io.IOException;
Jaewan Kime14c3f42014-06-27 13:47:48 +090087import java.io.PrintWriter;
Jae Seo39570912014-02-20 18:23:25 -080088import java.util.ArrayList;
Chulwoo Lee19ba61a2014-09-03 00:59:35 +090089import java.util.Arrays;
Jae Seo39570912014-02-20 18:23:25 -080090import java.util.HashMap;
Jae Seo5c80ad22014-06-12 19:52:58 -070091import java.util.HashSet;
Wonsik Kim187423c2014-06-25 14:12:48 +090092import java.util.Iterator;
Jae Seo39570912014-02-20 18:23:25 -080093import java.util.List;
94import java.util.Map;
Jae Seo5c80ad22014-06-12 19:52:58 -070095import java.util.Set;
Jae Seo39570912014-02-20 18:23:25 -080096
97/** This class provides a system service that manages television inputs. */
98public final class TvInputManagerService extends SystemService {
Jae Seoee2ec052014-09-14 10:30:05 -070099 private static final boolean DEBUG = false;
Jae Seo39570912014-02-20 18:23:25 -0800100 private static final String TAG = "TvInputManagerService";
101
102 private final Context mContext;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000103 private final TvInputHardwareManager mTvInputHardwareManager;
Jae Seo39570912014-02-20 18:23:25 -0800104
Jae Seo31dc634be2014-04-15 17:40:23 -0700105 private final ContentResolver mContentResolver;
106
Jae Seo39570912014-02-20 18:23:25 -0800107 // A global lock.
108 private final Object mLock = new Object();
109
110 // ID of the current user.
111 private int mCurrentUserId = UserHandle.USER_OWNER;
112
113 // A map from user id to UserState.
114 private final SparseArray<UserState> mUserStates = new SparseArray<UserState>();
115
Jae Seo7eb75df2014-08-08 22:20:48 -0700116 private final WatchLogHandler mWatchLogHandler;
Jae Seo31dc634be2014-04-15 17:40:23 -0700117
Jae Seo39570912014-02-20 18:23:25 -0800118 public TvInputManagerService(Context context) {
119 super(context);
Jae Seo31dc634be2014-04-15 17:40:23 -0700120
Jae Seo39570912014-02-20 18:23:25 -0800121 mContext = context;
Jae Seo31dc634be2014-04-15 17:40:23 -0700122 mContentResolver = context.getContentResolver();
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900123 mWatchLogHandler = new WatchLogHandler(mContentResolver, IoThread.get().getLooper());
Jae Seo31dc634be2014-04-15 17:40:23 -0700124
Wonsik Kim187423c2014-06-25 14:12:48 +0900125 mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
Jae Seo31dc634be2014-04-15 17:40:23 -0700126
Jae Seo39570912014-02-20 18:23:25 -0800127 synchronized (mLock) {
Jae Seo783645e2014-07-28 17:30:50 +0900128 mUserStates.put(mCurrentUserId, new UserState(mContext, mCurrentUserId));
Jae Seo39570912014-02-20 18:23:25 -0800129 }
130 }
131
132 @Override
133 public void onStart() {
134 publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());
135 }
136
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900137 @Override
138 public void onBootPhase(int phase) {
139 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
140 registerBroadcastReceivers();
Wonsik Kim187423c2014-06-25 14:12:48 +0900141 } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900142 synchronized (mLock) {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900143 buildTvInputListLocked(mCurrentUserId, null);
Jae Seo9c165d62014-08-25 14:39:26 -0700144 buildTvContentRatingSystemListLocked(mCurrentUserId);
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900145 }
146 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900147 mTvInputHardwareManager.onBootPhase(phase);
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900148 }
149
Jae Seo39570912014-02-20 18:23:25 -0800150 private void registerBroadcastReceivers() {
151 PackageMonitor monitor = new PackageMonitor() {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900152 private void buildTvInputList(String[] packages) {
153 synchronized (mLock) {
154 buildTvInputListLocked(getChangingUserId(), packages);
155 buildTvContentRatingSystemListLocked(getChangingUserId());
156 }
157 }
158
159 @Override
160 public void onPackageUpdateFinished(String packageName, int uid) {
161 if (DEBUG) Slog.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")");
162 // This callback is invoked when the TV input is reinstalled.
163 // In this case, isReplacing() always returns true.
164 buildTvInputList(new String[] { packageName });
165 }
166
167 @Override
168 public void onPackagesAvailable(String[] packages) {
169 if (DEBUG) {
170 Slog.d(TAG, "onPackagesAvailable(packages=" + Arrays.toString(packages) + ")");
171 }
172 // This callback is invoked when the media on which some packages exist become
173 // available.
174 if (isReplacing()) {
175 buildTvInputList(packages);
176 }
177 }
178
179 @Override
180 public void onPackagesUnavailable(String[] packages) {
181 // This callback is invoked when the media on which some packages exist become
182 // unavailable.
183 if (DEBUG) {
184 Slog.d(TAG, "onPackagesUnavailable(packages=" + Arrays.toString(packages)
185 + ")");
186 }
187 if (isReplacing()) {
188 buildTvInputList(packages);
189 }
190 }
191
Jae Seo39570912014-02-20 18:23:25 -0800192 @Override
193 public void onSomePackagesChanged() {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900194 // TODO: Use finer-grained methods(e.g. onPackageAdded, onPackageRemoved) to manage
195 // the TV inputs.
Dongwon Kang426c9a42014-08-26 17:39:21 +0900196 if (DEBUG) Slog.d(TAG, "onSomePackagesChanged()");
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900197 if (isReplacing()) {
198 if (DEBUG) Slog.d(TAG, "Skipped building TV input list due to replacing");
199 // When the package is updated, buildTvInputListLocked is called in other
200 // methods instead.
201 return;
Jae Seo39570912014-02-20 18:23:25 -0800202 }
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900203 buildTvInputList(null);
Jae Seo39570912014-02-20 18:23:25 -0800204 }
Jae Seo5c80ad22014-06-12 19:52:58 -0700205
206 @Override
Dongwon Kang31a8f842015-04-08 18:26:23 -0700207 public boolean onPackageChanged(String packageName, int uid, String[] components) {
208 // The input list needs to be updated in any cases, regardless of whether
209 // it happened to the whole package or a specific component. Returning true so that
210 // the update can be handled in {@link #onSomePackagesChanged}.
211 return true;
212 }
213
214 @Override
Jae Seo5c80ad22014-06-12 19:52:58 -0700215 public void onPackageRemoved(String packageName, int uid) {
216 synchronized (mLock) {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900217 UserState userState = getUserStateLocked(getChangingUserId());
Wonsik Kim969167d2014-06-24 16:33:17 +0900218 if (!userState.packageSet.contains(packageName)) {
Jae Seo5c80ad22014-06-12 19:52:58 -0700219 // Not a TV input package.
220 return;
221 }
222 }
223
224 ArrayList<ContentProviderOperation> operations =
225 new ArrayList<ContentProviderOperation>();
226
227 String selection = TvContract.BaseTvColumns.COLUMN_PACKAGE_NAME + "=?";
228 String[] selectionArgs = { packageName };
229
230 operations.add(ContentProviderOperation.newDelete(TvContract.Channels.CONTENT_URI)
231 .withSelection(selection, selectionArgs).build());
232 operations.add(ContentProviderOperation.newDelete(TvContract.Programs.CONTENT_URI)
233 .withSelection(selection, selectionArgs).build());
234 operations.add(ContentProviderOperation
235 .newDelete(TvContract.WatchedPrograms.CONTENT_URI)
236 .withSelection(selection, selectionArgs).build());
237
238 ContentProviderResult[] results = null;
239 try {
240 results = mContentResolver.applyBatch(TvContract.AUTHORITY, operations);
241 } catch (RemoteException | OperationApplicationException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700242 Slog.e(TAG, "error in applyBatch", e);
Jae Seo5c80ad22014-06-12 19:52:58 -0700243 }
244
245 if (DEBUG) {
246 Slog.d(TAG, "onPackageRemoved(packageName=" + packageName + ", uid=" + uid
247 + ")");
248 Slog.d(TAG, "results=" + results);
249 }
250 }
Jae Seo39570912014-02-20 18:23:25 -0800251 };
252 monitor.register(mContext, null, UserHandle.ALL, true);
253
254 IntentFilter intentFilter = new IntentFilter();
255 intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
256 intentFilter.addAction(Intent.ACTION_USER_REMOVED);
257 mContext.registerReceiverAsUser(new BroadcastReceiver() {
258 @Override
259 public void onReceive(Context context, Intent intent) {
260 String action = intent.getAction();
261 if (Intent.ACTION_USER_SWITCHED.equals(action)) {
262 switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
263 } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
264 removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
265 }
266 }
267 }, UserHandle.ALL, intentFilter, null, null);
268 }
269
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900270 private static boolean hasHardwarePermission(PackageManager pm, ComponentName component) {
Wonsik Kim187423c2014-06-25 14:12:48 +0900271 return pm.checkPermission(android.Manifest.permission.TV_INPUT_HARDWARE,
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900272 component.getPackageName()) == PackageManager.PERMISSION_GRANTED;
Wonsik Kim187423c2014-06-25 14:12:48 +0900273 }
274
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900275 private void buildTvInputListLocked(int userId, String[] updatedPackages) {
Jae Seo39570912014-02-20 18:23:25 -0800276 UserState userState = getUserStateLocked(userId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900277 userState.packageSet.clear();
Jae Seo39570912014-02-20 18:23:25 -0800278
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900279 if (DEBUG) Slog.d(TAG, "buildTvInputList");
Jae Seo39570912014-02-20 18:23:25 -0800280 PackageManager pm = mContext.getPackageManager();
281 List<ResolveInfo> services = pm.queryIntentServices(
Chulwoo Leee7bb7d62014-05-27 14:10:37 +0900282 new Intent(TvInputService.SERVICE_INTERFACE),
283 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900284 List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
Jae Seo39570912014-02-20 18:23:25 -0800285 for (ResolveInfo ri : services) {
286 ServiceInfo si = ri.serviceInfo;
287 if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900288 Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
Jae Seo39570912014-02-20 18:23:25 -0800289 + android.Manifest.permission.BIND_TV_INPUT);
290 continue;
291 }
Jae Seo9cc28e52014-08-12 16:45:58 -0700292
293 ComponentName component = new ComponentName(si.packageName, si.name);
294 if (hasHardwarePermission(pm, component)) {
295 ServiceState serviceState = userState.serviceStateMap.get(component);
296 if (serviceState == null) {
297 // We see this hardware TV input service for the first time; we need to
298 // prepare the ServiceState object so that we can connect to the service and
299 // let it add TvInputInfo objects to mInputList if there's any.
300 serviceState = new ServiceState(component, userId);
301 userState.serviceStateMap.put(component, serviceState);
Wonsik Kimf271eac2014-08-30 12:55:10 +0900302 updateServiceConnectionLocked(component, userId);
Wonsik Kim187423c2014-06-25 14:12:48 +0900303 } else {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900304 inputList.addAll(serviceState.inputList);
Jae Seo9cc28e52014-08-12 16:45:58 -0700305 }
306 } else {
307 try {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900308 inputList.add(TvInputInfo.createTvInputInfo(mContext, ri));
Jae Seo9cc28e52014-08-12 16:45:58 -0700309 } catch (XmlPullParserException | IOException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700310 Slog.e(TAG, "failed to load TV input " + si.name, e);
Jae Seo9cc28e52014-08-12 16:45:58 -0700311 continue;
Wonsik Kim969167d2014-06-24 16:33:17 +0900312 }
Chulwoo Leee7bb7d62014-05-27 14:10:37 +0900313 }
Jae Seo9cc28e52014-08-12 16:45:58 -0700314 userState.packageSet.add(si.packageName);
315 }
316
317 Map<String, TvInputState> inputMap = new HashMap<String, TvInputState>();
318 for (TvInputInfo info : inputList) {
Jae Seofea8dd42014-08-26 13:57:41 -0700319 if (DEBUG) {
320 Slog.d(TAG, "add " + info.getId());
321 }
Jae Seo9cc28e52014-08-12 16:45:58 -0700322 TvInputState state = userState.inputMap.get(info.getId());
323 if (state == null) {
324 state = new TvInputState();
325 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900326 state.info = info;
Jae Seo9cc28e52014-08-12 16:45:58 -0700327 inputMap.put(info.getId(), state);
Jae Seo39570912014-02-20 18:23:25 -0800328 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900329
330 for (String inputId : inputMap.keySet()) {
331 if (!userState.inputMap.containsKey(inputId)) {
332 notifyInputAddedLocked(userState, inputId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900333 } else if (updatedPackages != null) {
334 // Notify the package updates
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +0900335 ComponentName component = inputMap.get(inputId).info.getComponent();
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900336 for (String updatedPackage : updatedPackages) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +0900337 if (component.getPackageName().equals(updatedPackage)) {
338 updateServiceConnectionLocked(component, userId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900339 notifyInputUpdatedLocked(userState, inputId);
340 break;
341 }
342 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900343 }
344 }
345
346 for (String inputId : userState.inputMap.keySet()) {
347 if (!inputMap.containsKey(inputId)) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900348 TvInputInfo info = userState.inputMap.get(inputId).info;
Dongwon Kang426c9a42014-08-26 17:39:21 +0900349 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
350 if (serviceState != null) {
351 abortPendingCreateSessionRequestsLocked(serviceState, inputId, userId);
352 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900353 notifyInputRemovedLocked(userState, inputId);
354 }
355 }
356
357 userState.inputMap.clear();
358 userState.inputMap = inputMap;
Jae Seo9c165d62014-08-25 14:39:26 -0700359 }
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900360
Jae Seo9c165d62014-08-25 14:39:26 -0700361 private void buildTvContentRatingSystemListLocked(int userId) {
362 UserState userState = getUserStateLocked(userId);
363 userState.contentRatingSystemList.clear();
364
365 final PackageManager pm = mContext.getPackageManager();
366 Intent intent = new Intent(TvInputManager.ACTION_QUERY_CONTENT_RATING_SYSTEMS);
367 for (ResolveInfo resolveInfo :
368 pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA)) {
369 ActivityInfo receiver = resolveInfo.activityInfo;
370 Bundle metaData = receiver.metaData;
371 if (metaData == null) {
372 continue;
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900373 }
Jae Seo9c165d62014-08-25 14:39:26 -0700374
375 int xmlResId = metaData.getInt(TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS);
376 if (xmlResId == 0) {
377 Slog.w(TAG, "Missing meta-data '"
378 + TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS + "' on receiver "
379 + receiver.packageName + "/" + receiver.name);
380 continue;
381 }
382 userState.contentRatingSystemList.add(
383 TvContentRatingSystemInfo.createTvContentRatingSystemInfo(xmlResId,
384 receiver.applicationInfo));
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900385 }
Jae Seo39570912014-02-20 18:23:25 -0800386 }
387
388 private void switchUser(int userId) {
389 synchronized (mLock) {
390 if (mCurrentUserId == userId) {
391 return;
392 }
393 // final int oldUserId = mCurrentUserId;
394 // TODO: Release services and sessions in the old user state, if needed.
395 mCurrentUserId = userId;
396
397 UserState userState = mUserStates.get(userId);
398 if (userState == null) {
Jae Seo783645e2014-07-28 17:30:50 +0900399 userState = new UserState(mContext, userId);
Jae Seo39570912014-02-20 18:23:25 -0800400 }
401 mUserStates.put(userId, userState);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900402 buildTvInputListLocked(userId, null);
Jae Seo9c165d62014-08-25 14:39:26 -0700403 buildTvContentRatingSystemListLocked(userId);
Jae Seo39570912014-02-20 18:23:25 -0800404 }
405 }
406
407 private void removeUser(int userId) {
408 synchronized (mLock) {
Jae Seob06cb882014-04-09 12:08:17 -0700409 UserState userState = mUserStates.get(userId);
410 if (userState == null) {
411 return;
412 }
Jae Seo39570912014-02-20 18:23:25 -0800413 // Release created sessions.
Jae Seo39570912014-02-20 18:23:25 -0800414 for (SessionState state : userState.sessionStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900415 if (state.session != null) {
Jae Seo39570912014-02-20 18:23:25 -0800416 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900417 state.session.release();
Jae Seo39570912014-02-20 18:23:25 -0800418 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900419 Slog.e(TAG, "error in release", e);
Jae Seo39570912014-02-20 18:23:25 -0800420 }
421 }
422 }
423 userState.sessionStateMap.clear();
424
425 // Unregister all callbacks and unbind all services.
426 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900427 if (serviceState.callback != null) {
Jae Seo39570912014-02-20 18:23:25 -0800428 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900429 serviceState.service.unregisterCallback(serviceState.callback);
Jae Seo39570912014-02-20 18:23:25 -0800430 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900431 Slog.e(TAG, "error in unregisterCallback", e);
Jae Seo39570912014-02-20 18:23:25 -0800432 }
433 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900434 mContext.unbindService(serviceState.connection);
Jae Seo39570912014-02-20 18:23:25 -0800435 }
436 userState.serviceStateMap.clear();
437
Jae Seofea8dd42014-08-26 13:57:41 -0700438 // Clear everything else.
439 userState.inputMap.clear();
440 userState.packageSet.clear();
Jae Seo9c165d62014-08-25 14:39:26 -0700441 userState.contentRatingSystemList.clear();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900442 userState.clientStateMap.clear();
Jae Seofea8dd42014-08-26 13:57:41 -0700443 userState.callbackSet.clear();
444 userState.mainSessionToken = null;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900445
Jae Seo39570912014-02-20 18:23:25 -0800446 mUserStates.remove(userId);
447 }
448 }
449
450 private UserState getUserStateLocked(int userId) {
451 UserState userState = mUserStates.get(userId);
452 if (userState == null) {
453 throw new IllegalStateException("User state not found for user ID " + userId);
454 }
455 return userState;
456 }
457
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900458 private ServiceState getServiceStateLocked(ComponentName component, int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800459 UserState userState = getUserStateLocked(userId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900460 ServiceState serviceState = userState.serviceStateMap.get(component);
Jae Seo39570912014-02-20 18:23:25 -0800461 if (serviceState == null) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900462 throw new IllegalStateException("Service state not found for " + component + " (userId="
Sungsoo Lim7de5e232014-04-12 16:51:27 +0900463 + userId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800464 }
465 return serviceState;
466 }
467
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900468 private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid, int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800469 UserState userState = getUserStateLocked(userId);
470 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
471 if (sessionState == null) {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900472 throw new SessionNotFoundException("Session state not found for token " + sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800473 }
474 // Only the application that requested this session or the system can access it.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900475 if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) {
Jae Seo39570912014-02-20 18:23:25 -0800476 throw new SecurityException("Illegal access to the session with token " + sessionToken
477 + " from uid " + callingUid);
478 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900479 return sessionState;
480 }
481
482 private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900483 return getSessionLocked(getSessionStateLocked(sessionToken, callingUid, userId));
484 }
485
486 private ITvInputSession getSessionLocked(SessionState sessionState) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900487 ITvInputSession session = sessionState.session;
Jae Seo39570912014-02-20 18:23:25 -0800488 if (session == null) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900489 throw new IllegalStateException("Session not yet created for token "
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900490 + sessionState.sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800491 }
492 return session;
493 }
494
495 private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
496 String methodName) {
497 return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
498 false, methodName, null);
499 }
500
Wonsik Kim187423c2014-06-25 14:12:48 +0900501 private static boolean shouldMaintainConnection(ServiceState serviceState) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900502 return !serviceState.sessionTokens.isEmpty() || serviceState.isHardware;
503 // TODO: Find a way to maintain connection to hardware TV input service only when necessary.
Wonsik Kim187423c2014-06-25 14:12:48 +0900504 }
505
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900506 private void updateServiceConnectionLocked(ComponentName component, int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800507 UserState userState = getUserStateLocked(userId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900508 ServiceState serviceState = userState.serviceStateMap.get(component);
Jae Seo39570912014-02-20 18:23:25 -0800509 if (serviceState == null) {
510 return;
511 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900512 if (serviceState.reconnecting) {
513 if (!serviceState.sessionTokens.isEmpty()) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900514 // wait until all the sessions are removed.
515 return;
516 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900517 serviceState.reconnecting = false;
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900518 }
Wonsik Kim187423c2014-06-25 14:12:48 +0900519 boolean maintainConnection = shouldMaintainConnection(serviceState);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900520 if (serviceState.service == null && maintainConnection && userId == mCurrentUserId) {
Jae Seo39570912014-02-20 18:23:25 -0800521 // This means that the service is not yet connected but its state indicates that we
522 // have pending requests. Then, connect the service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900523 if (serviceState.bound) {
Jae Seo39570912014-02-20 18:23:25 -0800524 // We have already bound to the service so we don't try to bind again until after we
525 // unbind later on.
526 return;
527 }
528 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900529 Slog.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800530 }
Sungsoo Limd6672b52014-04-30 10:43:26 +0900531
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900532 Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900533 serviceState.bound = mContext.bindServiceAsUser(
534 i, serviceState.connection, Context.BIND_AUTO_CREATE, new UserHandle(userId));
535 } else if (serviceState.service != null && !maintainConnection) {
Jae Seo39570912014-02-20 18:23:25 -0800536 // This means that the service is already connected but its state indicates that we have
537 // nothing to do with it. Then, disconnect the service.
538 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900539 Slog.d(TAG, "unbindService(service=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -0800540 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900541 mContext.unbindService(serviceState.connection);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900542 userState.serviceStateMap.remove(component);
Jae Seo39570912014-02-20 18:23:25 -0800543 }
544 }
545
Dongwon Kang426c9a42014-08-26 17:39:21 +0900546 private void abortPendingCreateSessionRequestsLocked(ServiceState serviceState,
547 String inputId, int userId) {
548 // Let clients know the create session requests are failed.
549 UserState userState = getUserStateLocked(userId);
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900550 List<SessionState> sessionsToAbort = new ArrayList<>();
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900551 for (IBinder sessionToken : serviceState.sessionTokens) {
Dongwon Kang426c9a42014-08-26 17:39:21 +0900552 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900553 if (sessionState.session == null && (inputId == null
554 || sessionState.info.getId().equals(inputId))) {
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900555 sessionsToAbort.add(sessionState);
Dongwon Kang426c9a42014-08-26 17:39:21 +0900556 }
557 }
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900558 for (SessionState sessionState : sessionsToAbort) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900559 removeSessionStateLocked(sessionState.sessionToken, sessionState.userId);
560 sendSessionTokenToClientLocked(sessionState.client,
561 sessionState.info.getId(), null, null, sessionState.seq);
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900562 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900563 updateServiceConnectionLocked(serviceState.component, userId);
Dongwon Kang426c9a42014-08-26 17:39:21 +0900564 }
565
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900566 private void createSessionInternalLocked(ITvInputService service, IBinder sessionToken,
567 int userId) {
568 UserState userState = getUserStateLocked(userId);
569 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800570 if (DEBUG) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900571 Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.info.getId() + ")");
Jae Seo39570912014-02-20 18:23:25 -0800572 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900573 InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
Jae Seo6a6059a2014-04-17 21:35:29 -0700574
Jae Seo39570912014-02-20 18:23:25 -0800575 // Set up a callback to send the session token.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900576 ITvInputSessionCallback callback = new SessionCallback(sessionState, channels);
Jae Seo39570912014-02-20 18:23:25 -0800577
578 // Create a session. When failed, send a null token immediately.
579 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900580 service.createSession(channels[1], callback, sessionState.info.getId());
Jae Seo39570912014-02-20 18:23:25 -0800581 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900582 Slog.e(TAG, "error in createSession", e);
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900583 removeSessionStateLocked(sessionToken, userId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900584 sendSessionTokenToClientLocked(sessionState.client, sessionState.info.getId(), null,
585 null, sessionState.seq);
Jae Seo39570912014-02-20 18:23:25 -0800586 }
Jae Seo6a6059a2014-04-17 21:35:29 -0700587 channels[1].dispose();
Jae Seo39570912014-02-20 18:23:25 -0800588 }
589
Sungsoo Limd6672b52014-04-30 10:43:26 +0900590 private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId,
Jae Seo5c80ad22014-06-12 19:52:58 -0700591 IBinder sessionToken, InputChannel channel, int seq) {
Jae Seo39570912014-02-20 18:23:25 -0800592 try {
Sungsoo Limd6672b52014-04-30 10:43:26 +0900593 client.onSessionCreated(inputId, sessionToken, channel, seq);
Jae Seofea8dd42014-08-26 13:57:41 -0700594 } catch (RemoteException e) {
595 Slog.e(TAG, "error in onSessionCreated", e);
Jae Seo39570912014-02-20 18:23:25 -0800596 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900597 }
Jae Seo39570912014-02-20 18:23:25 -0800598
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900599 private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900600 SessionState sessionState = null;
601 try {
602 sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
603 if (sessionState.session != null) {
604 UserState userState = getUserStateLocked(userId);
605 if (sessionToken == userState.mainSessionToken) {
606 setMainLocked(sessionToken, false, callingUid, userId);
607 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900608 sessionState.session.release();
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900609 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900610 } catch (RemoteException | SessionNotFoundException e) {
611 Slog.e(TAG, "error in releaseSession", e);
612 } finally {
613 if (sessionState != null) {
614 sessionState.session = null;
615 }
Jae Seo39570912014-02-20 18:23:25 -0800616 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900617 removeSessionStateLocked(sessionToken, userId);
Jae Seo39570912014-02-20 18:23:25 -0800618 }
619
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900620 private void removeSessionStateLocked(IBinder sessionToken, int userId) {
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900621 UserState userState = getUserStateLocked(userId);
Ji-Hwan Leeabca0ee2014-07-24 17:34:19 +0900622 if (sessionToken == userState.mainSessionToken) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900623 if (DEBUG) {
624 Slog.d(TAG, "mainSessionToken=null");
625 }
Ji-Hwan Leeabca0ee2014-07-24 17:34:19 +0900626 userState.mainSessionToken = null;
627 }
628
629 // Remove the session state from the global session state map of the current user.
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900630 SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
631
Chulwoo Lee8d4ded02014-07-10 03:56:39 +0900632 if (sessionState == null) {
633 return;
634 }
635
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900636 // Also remove the session token from the session token list of the current client and
637 // service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900638 ClientState clientState = userState.clientStateMap.get(sessionState.client.asBinder());
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900639 if (clientState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900640 clientState.sessionTokens.remove(sessionToken);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900641 if (clientState.isEmpty()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900642 userState.clientStateMap.remove(sessionState.client.asBinder());
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900643 }
644 }
645
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900646 TvInputInfo info = sessionState.info;
Wonsik Kim187423c2014-06-25 14:12:48 +0900647 if (info != null) {
648 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
649 if (serviceState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900650 serviceState.sessionTokens.remove(sessionToken);
Wonsik Kim187423c2014-06-25 14:12:48 +0900651 }
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900652 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900653 updateServiceConnectionLocked(sessionState.info.getComponent(), userId);
Jae Seo7eb75df2014-08-08 22:20:48 -0700654
655 // Log the end of watch.
656 SomeArgs args = SomeArgs.obtain();
657 args.arg1 = sessionToken;
658 args.arg2 = System.currentTimeMillis();
659 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_END, args).sendToTarget();
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900660 }
661
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900662 private void setMainLocked(IBinder sessionToken, boolean isMain, int callingUid, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900663 try {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900664 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
665 if (sessionState.hardwareSessionToken != null) {
666 sessionState = getSessionStateLocked(sessionState.hardwareSessionToken,
667 Process.SYSTEM_UID, userId);
668 }
669 ServiceState serviceState = getServiceStateLocked(sessionState.info.getComponent(), userId);
670 if (!serviceState.isHardware) {
671 return;
672 }
673 ITvInputSession session = getSessionLocked(sessionState);
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900674 session.setMain(isMain);
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900675 } catch (RemoteException | SessionNotFoundException e) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900676 Slog.e(TAG, "error in setMain", e);
677 }
678 }
679
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900680 private void notifyInputAddedLocked(UserState userState, String inputId) {
681 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700682 Slog.d(TAG, "notifyInputAddedLocked(inputId=" + inputId + ")");
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900683 }
684 for (ITvInputManagerCallback callback : userState.callbackSet) {
685 try {
686 callback.onInputAdded(inputId);
687 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700688 Slog.e(TAG, "failed to report added input to callback", e);
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900689 }
690 }
691 }
692
693 private void notifyInputRemovedLocked(UserState userState, String inputId) {
694 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700695 Slog.d(TAG, "notifyInputRemovedLocked(inputId=" + inputId + ")");
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900696 }
697 for (ITvInputManagerCallback callback : userState.callbackSet) {
698 try {
699 callback.onInputRemoved(inputId);
700 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700701 Slog.e(TAG, "failed to report removed input to callback", e);
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900702 }
703 }
704 }
705
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900706 private void notifyInputUpdatedLocked(UserState userState, String inputId) {
707 if (DEBUG) {
708 Slog.d(TAG, "notifyInputUpdatedLocked(inputId=" + inputId + ")");
709 }
710 for (ITvInputManagerCallback callback : userState.callbackSet) {
711 try {
712 callback.onInputUpdated(inputId);
713 } catch (RemoteException e) {
714 Slog.e(TAG, "failed to report updated input to callback", e);
715 }
716 }
717 }
718
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900719 private void notifyInputStateChangedLocked(UserState userState, String inputId,
Wonsik Kim969167d2014-06-24 16:33:17 +0900720 int state, ITvInputManagerCallback targetCallback) {
721 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700722 Slog.d(TAG, "notifyInputStateChangedLocked(inputId=" + inputId
723 + ", state=" + state + ")");
Wonsik Kim969167d2014-06-24 16:33:17 +0900724 }
725 if (targetCallback == null) {
726 for (ITvInputManagerCallback callback : userState.callbackSet) {
727 try {
728 callback.onInputStateChanged(inputId, state);
729 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700730 Slog.e(TAG, "failed to report state change to callback", e);
Wonsik Kim969167d2014-06-24 16:33:17 +0900731 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900732 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900733 } else {
734 try {
735 targetCallback.onInputStateChanged(inputId, state);
736 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700737 Slog.e(TAG, "failed to report state change to callback", e);
Wonsik Kim969167d2014-06-24 16:33:17 +0900738 }
739 }
740 }
741
742 private void setStateLocked(String inputId, int state, int userId) {
743 UserState userState = getUserStateLocked(userId);
744 TvInputState inputState = userState.inputMap.get(inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900745 ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent());
746 int oldState = inputState.state;
747 inputState.state = state;
748 if (serviceState != null && serviceState.service == null
Wonsik Kim187423c2014-06-25 14:12:48 +0900749 && shouldMaintainConnection(serviceState)) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900750 // We don't notify state change while reconnecting. It should remain disconnected.
751 return;
752 }
753 if (oldState != state) {
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900754 notifyInputStateChangedLocked(userState, inputId, state, null);
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900755 }
756 }
757
Jae Seo39570912014-02-20 18:23:25 -0800758 private final class BinderService extends ITvInputManager.Stub {
759 @Override
760 public List<TvInputInfo> getTvInputList(int userId) {
761 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
762 Binder.getCallingUid(), userId, "getTvInputList");
763 final long identity = Binder.clearCallingIdentity();
764 try {
765 synchronized (mLock) {
766 UserState userState = getUserStateLocked(resolvedUserId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900767 List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
768 for (TvInputState state : userState.inputMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900769 inputList.add(state.info);
Jae Seo39570912014-02-20 18:23:25 -0800770 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900771 return inputList;
Jae Seo39570912014-02-20 18:23:25 -0800772 }
773 } finally {
774 Binder.restoreCallingIdentity(identity);
775 }
Jae Seo39570912014-02-20 18:23:25 -0800776 }
777
778 @Override
Jae Seob3758052014-07-12 19:25:24 -0700779 public TvInputInfo getTvInputInfo(String inputId, int userId) {
780 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
781 Binder.getCallingUid(), userId, "getTvInputInfo");
782 final long identity = Binder.clearCallingIdentity();
783 try {
784 synchronized (mLock) {
785 UserState userState = getUserStateLocked(resolvedUserId);
786 TvInputState state = userState.inputMap.get(inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900787 return state == null ? null : state.info;
Jae Seob3758052014-07-12 19:25:24 -0700788 }
789 } finally {
790 Binder.restoreCallingIdentity(identity);
791 }
792 }
793
794 @Override
Dongwon Kang993f81e2014-11-27 19:34:18 +0900795 public int getTvInputState(String inputId, int userId) {
796 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
797 Binder.getCallingUid(), userId, "getTvInputState");
798 final long identity = Binder.clearCallingIdentity();
799 try {
800 synchronized (mLock) {
801 UserState userState = getUserStateLocked(resolvedUserId);
802 TvInputState state = userState.inputMap.get(inputId);
803 return state == null ? INPUT_STATE_UNKNOWN : state.state;
804 }
805 } finally {
806 Binder.restoreCallingIdentity(identity);
807 }
808 }
809
810 @Override
Jae Seo9c165d62014-08-25 14:39:26 -0700811 public List<TvContentRatingSystemInfo> getTvContentRatingSystemList(int userId) {
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900812 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
Jae Seo9c165d62014-08-25 14:39:26 -0700813 Binder.getCallingUid(), userId, "getTvContentRatingSystemList");
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900814 final long identity = Binder.clearCallingIdentity();
815 try {
816 synchronized (mLock) {
817 UserState userState = getUserStateLocked(resolvedUserId);
Jae Seo9c165d62014-08-25 14:39:26 -0700818 return userState.contentRatingSystemList;
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900819 }
820 } finally {
821 Binder.restoreCallingIdentity(identity);
822 }
823 }
824
825 @Override
Wonsik Kim969167d2014-06-24 16:33:17 +0900826 public void registerCallback(final ITvInputManagerCallback callback, int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800827 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
828 Binder.getCallingUid(), userId, "registerCallback");
829 final long identity = Binder.clearCallingIdentity();
830 try {
831 synchronized (mLock) {
Jae Seofea8dd42014-08-26 13:57:41 -0700832 final UserState userState = getUserStateLocked(resolvedUserId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900833 userState.callbackSet.add(callback);
Jae Seofea8dd42014-08-26 13:57:41 -0700834 try {
835 callback.asBinder().linkToDeath(new IBinder.DeathRecipient() {
836 @Override
837 public void binderDied() {
838 synchronized (mLock) {
839 if (userState.callbackSet != null) {
840 userState.callbackSet.remove(callback);
841 }
842 }
843 }
844 }, 0);
845 } catch (RemoteException e) {
846 Slog.e(TAG, "client process has already died", e);
847 }
Jae Seo39570912014-02-20 18:23:25 -0800848 }
849 } finally {
850 Binder.restoreCallingIdentity(identity);
851 }
852 }
853
854 @Override
Wonsik Kim969167d2014-06-24 16:33:17 +0900855 public void unregisterCallback(ITvInputManagerCallback callback, int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800856 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
857 Binder.getCallingUid(), userId, "unregisterCallback");
858 final long identity = Binder.clearCallingIdentity();
859 try {
860 synchronized (mLock) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900861 UserState userState = getUserStateLocked(resolvedUserId);
862 userState.callbackSet.remove(callback);
Jae Seo39570912014-02-20 18:23:25 -0800863 }
864 } finally {
865 Binder.restoreCallingIdentity(identity);
866 }
867 }
868
869 @Override
Jae Seo783645e2014-07-28 17:30:50 +0900870 public boolean isParentalControlsEnabled(int userId) {
871 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
872 Binder.getCallingUid(), userId, "isParentalControlsEnabled");
873 final long identity = Binder.clearCallingIdentity();
874 try {
875 synchronized (mLock) {
876 UserState userState = getUserStateLocked(resolvedUserId);
877 return userState.persistentDataStore.isParentalControlsEnabled();
878 }
879 } finally {
880 Binder.restoreCallingIdentity(identity);
881 }
882 }
883
884 @Override
885 public void setParentalControlsEnabled(boolean enabled, int userId) {
886 ensureParentalControlsPermission();
887 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
888 Binder.getCallingUid(), userId, "setParentalControlsEnabled");
889 final long identity = Binder.clearCallingIdentity();
890 try {
891 synchronized (mLock) {
892 UserState userState = getUserStateLocked(resolvedUserId);
893 userState.persistentDataStore.setParentalControlsEnabled(enabled);
894 }
895 } finally {
896 Binder.restoreCallingIdentity(identity);
897 }
898 }
899
900 @Override
901 public boolean isRatingBlocked(String rating, int userId) {
902 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
903 Binder.getCallingUid(), userId, "isRatingBlocked");
904 final long identity = Binder.clearCallingIdentity();
905 try {
906 synchronized (mLock) {
907 UserState userState = getUserStateLocked(resolvedUserId);
908 return userState.persistentDataStore.isRatingBlocked(
909 TvContentRating.unflattenFromString(rating));
910 }
911 } finally {
912 Binder.restoreCallingIdentity(identity);
913 }
914 }
915
916 @Override
917 public List<String> getBlockedRatings(int userId) {
918 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
919 Binder.getCallingUid(), userId, "getBlockedRatings");
920 final long identity = Binder.clearCallingIdentity();
921 try {
922 synchronized (mLock) {
923 UserState userState = getUserStateLocked(resolvedUserId);
924 List<String> ratings = new ArrayList<String>();
925 for (TvContentRating rating
926 : userState.persistentDataStore.getBlockedRatings()) {
927 ratings.add(rating.flattenToString());
928 }
929 return ratings;
930 }
931 } finally {
932 Binder.restoreCallingIdentity(identity);
933 }
934 }
935
936 @Override
937 public void addBlockedRating(String rating, int userId) {
938 ensureParentalControlsPermission();
939 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
940 Binder.getCallingUid(), userId, "addBlockedRating");
941 final long identity = Binder.clearCallingIdentity();
942 try {
943 synchronized (mLock) {
944 UserState userState = getUserStateLocked(resolvedUserId);
945 userState.persistentDataStore.addBlockedRating(
946 TvContentRating.unflattenFromString(rating));
947 }
948 } finally {
949 Binder.restoreCallingIdentity(identity);
950 }
951 }
952
953 @Override
954 public void removeBlockedRating(String rating, int userId) {
955 ensureParentalControlsPermission();
956 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
957 Binder.getCallingUid(), userId, "removeBlockedRating");
958 final long identity = Binder.clearCallingIdentity();
959 try {
960 synchronized (mLock) {
961 UserState userState = getUserStateLocked(resolvedUserId);
962 userState.persistentDataStore.removeBlockedRating(
963 TvContentRating.unflattenFromString(rating));
964 }
965 } finally {
966 Binder.restoreCallingIdentity(identity);
967 }
968 }
969
970 private void ensureParentalControlsPermission() {
Jae Seofc836f62014-08-27 00:47:56 +0000971 if (mContext.checkCallingPermission(
972 android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
973 != PackageManager.PERMISSION_GRANTED) {
974 throw new SecurityException(
975 "The caller does not have parental controls permission");
976 }
Jae Seo783645e2014-07-28 17:30:50 +0900977 }
978
979 @Override
Sungsoo Limd6672b52014-04-30 10:43:26 +0900980 public void createSession(final ITvInputClient client, final String inputId,
Jae Seo39570912014-02-20 18:23:25 -0800981 int seq, int userId) {
982 final int callingUid = Binder.getCallingUid();
983 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
984 userId, "createSession");
985 final long identity = Binder.clearCallingIdentity();
986 try {
987 synchronized (mLock) {
Jae Seo39570912014-02-20 18:23:25 -0800988 UserState userState = getUserStateLocked(resolvedUserId);
Dongwon Kang426c9a42014-08-26 17:39:21 +0900989 TvInputState inputState = userState.inputMap.get(inputId);
990 if (inputState == null) {
991 Slog.w(TAG, "Failed to find input state for inputId=" + inputId);
992 sendSessionTokenToClientLocked(client, inputId, null, null, seq);
993 return;
994 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900995 TvInputInfo info = inputState.info;
Wonsik Kim187423c2014-06-25 14:12:48 +0900996 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
Jae Seo39570912014-02-20 18:23:25 -0800997 if (serviceState == null) {
Wonsik Kim187423c2014-06-25 14:12:48 +0900998 serviceState = new ServiceState(info.getComponent(), resolvedUserId);
999 userState.serviceStateMap.put(info.getComponent(), serviceState);
Jae Seo39570912014-02-20 18:23:25 -08001000 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001001 // Send a null token immediately while reconnecting.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001002 if (serviceState.reconnecting == true) {
Jae Seo5c80ad22014-06-12 19:52:58 -07001003 sendSessionTokenToClientLocked(client, inputId, null, null, seq);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001004 return;
1005 }
1006
1007 // Create a new session token and a session state.
1008 IBinder sessionToken = new Binder();
Wonsik Kim187423c2014-06-25 14:12:48 +09001009 SessionState sessionState = new SessionState(sessionToken, info, client,
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001010 seq, callingUid, resolvedUserId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001011
1012 // Add them to the global session state map of the current user.
1013 userState.sessionStateMap.put(sessionToken, sessionState);
1014
1015 // Also, add them to the session state map of the current service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001016 serviceState.sessionTokens.add(sessionToken);
Jae Seo39570912014-02-20 18:23:25 -08001017
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001018 if (serviceState.service != null) {
1019 createSessionInternalLocked(serviceState.service, sessionToken,
Sungsoo Lim7de5e232014-04-12 16:51:27 +09001020 resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001021 } else {
Wonsik Kim187423c2014-06-25 14:12:48 +09001022 updateServiceConnectionLocked(info.getComponent(), resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001023 }
1024 }
1025 } finally {
1026 Binder.restoreCallingIdentity(identity);
1027 }
1028 }
1029
1030 @Override
1031 public void releaseSession(IBinder sessionToken, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001032 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -07001033 Slog.d(TAG, "releaseSession(sessionToken=" + sessionToken + ")");
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001034 }
Jae Seo39570912014-02-20 18:23:25 -08001035 final int callingUid = Binder.getCallingUid();
1036 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1037 userId, "releaseSession");
1038 final long identity = Binder.clearCallingIdentity();
1039 try {
1040 synchronized (mLock) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001041 releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001042 }
1043 } finally {
1044 Binder.restoreCallingIdentity(identity);
1045 }
1046 }
1047
1048 @Override
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001049 public void setMainSession(IBinder sessionToken, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001050 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -07001051 Slog.d(TAG, "setMainSession(sessionToken=" + sessionToken + ")");
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001052 }
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001053 final int callingUid = Binder.getCallingUid();
1054 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1055 userId, "setMainSession");
1056 final long identity = Binder.clearCallingIdentity();
1057 try {
1058 synchronized (mLock) {
Ji-Hwan Lee982abe62014-07-24 17:11:03 +09001059 UserState userState = getUserStateLocked(resolvedUserId);
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001060 if (userState.mainSessionToken == sessionToken) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001061 return;
1062 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001063 if (DEBUG) {
1064 Slog.d(TAG, "mainSessionToken=" + sessionToken);
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001065 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001066 IBinder oldMainSessionToken = userState.mainSessionToken;
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001067 userState.mainSessionToken = sessionToken;
1068
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001069 // Inform the new main session first.
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001070 // See {@link TvInputService.Session#onSetMain}.
1071 if (sessionToken != null) {
1072 setMainLocked(sessionToken, true, callingUid, userId);
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001073 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001074 if (oldMainSessionToken != null) {
1075 setMainLocked(oldMainSessionToken, false, Process.SYSTEM_UID, userId);
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001076 }
1077 }
1078 } finally {
1079 Binder.restoreCallingIdentity(identity);
1080 }
1081 }
1082
1083 @Override
Jae Seo39570912014-02-20 18:23:25 -08001084 public void setSurface(IBinder sessionToken, Surface surface, int userId) {
1085 final int callingUid = Binder.getCallingUid();
1086 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1087 userId, "setSurface");
1088 final long identity = Binder.clearCallingIdentity();
1089 try {
1090 synchronized (mLock) {
1091 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001092 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1093 resolvedUserId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001094 if (sessionState.hardwareSessionToken == null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001095 getSessionLocked(sessionState).setSurface(surface);
1096 } else {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001097 getSessionLocked(sessionState.hardwareSessionToken,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001098 Process.SYSTEM_UID, resolvedUserId).setSurface(surface);
1099 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001100 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001101 Slog.e(TAG, "error in setSurface", e);
Jae Seo39570912014-02-20 18:23:25 -08001102 }
1103 }
1104 } finally {
Youngsang Chof8362062014-04-30 17:24:20 +09001105 if (surface != null) {
1106 // surface is not used in TvInputManagerService.
1107 surface.release();
1108 }
Jae Seo39570912014-02-20 18:23:25 -08001109 Binder.restoreCallingIdentity(identity);
1110 }
1111 }
1112
1113 @Override
Youngsang Choe821d712014-07-16 14:22:19 -07001114 public void dispatchSurfaceChanged(IBinder sessionToken, int format, int width,
1115 int height, int userId) {
1116 final int callingUid = Binder.getCallingUid();
1117 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1118 userId, "dispatchSurfaceChanged");
1119 final long identity = Binder.clearCallingIdentity();
1120 try {
1121 synchronized (mLock) {
1122 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001123 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1124 resolvedUserId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001125 getSessionLocked(sessionState).dispatchSurfaceChanged(format, width,
1126 height);
1127 if (sessionState.hardwareSessionToken != null) {
1128 getSessionLocked(sessionState.hardwareSessionToken, Process.SYSTEM_UID,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001129 resolvedUserId).dispatchSurfaceChanged(format, width, height);
1130 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001131 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Choe821d712014-07-16 14:22:19 -07001132 Slog.e(TAG, "error in dispatchSurfaceChanged", e);
1133 }
1134 }
1135 } finally {
1136 Binder.restoreCallingIdentity(identity);
1137 }
1138 }
1139
1140 @Override
Jae Seo39570912014-02-20 18:23:25 -08001141 public void setVolume(IBinder sessionToken, float volume, int userId) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001142 final float REMOTE_VOLUME_ON = 1.0f;
1143 final float REMOTE_VOLUME_OFF = 0f;
Jae Seo39570912014-02-20 18:23:25 -08001144 final int callingUid = Binder.getCallingUid();
1145 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1146 userId, "setVolume");
1147 final long identity = Binder.clearCallingIdentity();
1148 try {
1149 synchronized (mLock) {
1150 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001151 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1152 resolvedUserId);
1153 getSessionLocked(sessionState).setVolume(volume);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001154 if (sessionState.hardwareSessionToken != null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001155 // Here, we let the hardware session know only whether volume is on or
1156 // off to prevent that the volume is controlled in the both side.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001157 getSessionLocked(sessionState.hardwareSessionToken,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001158 Process.SYSTEM_UID, resolvedUserId).setVolume((volume > 0.0f)
1159 ? REMOTE_VOLUME_ON : REMOTE_VOLUME_OFF);
1160 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001161 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001162 Slog.e(TAG, "error in setVolume", e);
Jae Seo39570912014-02-20 18:23:25 -08001163 }
1164 }
1165 } finally {
1166 Binder.restoreCallingIdentity(identity);
1167 }
1168 }
1169
1170 @Override
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +09001171 public void tune(IBinder sessionToken, final Uri channelUri, Bundle params, int userId) {
Jae Seo39570912014-02-20 18:23:25 -08001172 final int callingUid = Binder.getCallingUid();
1173 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1174 userId, "tune");
1175 final long identity = Binder.clearCallingIdentity();
1176 try {
1177 synchronized (mLock) {
Jae Seo39570912014-02-20 18:23:25 -08001178 try {
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +09001179 getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(
1180 channelUri, params);
Jae Seoc22d0c02014-08-15 13:03:21 -07001181 if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
Youngsang Cho008f6d42014-07-22 21:29:47 -07001182 // Do not log the watch history for passthrough inputs.
1183 return;
1184 }
Jae Seo31dc634be2014-04-15 17:40:23 -07001185
Jae Seo31dc634be2014-04-15 17:40:23 -07001186 UserState userState = getUserStateLocked(resolvedUserId);
1187 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Jae Seo31dc634be2014-04-15 17:40:23 -07001188
Jae Seo7eb75df2014-08-08 22:20:48 -07001189 // Log the start of watch.
Jae Seo31dc634be2014-04-15 17:40:23 -07001190 SomeArgs args = SomeArgs.obtain();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001191 args.arg1 = sessionState.info.getComponent().getPackageName();
Jae Seo7eb75df2014-08-08 22:20:48 -07001192 args.arg2 = System.currentTimeMillis();
1193 args.arg3 = ContentUris.parseId(channelUri);
1194 args.arg4 = params;
1195 args.arg5 = sessionToken;
1196 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_START, args)
1197 .sendToTarget();
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001198 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001199 Slog.e(TAG, "error in tune", e);
Jae Seo39570912014-02-20 18:23:25 -08001200 return;
1201 }
1202 }
1203 } finally {
1204 Binder.restoreCallingIdentity(identity);
1205 }
1206 }
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001207
1208 @Override
Sungsoo Lim9bf671f2014-07-19 12:59:51 +09001209 public void requestUnblockContent(
1210 IBinder sessionToken, String unblockedRating, int userId) {
Jaewan Kim903d6b72014-07-16 11:28:56 +09001211 final int callingUid = Binder.getCallingUid();
1212 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1213 userId, "unblockContent");
1214 final long identity = Binder.clearCallingIdentity();
1215 try {
1216 synchronized (mLock) {
1217 try {
1218 getSessionLocked(sessionToken, callingUid, resolvedUserId)
Sungsoo Lim9bf671f2014-07-19 12:59:51 +09001219 .requestUnblockContent(unblockedRating);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001220 } catch (RemoteException | SessionNotFoundException e) {
Jae Seofea8dd42014-08-26 13:57:41 -07001221 Slog.e(TAG, "error in requestUnblockContent", e);
Jaewan Kim903d6b72014-07-16 11:28:56 +09001222 }
1223 }
1224 } finally {
1225 Binder.restoreCallingIdentity(identity);
1226 }
1227 }
1228
1229 @Override
Jae Seo2c1c31c2014-07-10 14:57:01 -07001230 public void setCaptionEnabled(IBinder sessionToken, boolean enabled, int userId) {
1231 final int callingUid = Binder.getCallingUid();
1232 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1233 userId, "setCaptionEnabled");
1234 final long identity = Binder.clearCallingIdentity();
1235 try {
1236 synchronized (mLock) {
1237 try {
1238 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1239 .setCaptionEnabled(enabled);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001240 } catch (RemoteException | SessionNotFoundException e) {
Jae Seo2c1c31c2014-07-10 14:57:01 -07001241 Slog.e(TAG, "error in setCaptionEnabled", e);
1242 }
1243 }
1244 } finally {
1245 Binder.restoreCallingIdentity(identity);
1246 }
1247 }
1248
1249 @Override
Jae Seo10d285a2014-07-31 22:46:47 +09001250 public void selectTrack(IBinder sessionToken, int type, String trackId, int userId) {
Dongwon Kang1f213912014-07-02 18:35:08 +09001251 final int callingUid = Binder.getCallingUid();
1252 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1253 userId, "selectTrack");
1254 final long identity = Binder.clearCallingIdentity();
1255 try {
1256 synchronized (mLock) {
1257 try {
1258 getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack(
Jae Seo10d285a2014-07-31 22:46:47 +09001259 type, trackId);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001260 } catch (RemoteException | SessionNotFoundException e) {
Dongwon Kang1f213912014-07-02 18:35:08 +09001261 Slog.e(TAG, "error in selectTrack", e);
1262 }
1263 }
1264 } finally {
1265 Binder.restoreCallingIdentity(identity);
1266 }
1267 }
1268
1269 @Override
Jae Seoa759b112014-07-18 22:16:08 -07001270 public void sendAppPrivateCommand(IBinder sessionToken, String command, Bundle data,
1271 int userId) {
1272 final int callingUid = Binder.getCallingUid();
1273 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1274 userId, "sendAppPrivateCommand");
1275 final long identity = Binder.clearCallingIdentity();
1276 try {
1277 synchronized (mLock) {
1278 try {
1279 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1280 .appPrivateCommand(command, data);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001281 } catch (RemoteException | SessionNotFoundException e) {
Jae Seofea8dd42014-08-26 13:57:41 -07001282 Slog.e(TAG, "error in appPrivateCommand", e);
Jae Seoa759b112014-07-18 22:16:08 -07001283 }
1284 }
1285 } finally {
1286 Binder.restoreCallingIdentity(identity);
1287 }
1288 }
1289
1290 @Override
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001291 public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
1292 int userId) {
1293 final int callingUid = Binder.getCallingUid();
1294 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1295 userId, "createOverlayView");
1296 final long identity = Binder.clearCallingIdentity();
1297 try {
1298 synchronized (mLock) {
1299 try {
1300 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1301 .createOverlayView(windowToken, frame);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001302 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001303 Slog.e(TAG, "error in createOverlayView", e);
1304 }
1305 }
1306 } finally {
1307 Binder.restoreCallingIdentity(identity);
1308 }
1309 }
1310
1311 @Override
1312 public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
1313 final int callingUid = Binder.getCallingUid();
1314 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1315 userId, "relayoutOverlayView");
1316 final long identity = Binder.clearCallingIdentity();
1317 try {
1318 synchronized (mLock) {
1319 try {
1320 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1321 .relayoutOverlayView(frame);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001322 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001323 Slog.e(TAG, "error in relayoutOverlayView", e);
1324 }
1325 }
1326 } finally {
1327 Binder.restoreCallingIdentity(identity);
1328 }
1329 }
1330
1331 @Override
1332 public void removeOverlayView(IBinder sessionToken, int userId) {
1333 final int callingUid = Binder.getCallingUid();
1334 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1335 userId, "removeOverlayView");
1336 final long identity = Binder.clearCallingIdentity();
1337 try {
1338 synchronized (mLock) {
1339 try {
1340 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1341 .removeOverlayView();
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001342 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001343 Slog.e(TAG, "error in removeOverlayView", e);
1344 }
1345 }
1346 } finally {
1347 Binder.restoreCallingIdentity(identity);
1348 }
1349 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001350
1351 @Override
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001352 public void timeShiftPause(IBinder sessionToken, int userId) {
1353 final int callingUid = Binder.getCallingUid();
1354 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1355 userId, "timeShiftPause");
1356 final long identity = Binder.clearCallingIdentity();
1357 try {
1358 synchronized (mLock) {
1359 try {
1360 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1361 .timeShiftPause();
1362 } catch (RemoteException | SessionNotFoundException e) {
1363 Slog.e(TAG, "error in timeShiftPause", e);
1364 }
1365 }
1366 } finally {
1367 Binder.restoreCallingIdentity(identity);
1368 }
1369 }
1370
1371 @Override
1372 public void timeShiftResume(IBinder sessionToken, int userId) {
1373 final int callingUid = Binder.getCallingUid();
1374 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1375 userId, "timeShiftResume");
1376 final long identity = Binder.clearCallingIdentity();
1377 try {
1378 synchronized (mLock) {
1379 try {
1380 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1381 .timeShiftResume();
1382 } catch (RemoteException | SessionNotFoundException e) {
1383 Slog.e(TAG, "error in timeShiftResume", e);
1384 }
1385 }
1386 } finally {
1387 Binder.restoreCallingIdentity(identity);
1388 }
1389 }
1390
1391 @Override
1392 public void timeShiftSeekTo(IBinder sessionToken, long timeMs, int userId) {
1393 final int callingUid = Binder.getCallingUid();
1394 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1395 userId, "timeShiftSeekTo");
1396 final long identity = Binder.clearCallingIdentity();
1397 try {
1398 synchronized (mLock) {
1399 try {
1400 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1401 .timeShiftSeekTo(timeMs);
1402 } catch (RemoteException | SessionNotFoundException e) {
1403 Slog.e(TAG, "error in timeShiftSeekTo", e);
1404 }
1405 }
1406 } finally {
1407 Binder.restoreCallingIdentity(identity);
1408 }
1409 }
1410
1411 @Override
1412 public void timeShiftSetPlaybackRate(IBinder sessionToken, float rate, int audioMode,
1413 int userId) {
1414 final int callingUid = Binder.getCallingUid();
1415 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1416 userId, "timeShiftSetPlaybackRate");
1417 final long identity = Binder.clearCallingIdentity();
1418 try {
1419 synchronized (mLock) {
1420 try {
1421 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1422 .timeShiftSetPlaybackRate(rate, audioMode);
1423 } catch (RemoteException | SessionNotFoundException e) {
1424 Slog.e(TAG, "error in timeShiftSetPlaybackRate", e);
1425 }
1426 }
1427 } finally {
1428 Binder.restoreCallingIdentity(identity);
1429 }
1430 }
1431
1432 @Override
Jae Seo465f0d62015-04-06 18:40:46 -07001433 public void timeShiftEnablePositionTracking(IBinder sessionToken, boolean enable,
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001434 int userId) {
1435 final int callingUid = Binder.getCallingUid();
1436 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
Jae Seo465f0d62015-04-06 18:40:46 -07001437 userId, "timeShiftEnablePositionTracking");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001438 final long identity = Binder.clearCallingIdentity();
1439 try {
1440 synchronized (mLock) {
1441 try {
1442 getSessionLocked(sessionToken, callingUid, resolvedUserId)
Jae Seo465f0d62015-04-06 18:40:46 -07001443 .timeShiftEnablePositionTracking(enable);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001444 } catch (RemoteException | SessionNotFoundException e) {
Jae Seo465f0d62015-04-06 18:40:46 -07001445 Slog.e(TAG, "error in timeShiftEnablePositionTracking", e);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001446 }
1447 }
1448 } finally {
1449 Binder.restoreCallingIdentity(identity);
1450 }
1451 }
1452
1453 @Override
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001454 public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
Wonsik Kim969167d2014-06-24 16:33:17 +09001455 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001456 != PackageManager.PERMISSION_GRANTED) {
1457 return null;
1458 }
1459
1460 final long identity = Binder.clearCallingIdentity();
1461 try {
1462 return mTvInputHardwareManager.getHardwareList();
1463 } finally {
1464 Binder.restoreCallingIdentity(identity);
1465 }
1466 }
1467
1468 @Override
1469 public ITvInputHardware acquireTvInputHardware(int deviceId,
Wonsik Kim969167d2014-06-24 16:33:17 +09001470 ITvInputHardwareCallback callback, TvInputInfo info, int userId)
1471 throws RemoteException {
1472 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001473 != PackageManager.PERMISSION_GRANTED) {
1474 return null;
1475 }
1476
1477 final long identity = Binder.clearCallingIdentity();
1478 final int callingUid = Binder.getCallingUid();
1479 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1480 userId, "acquireTvInputHardware");
1481 try {
1482 return mTvInputHardwareManager.acquireHardware(
Wonsik Kim969167d2014-06-24 16:33:17 +09001483 deviceId, callback, info, callingUid, resolvedUserId);
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001484 } finally {
1485 Binder.restoreCallingIdentity(identity);
1486 }
1487 }
1488
1489 @Override
1490 public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
1491 throws RemoteException {
Wonsik Kim969167d2014-06-24 16:33:17 +09001492 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001493 != PackageManager.PERMISSION_GRANTED) {
1494 return;
1495 }
1496
1497 final long identity = Binder.clearCallingIdentity();
1498 final int callingUid = Binder.getCallingUid();
1499 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1500 userId, "releaseTvInputHardware");
1501 try {
1502 mTvInputHardwareManager.releaseHardware(
1503 deviceId, hardware, callingUid, resolvedUserId);
1504 } finally {
1505 Binder.restoreCallingIdentity(identity);
1506 }
1507 }
Jaewan Kime14c3f42014-06-27 13:47:48 +09001508
1509 @Override
Terry Heoc086a3d2014-06-18 14:26:44 +09001510 public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int userId)
1511 throws RemoteException {
1512 if (mContext.checkCallingPermission(
1513 android.Manifest.permission.CAPTURE_TV_INPUT)
1514 != PackageManager.PERMISSION_GRANTED) {
1515 throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1516 }
1517
1518 final long identity = Binder.clearCallingIdentity();
1519 final int callingUid = Binder.getCallingUid();
1520 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1521 userId, "getAvailableTvStreamConfigList");
1522 try {
1523 return mTvInputHardwareManager.getAvailableTvStreamConfigList(
1524 inputId, callingUid, resolvedUserId);
1525 } finally {
1526 Binder.restoreCallingIdentity(identity);
1527 }
1528 }
1529
1530 @Override
1531 public boolean captureFrame(String inputId, Surface surface, TvStreamConfig config,
1532 int userId)
1533 throws RemoteException {
1534 if (mContext.checkCallingPermission(
1535 android.Manifest.permission.CAPTURE_TV_INPUT)
1536 != PackageManager.PERMISSION_GRANTED) {
1537 throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1538 }
1539
1540 final long identity = Binder.clearCallingIdentity();
1541 final int callingUid = Binder.getCallingUid();
1542 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1543 userId, "captureFrame");
1544 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001545 String hardwareInputId = null;
Terry Heo79124a72014-07-21 15:17:17 +09001546 synchronized (mLock) {
1547 UserState userState = getUserStateLocked(resolvedUserId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001548 if (userState.inputMap.get(inputId) == null) {
Jae Seofea8dd42014-08-26 13:57:41 -07001549 Slog.e(TAG, "input not found for " + inputId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001550 return false;
1551 }
1552 for (SessionState sessionState : userState.sessionStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001553 if (sessionState.info.getId().equals(inputId)
1554 && sessionState.hardwareSessionToken != null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001555 hardwareInputId = userState.sessionStateMap.get(
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001556 sessionState.hardwareSessionToken).info.getId();
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001557 break;
1558 }
1559 }
Terry Heo79124a72014-07-21 15:17:17 +09001560 }
Terry Heoc086a3d2014-06-18 14:26:44 +09001561 return mTvInputHardwareManager.captureFrame(
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001562 (hardwareInputId != null) ? hardwareInputId : inputId,
Terry Heo79124a72014-07-21 15:17:17 +09001563 surface, config, callingUid, resolvedUserId);
Terry Heoc086a3d2014-06-18 14:26:44 +09001564 } finally {
1565 Binder.restoreCallingIdentity(identity);
1566 }
1567 }
1568
1569 @Override
Terry Heodf9f0a32014-08-06 13:53:33 +09001570 public boolean isSingleSessionActive(int userId) throws RemoteException {
1571 final long identity = Binder.clearCallingIdentity();
1572 final int callingUid = Binder.getCallingUid();
1573 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1574 userId, "isSingleSessionActive");
1575 try {
1576 synchronized (mLock) {
1577 UserState userState = getUserStateLocked(resolvedUserId);
1578 if (userState.sessionStateMap.size() == 1) {
1579 return true;
1580 }
1581 else if (userState.sessionStateMap.size() == 2) {
1582 SessionState[] sessionStates = userState.sessionStateMap.values().toArray(
1583 new SessionState[0]);
1584 // Check if there is a wrapper input.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001585 if (sessionStates[0].hardwareSessionToken != null
1586 || sessionStates[1].hardwareSessionToken != null) {
Terry Heodf9f0a32014-08-06 13:53:33 +09001587 return true;
1588 }
1589 }
1590 return false;
1591 }
1592 } finally {
1593 Binder.restoreCallingIdentity(identity);
1594 }
1595 }
1596
1597 @Override
Jae Seo0f8fc342014-07-02 10:47:08 -07001598 @SuppressWarnings("resource")
Jaewan Kime14c3f42014-06-27 13:47:48 +09001599 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1600 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
Jae Seo0f8fc342014-07-02 10:47:08 -07001601 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
Jaewan Kime14c3f42014-06-27 13:47:48 +09001602 != PackageManager.PERMISSION_GRANTED) {
Jae Seo0f8fc342014-07-02 10:47:08 -07001603 pw.println("Permission Denial: can't dump TvInputManager from pid="
1604 + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
Jaewan Kime14c3f42014-06-27 13:47:48 +09001605 return;
1606 }
1607
1608 synchronized (mLock) {
1609 pw.println("User Ids (Current user: " + mCurrentUserId + "):");
1610 pw.increaseIndent();
1611 for (int i = 0; i < mUserStates.size(); i++) {
1612 int userId = mUserStates.keyAt(i);
1613 pw.println(Integer.valueOf(userId));
1614 }
1615 pw.decreaseIndent();
1616
1617 for (int i = 0; i < mUserStates.size(); i++) {
1618 int userId = mUserStates.keyAt(i);
1619 UserState userState = getUserStateLocked(userId);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001620 pw.println("UserState (" + userId + "):");
1621 pw.increaseIndent();
1622
Wonsik Kim969167d2014-06-24 16:33:17 +09001623 pw.println("inputMap: inputId -> TvInputState");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001624 pw.increaseIndent();
Jaewan Kim8e6b51b2014-07-15 13:01:57 +09001625 for (Map.Entry<String, TvInputState> entry: userState.inputMap.entrySet()) {
1626 pw.println(entry.getKey() + ": " + entry.getValue());
Jaewan Kime14c3f42014-06-27 13:47:48 +09001627 }
1628 pw.decreaseIndent();
1629
Wonsik Kim969167d2014-06-24 16:33:17 +09001630 pw.println("packageSet:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001631 pw.increaseIndent();
Wonsik Kim969167d2014-06-24 16:33:17 +09001632 for (String packageName : userState.packageSet) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09001633 pw.println(packageName);
1634 }
1635 pw.decreaseIndent();
1636
1637 pw.println("clientStateMap: ITvInputClient -> ClientState");
1638 pw.increaseIndent();
1639 for (Map.Entry<IBinder, ClientState> entry :
1640 userState.clientStateMap.entrySet()) {
1641 ClientState client = entry.getValue();
1642 pw.println(entry.getKey() + ": " + client);
1643
1644 pw.increaseIndent();
1645
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001646 pw.println("sessionTokens:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001647 pw.increaseIndent();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001648 for (IBinder token : client.sessionTokens) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09001649 pw.println("" + token);
1650 }
1651 pw.decreaseIndent();
1652
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001653 pw.println("clientTokens: " + client.clientToken);
1654 pw.println("userId: " + client.userId);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001655
1656 pw.decreaseIndent();
1657 }
1658 pw.decreaseIndent();
1659
Wonsik Kim187423c2014-06-25 14:12:48 +09001660 pw.println("serviceStateMap: ComponentName -> ServiceState");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001661 pw.increaseIndent();
Wonsik Kim187423c2014-06-25 14:12:48 +09001662 for (Map.Entry<ComponentName, ServiceState> entry :
Jaewan Kime14c3f42014-06-27 13:47:48 +09001663 userState.serviceStateMap.entrySet()) {
1664 ServiceState service = entry.getValue();
1665 pw.println(entry.getKey() + ": " + service);
1666
1667 pw.increaseIndent();
1668
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001669 pw.println("sessionTokens:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001670 pw.increaseIndent();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001671 for (IBinder token : service.sessionTokens) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09001672 pw.println("" + token);
1673 }
1674 pw.decreaseIndent();
1675
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001676 pw.println("service: " + service.service);
1677 pw.println("callback: " + service.callback);
1678 pw.println("bound: " + service.bound);
1679 pw.println("reconnecting: " + service.reconnecting);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001680
1681 pw.decreaseIndent();
1682 }
1683 pw.decreaseIndent();
1684
1685 pw.println("sessionStateMap: ITvInputSession -> SessionState");
1686 pw.increaseIndent();
1687 for (Map.Entry<IBinder, SessionState> entry :
1688 userState.sessionStateMap.entrySet()) {
1689 SessionState session = entry.getValue();
1690 pw.println(entry.getKey() + ": " + session);
1691
1692 pw.increaseIndent();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001693 pw.println("info: " + session.info);
1694 pw.println("client: " + session.client);
1695 pw.println("seq: " + session.seq);
1696 pw.println("callingUid: " + session.callingUid);
1697 pw.println("userId: " + session.userId);
1698 pw.println("sessionToken: " + session.sessionToken);
1699 pw.println("session: " + session.session);
1700 pw.println("logUri: " + session.logUri);
1701 pw.println("hardwareSessionToken: " + session.hardwareSessionToken);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001702 pw.decreaseIndent();
1703 }
1704 pw.decreaseIndent();
1705
Wonsik Kim969167d2014-06-24 16:33:17 +09001706 pw.println("callbackSet:");
1707 pw.increaseIndent();
1708 for (ITvInputManagerCallback callback : userState.callbackSet) {
1709 pw.println(callback.toString());
1710 }
1711 pw.decreaseIndent();
1712
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001713 pw.println("mainSessionToken: " + userState.mainSessionToken);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001714 pw.decreaseIndent();
1715 }
1716 }
1717 }
Jae Seo39570912014-02-20 18:23:25 -08001718 }
1719
Wonsik Kim969167d2014-06-24 16:33:17 +09001720 private static final class UserState {
1721 // A mapping from the TV input id to its TvInputState.
1722 private Map<String, TvInputState> inputMap = new HashMap<String, TvInputState>();
1723
1724 // A set of all TV input packages.
1725 private final Set<String> packageSet = new HashSet<String>();
Jae Seo5c80ad22014-06-12 19:52:58 -07001726
Jae Seo9c165d62014-08-25 14:39:26 -07001727 // A list of all TV content rating systems defined.
1728 private final List<TvContentRatingSystemInfo>
1729 contentRatingSystemList = new ArrayList<TvContentRatingSystemInfo>();
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +09001730
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001731 // A mapping from the token of a client to its state.
1732 private final Map<IBinder, ClientState> clientStateMap =
1733 new HashMap<IBinder, ClientState>();
1734
Jae Seo39570912014-02-20 18:23:25 -08001735 // A mapping from the name of a TV input service to its state.
Wonsik Kim187423c2014-06-25 14:12:48 +09001736 private final Map<ComponentName, ServiceState> serviceStateMap =
1737 new HashMap<ComponentName, ServiceState>();
Jae Seo39570912014-02-20 18:23:25 -08001738
1739 // A mapping from the token of a TV input session to its state.
1740 private final Map<IBinder, SessionState> sessionStateMap =
1741 new HashMap<IBinder, SessionState>();
Wonsik Kim969167d2014-06-24 16:33:17 +09001742
1743 // A set of callbacks.
1744 private final Set<ITvInputManagerCallback> callbackSet =
1745 new HashSet<ITvInputManagerCallback>();
Terry Heo79124a72014-07-21 15:17:17 +09001746
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001747 // The token of a "main" TV input session.
1748 private IBinder mainSessionToken = null;
Jae Seo783645e2014-07-28 17:30:50 +09001749
1750 // Persistent data store for all internal settings maintained by the TV input manager
1751 // service.
1752 private final PersistentDataStore persistentDataStore;
1753
1754 private UserState(Context context, int userId) {
1755 persistentDataStore = new PersistentDataStore(context, userId);
1756 }
Jae Seo39570912014-02-20 18:23:25 -08001757 }
1758
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001759 private final class ClientState implements IBinder.DeathRecipient {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001760 private final List<IBinder> sessionTokens = new ArrayList<IBinder>();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001761
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001762 private IBinder clientToken;
1763 private final int userId;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001764
1765 ClientState(IBinder clientToken, int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001766 this.clientToken = clientToken;
1767 this.userId = userId;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001768 }
1769
1770 public boolean isEmpty() {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001771 return sessionTokens.isEmpty();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001772 }
1773
1774 @Override
1775 public void binderDied() {
1776 synchronized (mLock) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001777 UserState userState = getUserStateLocked(userId);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001778 // DO NOT remove the client state of clientStateMap in this method. It will be
Ji-Hwan Leea65118e2014-07-24 16:30:02 +09001779 // removed in releaseSessionLocked().
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001780 ClientState clientState = userState.clientStateMap.get(clientToken);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001781 if (clientState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001782 while (clientState.sessionTokens.size() > 0) {
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001783 releaseSessionLocked(
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001784 clientState.sessionTokens.get(0), Process.SYSTEM_UID, userId);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001785 }
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001786 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001787 clientToken = null;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001788 }
1789 }
1790 }
1791
Jae Seo39570912014-02-20 18:23:25 -08001792 private final class ServiceState {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001793 private final List<IBinder> sessionTokens = new ArrayList<IBinder>();
1794 private final ServiceConnection connection;
1795 private final ComponentName component;
1796 private final boolean isHardware;
1797 private final List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
Jae Seo39570912014-02-20 18:23:25 -08001798
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001799 private ITvInputService service;
1800 private ServiceCallback callback;
1801 private boolean bound;
1802 private boolean reconnecting;
Jae Seo39570912014-02-20 18:23:25 -08001803
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001804 private ServiceState(ComponentName component, int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001805 this.component = component;
1806 this.connection = new InputServiceConnection(component, userId);
1807 this.isHardware = hasHardwarePermission(mContext.getPackageManager(), component);
1808 }
1809 }
1810
1811 private static final class TvInputState {
1812 // A TvInputInfo object which represents the TV input.
1813 private TvInputInfo info;
1814
1815 // The state of TV input. Connected by default.
1816 private int state = INPUT_STATE_CONNECTED;
1817
1818 @Override
1819 public String toString() {
1820 return "info: " + info + "; state: " + state;
Jae Seo39570912014-02-20 18:23:25 -08001821 }
1822 }
1823
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001824 private final class SessionState implements IBinder.DeathRecipient {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001825 private final TvInputInfo info;
1826 private final ITvInputClient client;
1827 private final int seq;
1828 private final int callingUid;
1829 private final int userId;
1830 private final IBinder sessionToken;
1831 private ITvInputSession session;
1832 private Uri logUri;
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001833 // Not null if this session represents an external device connected to a hardware TV input.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001834 private IBinder hardwareSessionToken;
Jae Seo39570912014-02-20 18:23:25 -08001835
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001836 private SessionState(IBinder sessionToken, TvInputInfo info, ITvInputClient client,
1837 int seq, int callingUid, int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001838 this.sessionToken = sessionToken;
1839 this.info = info;
1840 this.client = client;
1841 this.seq = seq;
1842 this.callingUid = callingUid;
1843 this.userId = userId;
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001844 }
1845
1846 @Override
1847 public void binderDied() {
1848 synchronized (mLock) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001849 session = null;
1850 if (client != null) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001851 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001852 client.onSessionReleased(seq);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001853 } catch(RemoteException e) {
1854 Slog.e(TAG, "error in onSessionReleased", e);
1855 }
1856 }
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001857 // If there are any other sessions based on this session, they should be released.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001858 UserState userState = getUserStateLocked(userId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001859 for (SessionState sessionState : userState.sessionStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001860 if (sessionToken == sessionState.hardwareSessionToken) {
1861 releaseSessionLocked(sessionState.sessionToken, Process.SYSTEM_UID,
1862 userId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001863 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001864 sessionState.client.onSessionReleased(sessionState.seq);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001865 } catch (RemoteException e) {
1866 Slog.e(TAG, "error in onSessionReleased", e);
1867 }
1868 }
1869 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001870 removeSessionStateLocked(sessionToken, userId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001871 }
Jae Seo39570912014-02-20 18:23:25 -08001872 }
1873 }
1874
1875 private final class InputServiceConnection implements ServiceConnection {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001876 private final ComponentName mComponent;
Jae Seo39570912014-02-20 18:23:25 -08001877 private final int mUserId;
1878
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001879 private InputServiceConnection(ComponentName component, int userId) {
1880 mComponent = component;
Jae Seo39570912014-02-20 18:23:25 -08001881 mUserId = userId;
1882 }
1883
1884 @Override
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001885 public void onServiceConnected(ComponentName component, IBinder service) {
Jae Seo39570912014-02-20 18:23:25 -08001886 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001887 Slog.d(TAG, "onServiceConnected(component=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -08001888 }
1889 synchronized (mLock) {
Wonsik Kim969167d2014-06-24 16:33:17 +09001890 UserState userState = getUserStateLocked(mUserId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001891 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001892 serviceState.service = ITvInputService.Stub.asInterface(service);
Jae Seo39570912014-02-20 18:23:25 -08001893
1894 // Register a callback, if we need to.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001895 if (serviceState.isHardware && serviceState.callback == null) {
1896 serviceState.callback = new ServiceCallback(mComponent, mUserId);
Jae Seo39570912014-02-20 18:23:25 -08001897 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001898 serviceState.service.registerCallback(serviceState.callback);
Jae Seo39570912014-02-20 18:23:25 -08001899 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001900 Slog.e(TAG, "error in registerCallback", e);
Jae Seo39570912014-02-20 18:23:25 -08001901 }
1902 }
1903
1904 // And create sessions, if any.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001905 for (IBinder sessionToken : serviceState.sessionTokens) {
1906 createSessionInternalLocked(serviceState.service, sessionToken, mUserId);
Jae Seo39570912014-02-20 18:23:25 -08001907 }
Wonsik Kim969167d2014-06-24 16:33:17 +09001908
Wonsik Kim187423c2014-06-25 14:12:48 +09001909 for (TvInputState inputState : userState.inputMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001910 if (inputState.info.getComponent().equals(component)
1911 && inputState.state != INPUT_STATE_DISCONNECTED) {
1912 notifyInputStateChangedLocked(userState, inputState.info.getId(),
1913 inputState.state, null);
Wonsik Kim187423c2014-06-25 14:12:48 +09001914 }
1915 }
1916
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001917 if (serviceState.isHardware) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001918 List<TvInputHardwareInfo> hardwareInfoList =
1919 mTvInputHardwareManager.getHardwareList();
Wonsik Kim187423c2014-06-25 14:12:48 +09001920 for (TvInputHardwareInfo hardwareInfo : hardwareInfoList) {
1921 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001922 serviceState.service.notifyHardwareAdded(hardwareInfo);
Wonsik Kim187423c2014-06-25 14:12:48 +09001923 } catch (RemoteException e) {
1924 Slog.e(TAG, "error in notifyHardwareAdded", e);
1925 }
1926 }
1927
Jae Seo546c6352014-08-07 11:57:01 -07001928 List<HdmiDeviceInfo> deviceInfoList =
1929 mTvInputHardwareManager.getHdmiDeviceList();
1930 for (HdmiDeviceInfo deviceInfo : deviceInfoList) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001931 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001932 serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001933 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07001934 Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001935 }
1936 }
Wonsik Kim969167d2014-06-24 16:33:17 +09001937 }
Jae Seo39570912014-02-20 18:23:25 -08001938 }
1939 }
1940
1941 @Override
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001942 public void onServiceDisconnected(ComponentName component) {
Jae Seo39570912014-02-20 18:23:25 -08001943 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001944 Slog.d(TAG, "onServiceDisconnected(component=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -08001945 }
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001946 if (!mComponent.equals(component)) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001947 throw new IllegalArgumentException("Mismatched ComponentName: "
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001948 + mComponent + " (expected), " + component + " (actual).");
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001949 }
1950 synchronized (mLock) {
1951 UserState userState = getUserStateLocked(mUserId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001952 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001953 if (serviceState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001954 serviceState.reconnecting = true;
1955 serviceState.bound = false;
1956 serviceState.service = null;
1957 serviceState.callback = null;
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001958
Dongwon Kang426c9a42014-08-26 17:39:21 +09001959 abortPendingCreateSessionRequestsLocked(serviceState, null, mUserId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001960
Wonsik Kim187423c2014-06-25 14:12:48 +09001961 for (TvInputState inputState : userState.inputMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001962 if (inputState.info.getComponent().equals(component)) {
1963 notifyInputStateChangedLocked(userState, inputState.info.getId(),
Wonsik Kim187423c2014-06-25 14:12:48 +09001964 INPUT_STATE_DISCONNECTED, null);
1965 }
1966 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001967 }
1968 }
Jae Seo39570912014-02-20 18:23:25 -08001969 }
1970 }
1971
1972 private final class ServiceCallback extends ITvInputServiceCallback.Stub {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001973 private final ComponentName mComponent;
Jae Seo39570912014-02-20 18:23:25 -08001974 private final int mUserId;
1975
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001976 ServiceCallback(ComponentName component, int userId) {
1977 mComponent = component;
Jae Seo39570912014-02-20 18:23:25 -08001978 mUserId = userId;
1979 }
1980
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001981 private void ensureHardwarePermission() {
1982 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1983 != PackageManager.PERMISSION_GRANTED) {
1984 throw new SecurityException("The caller does not have hardware permission");
1985 }
1986 }
Wonsik Kim187423c2014-06-25 14:12:48 +09001987
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001988 private void ensureValidInput(TvInputInfo inputInfo) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001989 if (inputInfo.getId() == null || !mComponent.equals(inputInfo.getComponent())) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001990 throw new IllegalArgumentException("Invalid TvInputInfo");
1991 }
1992 }
1993
1994 private void addTvInputLocked(TvInputInfo inputInfo) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001995 ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001996 serviceState.inputList.add(inputInfo);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +09001997 buildTvInputListLocked(mUserId, null);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001998 }
1999
2000 @Override
2001 public void addHardwareTvInput(int deviceId, TvInputInfo inputInfo) {
2002 ensureHardwarePermission();
2003 ensureValidInput(inputInfo);
2004 synchronized (mLock) {
2005 mTvInputHardwareManager.addHardwareTvInput(deviceId, inputInfo);
2006 addTvInputLocked(inputInfo);
2007 }
2008 }
2009
2010 @Override
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09002011 public void addHdmiTvInput(int id, TvInputInfo inputInfo) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002012 ensureHardwarePermission();
2013 ensureValidInput(inputInfo);
2014 synchronized (mLock) {
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09002015 mTvInputHardwareManager.addHdmiTvInput(id, inputInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002016 addTvInputLocked(inputInfo);
Wonsik Kim187423c2014-06-25 14:12:48 +09002017 }
2018 }
2019
2020 @Override
2021 public void removeTvInput(String inputId) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002022 ensureHardwarePermission();
Wonsik Kim187423c2014-06-25 14:12:48 +09002023 synchronized (mLock) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002024 ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002025 boolean removed = false;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002026 for (Iterator<TvInputInfo> it = serviceState.inputList.iterator();
Wonsik Kim187423c2014-06-25 14:12:48 +09002027 it.hasNext(); ) {
2028 if (it.next().getId().equals(inputId)) {
2029 it.remove();
2030 removed = true;
2031 break;
2032 }
2033 }
2034 if (removed) {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +09002035 buildTvInputListLocked(mUserId, null);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002036 mTvInputHardwareManager.removeTvInput(inputId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002037 } else {
Jae Seofea8dd42014-08-26 13:57:41 -07002038 Slog.e(TAG, "failed to remove input " + inputId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002039 }
Jae Seo39570912014-02-20 18:23:25 -08002040 }
2041 }
2042 }
Jae Seo31dc634be2014-04-15 17:40:23 -07002043
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002044 private final class SessionCallback extends ITvInputSessionCallback.Stub {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002045 private final SessionState mSessionState;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002046 private final InputChannel[] mChannels;
2047
2048 SessionCallback(SessionState sessionState, InputChannel[] channels) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002049 mSessionState = sessionState;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002050 mChannels = channels;
2051 }
2052
2053 @Override
2054 public void onSessionCreated(ITvInputSession session, IBinder harewareSessionToken) {
2055 if (DEBUG) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002056 Slog.d(TAG, "onSessionCreated(inputId=" + mSessionState.info.getId() + ")");
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002057 }
2058 synchronized (mLock) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002059 mSessionState.session = session;
2060 mSessionState.hardwareSessionToken = harewareSessionToken;
2061 if (session != null && addSessionTokenToClientStateLocked(session)) {
2062 sendSessionTokenToClientLocked(mSessionState.client,
2063 mSessionState.info.getId(), mSessionState.sessionToken, mChannels[0],
2064 mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002065 } else {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002066 removeSessionStateLocked(mSessionState.sessionToken, mSessionState.userId);
2067 sendSessionTokenToClientLocked(mSessionState.client,
2068 mSessionState.info.getId(), null, null, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002069 }
2070 mChannels[0].dispose();
2071 }
2072 }
2073
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002074 private boolean addSessionTokenToClientStateLocked(ITvInputSession session) {
2075 try {
2076 session.asBinder().linkToDeath(mSessionState, 0);
2077 } catch (RemoteException e) {
2078 Slog.e(TAG, "session process has already died", e);
2079 return false;
2080 }
2081
2082 IBinder clientToken = mSessionState.client.asBinder();
2083 UserState userState = getUserStateLocked(mSessionState.userId);
2084 ClientState clientState = userState.clientStateMap.get(clientToken);
2085 if (clientState == null) {
2086 clientState = new ClientState(clientToken, mSessionState.userId);
2087 try {
2088 clientToken.linkToDeath(clientState, 0);
2089 } catch (RemoteException e) {
2090 Slog.e(TAG, "client process has already died", e);
2091 return false;
2092 }
2093 userState.clientStateMap.put(clientToken, clientState);
2094 }
2095 clientState.sessionTokens.add(mSessionState.sessionToken);
2096 return true;
2097 }
2098
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002099 @Override
2100 public void onChannelRetuned(Uri channelUri) {
2101 synchronized (mLock) {
2102 if (DEBUG) {
2103 Slog.d(TAG, "onChannelRetuned(" + channelUri + ")");
2104 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002105 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002106 return;
2107 }
2108 try {
2109 // TODO: Consider adding this channel change in the watch log. When we do
2110 // that, how we can protect the watch log from malicious tv inputs should
2111 // be addressed. e.g. add a field which represents where the channel change
2112 // originated from.
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002113 mSessionState.client.onChannelRetuned(channelUri, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002114 } catch (RemoteException e) {
2115 Slog.e(TAG, "error in onChannelRetuned", e);
2116 }
2117 }
2118 }
2119
2120 @Override
2121 public void onTracksChanged(List<TvTrackInfo> tracks) {
2122 synchronized (mLock) {
2123 if (DEBUG) {
2124 Slog.d(TAG, "onTracksChanged(" + tracks + ")");
2125 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002126 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002127 return;
2128 }
2129 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002130 mSessionState.client.onTracksChanged(tracks, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002131 } catch (RemoteException e) {
2132 Slog.e(TAG, "error in onTracksChanged", e);
2133 }
2134 }
2135 }
2136
2137 @Override
2138 public void onTrackSelected(int type, String trackId) {
2139 synchronized (mLock) {
2140 if (DEBUG) {
2141 Slog.d(TAG, "onTrackSelected(type=" + type + ", trackId=" + trackId + ")");
2142 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002143 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002144 return;
2145 }
2146 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002147 mSessionState.client.onTrackSelected(type, trackId, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002148 } catch (RemoteException e) {
2149 Slog.e(TAG, "error in onTrackSelected", e);
2150 }
2151 }
2152 }
2153
2154 @Override
2155 public void onVideoAvailable() {
2156 synchronized (mLock) {
2157 if (DEBUG) {
2158 Slog.d(TAG, "onVideoAvailable()");
2159 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002160 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002161 return;
2162 }
2163 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002164 mSessionState.client.onVideoAvailable(mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002165 } catch (RemoteException e) {
2166 Slog.e(TAG, "error in onVideoAvailable", e);
2167 }
2168 }
2169 }
2170
2171 @Override
2172 public void onVideoUnavailable(int reason) {
2173 synchronized (mLock) {
2174 if (DEBUG) {
2175 Slog.d(TAG, "onVideoUnavailable(" + reason + ")");
2176 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002177 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002178 return;
2179 }
2180 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002181 mSessionState.client.onVideoUnavailable(reason, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002182 } catch (RemoteException e) {
2183 Slog.e(TAG, "error in onVideoUnavailable", e);
2184 }
2185 }
2186 }
2187
2188 @Override
2189 public void onContentAllowed() {
2190 synchronized (mLock) {
2191 if (DEBUG) {
2192 Slog.d(TAG, "onContentAllowed()");
2193 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002194 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002195 return;
2196 }
2197 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002198 mSessionState.client.onContentAllowed(mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002199 } catch (RemoteException e) {
2200 Slog.e(TAG, "error in onContentAllowed", e);
2201 }
2202 }
2203 }
2204
2205 @Override
2206 public void onContentBlocked(String rating) {
2207 synchronized (mLock) {
2208 if (DEBUG) {
2209 Slog.d(TAG, "onContentBlocked()");
2210 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002211 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002212 return;
2213 }
2214 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002215 mSessionState.client.onContentBlocked(rating, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002216 } catch (RemoteException e) {
2217 Slog.e(TAG, "error in onContentBlocked", e);
2218 }
2219 }
2220 }
2221
2222 @Override
2223 public void onLayoutSurface(int left, int top, int right, int bottom) {
2224 synchronized (mLock) {
2225 if (DEBUG) {
2226 Slog.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top
2227 + ", right=" + right + ", bottom=" + bottom + ",)");
2228 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002229 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002230 return;
2231 }
2232 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002233 mSessionState.client.onLayoutSurface(left, top, right, bottom,
2234 mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002235 } catch (RemoteException e) {
2236 Slog.e(TAG, "error in onLayoutSurface", e);
2237 }
2238 }
2239 }
2240
2241 @Override
2242 public void onSessionEvent(String eventType, Bundle eventArgs) {
2243 synchronized (mLock) {
2244 if (DEBUG) {
2245 Slog.d(TAG, "onEvent(what=" + eventType + ", data=" + eventArgs + ")");
2246 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002247 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002248 return;
2249 }
2250 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002251 mSessionState.client.onSessionEvent(eventType, eventArgs, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002252 } catch (RemoteException e) {
2253 Slog.e(TAG, "error in onSessionEvent", e);
2254 }
2255 }
2256 }
Dongwon Kang6f0240c2015-03-31 17:56:36 -07002257
2258 @Override
2259 public void onTimeShiftStatusChanged(int status) {
2260 synchronized (mLock) {
2261 if (DEBUG) {
2262 Slog.d(TAG, "onTimeShiftStatusChanged()");
2263 }
2264 if (mSessionState.session == null || mSessionState.client == null) {
2265 return;
2266 }
2267 try {
2268 mSessionState.client.onTimeShiftStatusChanged(status, mSessionState.seq);
2269 } catch (RemoteException e) {
2270 Slog.e(TAG, "error in onTimeShiftStatusChanged", e);
2271 }
2272 }
2273 }
2274
2275 @Override
2276 public void onTimeShiftStartPositionChanged(long timeMs) {
2277 synchronized (mLock) {
2278 if (DEBUG) {
2279 Slog.d(TAG, "onTimeShiftStartPositionChanged()");
2280 }
2281 if (mSessionState.session == null || mSessionState.client == null) {
2282 return;
2283 }
2284 try {
2285 mSessionState.client.onTimeShiftStartPositionChanged(timeMs, mSessionState.seq);
2286 } catch (RemoteException e) {
2287 Slog.e(TAG, "error in onTimeShiftStartPositionChanged", e);
2288 }
2289 }
2290 }
2291
2292 @Override
2293 public void onTimeShiftCurrentPositionChanged(long timeMs) {
2294 synchronized (mLock) {
2295 if (DEBUG) {
2296 Slog.d(TAG, "onTimeShiftCurrentPositionChanged()");
2297 }
2298 if (mSessionState.session == null || mSessionState.client == null) {
2299 return;
2300 }
2301 try {
2302 mSessionState.client.onTimeShiftCurrentPositionChanged(timeMs,
2303 mSessionState.seq);
2304 } catch (RemoteException e) {
2305 Slog.e(TAG, "error in onTimeShiftCurrentPositionChanged", e);
2306 }
2307 }
2308 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002309 }
2310
2311 private static final class WatchLogHandler extends Handler {
Jae Seo7eb75df2014-08-08 22:20:48 -07002312 // There are only two kinds of watch events that can happen on the system:
2313 // 1. The current TV input session is tuned to a new channel.
2314 // 2. The session is released for some reason.
2315 // The former indicates the end of the previous log entry, if any, followed by the start of
2316 // a new entry. The latter indicates the end of the most recent entry for the given session.
2317 // Here the system supplies the database the smallest set of information only that is
2318 // sufficient to consolidate the log entries while minimizing database operations in the
2319 // system service.
2320 private static final int MSG_LOG_WATCH_START = 1;
2321 private static final int MSG_LOG_WATCH_END = 2;
Jae Seo31dc634be2014-04-15 17:40:23 -07002322
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002323 private final ContentResolver mContentResolver;
2324
2325 public WatchLogHandler(ContentResolver contentResolver, Looper looper) {
Jae Seo31dc634be2014-04-15 17:40:23 -07002326 super(looper);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002327 mContentResolver = contentResolver;
Jae Seo31dc634be2014-04-15 17:40:23 -07002328 }
2329
2330 @Override
2331 public void handleMessage(Message msg) {
2332 switch (msg.what) {
Jae Seo7eb75df2014-08-08 22:20:48 -07002333 case MSG_LOG_WATCH_START: {
Jae Seo31dc634be2014-04-15 17:40:23 -07002334 SomeArgs args = (SomeArgs) msg.obj;
Jae Seo7eb75df2014-08-08 22:20:48 -07002335 String packageName = (String) args.arg1;
2336 long watchStartTime = (long) args.arg2;
2337 long channelId = (long) args.arg3;
2338 Bundle tuneParams = (Bundle) args.arg4;
2339 IBinder sessionToken = (IBinder) args.arg5;
2340
2341 ContentValues values = new ContentValues();
2342 values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
2343 values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
2344 watchStartTime);
2345 values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
2346 if (tuneParams != null) {
2347 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS,
2348 encodeTuneParams(tuneParams));
2349 }
2350 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
2351 sessionToken.toString());
2352
2353 mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
Jae Seo31dc634be2014-04-15 17:40:23 -07002354 args.recycle();
2355 return;
2356 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002357 case MSG_LOG_WATCH_END: {
Jae Seo31dc634be2014-04-15 17:40:23 -07002358 SomeArgs args = (SomeArgs) msg.obj;
Jae Seo7eb75df2014-08-08 22:20:48 -07002359 IBinder sessionToken = (IBinder) args.arg1;
2360 long watchEndTime = (long) args.arg2;
2361
2362 ContentValues values = new ContentValues();
2363 values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
2364 watchEndTime);
2365 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
2366 sessionToken.toString());
2367
2368 mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
Jae Seo31dc634be2014-04-15 17:40:23 -07002369 args.recycle();
2370 return;
2371 }
2372 default: {
Jae Seo6a6059a2014-04-17 21:35:29 -07002373 Slog.w(TAG, "Unhandled message code: " + msg.what);
Jae Seo31dc634be2014-04-15 17:40:23 -07002374 return;
2375 }
2376 }
2377 }
2378
Jae Seo7eb75df2014-08-08 22:20:48 -07002379 private String encodeTuneParams(Bundle tuneParams) {
2380 StringBuilder builder = new StringBuilder();
2381 Set<String> keySet = tuneParams.keySet();
2382 Iterator<String> it = keySet.iterator();
2383 while (it.hasNext()) {
2384 String key = it.next();
2385 Object value = tuneParams.get(key);
2386 if (value == null) {
2387 continue;
Jae Seo579befe2014-08-06 19:18:37 -07002388 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002389 builder.append(replaceEscapeCharacters(key));
2390 builder.append("=");
2391 builder.append(replaceEscapeCharacters(value.toString()));
2392 if (it.hasNext()) {
2393 builder.append(", ");
Jae Seo31dc634be2014-04-15 17:40:23 -07002394 }
2395 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002396 return builder.toString();
Jae Seo31dc634be2014-04-15 17:40:23 -07002397 }
2398
Jae Seo7eb75df2014-08-08 22:20:48 -07002399 private String replaceEscapeCharacters(String src) {
2400 final char ESCAPE_CHARACTER = '%';
2401 final String ENCODING_TARGET_CHARACTERS = "%=,";
2402 StringBuilder builder = new StringBuilder();
2403 for (char ch : src.toCharArray()) {
2404 if (ENCODING_TARGET_CHARACTERS.indexOf(ch) >= 0) {
2405 builder.append(ESCAPE_CHARACTER);
Chulwoo Lee8d4ded02014-07-10 03:56:39 +09002406 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002407 builder.append(ch);
Jae Seo31dc634be2014-04-15 17:40:23 -07002408 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002409 return builder.toString();
Jae Seo579befe2014-08-06 19:18:37 -07002410 }
Jae Seo31dc634be2014-04-15 17:40:23 -07002411 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002412
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002413 private final class HardwareListener implements TvInputHardwareManager.Listener {
Wonsik Kim187423c2014-06-25 14:12:48 +09002414 @Override
2415 public void onStateChanged(String inputId, int state) {
Wonsik Kim969167d2014-06-24 16:33:17 +09002416 synchronized (mLock) {
2417 setStateLocked(inputId, state, mCurrentUserId);
2418 }
2419 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002420
2421 @Override
2422 public void onHardwareDeviceAdded(TvInputHardwareInfo info) {
2423 synchronized (mLock) {
2424 UserState userState = getUserStateLocked(mCurrentUserId);
2425 // Broadcast the event to all hardware inputs.
2426 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002427 if (!serviceState.isHardware || serviceState.service == null) continue;
Wonsik Kim187423c2014-06-25 14:12:48 +09002428 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002429 serviceState.service.notifyHardwareAdded(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09002430 } catch (RemoteException e) {
2431 Slog.e(TAG, "error in notifyHardwareAdded", e);
2432 }
2433 }
2434 }
2435 }
2436
2437 @Override
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002438 public void onHardwareDeviceRemoved(TvInputHardwareInfo info) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002439 synchronized (mLock) {
2440 UserState userState = getUserStateLocked(mCurrentUserId);
2441 // Broadcast the event to all hardware inputs.
2442 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002443 if (!serviceState.isHardware || serviceState.service == null) continue;
Wonsik Kim187423c2014-06-25 14:12:48 +09002444 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002445 serviceState.service.notifyHardwareRemoved(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09002446 } catch (RemoteException e) {
2447 Slog.e(TAG, "error in notifyHardwareRemoved", e);
2448 }
2449 }
2450 }
2451 }
2452
2453 @Override
Jae Seo546c6352014-08-07 11:57:01 -07002454 public void onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002455 synchronized (mLock) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002456 UserState userState = getUserStateLocked(mCurrentUserId);
2457 // Broadcast the event to all hardware inputs.
2458 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002459 if (!serviceState.isHardware || serviceState.service == null) continue;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002460 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002461 serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002462 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002463 Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002464 }
2465 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002466 }
2467 }
2468
2469 @Override
Jae Seo546c6352014-08-07 11:57:01 -07002470 public void onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002471 synchronized (mLock) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002472 UserState userState = getUserStateLocked(mCurrentUserId);
2473 // Broadcast the event to all hardware inputs.
2474 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002475 if (!serviceState.isHardware || serviceState.service == null) continue;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002476 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002477 serviceState.service.notifyHdmiDeviceRemoved(deviceInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002478 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002479 Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002480 }
2481 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002482 }
2483 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09002484
2485 @Override
Wonsik Kime92f8572014-08-12 18:30:24 +09002486 public void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo deviceInfo) {
2487 synchronized (mLock) {
2488 Integer state = null;
2489 switch (deviceInfo.getDevicePowerStatus()) {
2490 case HdmiControlManager.POWER_STATUS_ON:
2491 state = INPUT_STATE_CONNECTED;
2492 break;
2493 case HdmiControlManager.POWER_STATUS_STANDBY:
2494 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON:
2495 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY:
2496 state = INPUT_STATE_CONNECTED_STANDBY;
2497 break;
2498 case HdmiControlManager.POWER_STATUS_UNKNOWN:
2499 default:
2500 state = null;
2501 break;
2502 }
2503 if (state != null) {
2504 setStateLocked(inputId, state.intValue(), mCurrentUserId);
2505 }
2506 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09002507 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002508 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09002509
2510 private static class SessionNotFoundException extends IllegalArgumentException {
2511 public SessionNotFoundException() {
2512 }
2513
2514 public SessionNotFoundException(String name) {
2515 super(name);
2516 }
2517 }
Jae Seo39570912014-02-20 18:23:25 -08002518}