blob: 5ab3fa1e8486477167189fece1acc8d0cb252d55 [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
207 public void onPackageRemoved(String packageName, int uid) {
208 synchronized (mLock) {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900209 UserState userState = getUserStateLocked(getChangingUserId());
Wonsik Kim969167d2014-06-24 16:33:17 +0900210 if (!userState.packageSet.contains(packageName)) {
Jae Seo5c80ad22014-06-12 19:52:58 -0700211 // Not a TV input package.
212 return;
213 }
214 }
215
216 ArrayList<ContentProviderOperation> operations =
217 new ArrayList<ContentProviderOperation>();
218
219 String selection = TvContract.BaseTvColumns.COLUMN_PACKAGE_NAME + "=?";
220 String[] selectionArgs = { packageName };
221
222 operations.add(ContentProviderOperation.newDelete(TvContract.Channels.CONTENT_URI)
223 .withSelection(selection, selectionArgs).build());
224 operations.add(ContentProviderOperation.newDelete(TvContract.Programs.CONTENT_URI)
225 .withSelection(selection, selectionArgs).build());
226 operations.add(ContentProviderOperation
227 .newDelete(TvContract.WatchedPrograms.CONTENT_URI)
228 .withSelection(selection, selectionArgs).build());
229
230 ContentProviderResult[] results = null;
231 try {
232 results = mContentResolver.applyBatch(TvContract.AUTHORITY, operations);
233 } catch (RemoteException | OperationApplicationException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700234 Slog.e(TAG, "error in applyBatch", e);
Jae Seo5c80ad22014-06-12 19:52:58 -0700235 }
236
237 if (DEBUG) {
238 Slog.d(TAG, "onPackageRemoved(packageName=" + packageName + ", uid=" + uid
239 + ")");
240 Slog.d(TAG, "results=" + results);
241 }
242 }
Jae Seo39570912014-02-20 18:23:25 -0800243 };
244 monitor.register(mContext, null, UserHandle.ALL, true);
245
246 IntentFilter intentFilter = new IntentFilter();
247 intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
248 intentFilter.addAction(Intent.ACTION_USER_REMOVED);
249 mContext.registerReceiverAsUser(new BroadcastReceiver() {
250 @Override
251 public void onReceive(Context context, Intent intent) {
252 String action = intent.getAction();
253 if (Intent.ACTION_USER_SWITCHED.equals(action)) {
254 switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
255 } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
256 removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
257 }
258 }
259 }, UserHandle.ALL, intentFilter, null, null);
260 }
261
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900262 private static boolean hasHardwarePermission(PackageManager pm, ComponentName component) {
Wonsik Kim187423c2014-06-25 14:12:48 +0900263 return pm.checkPermission(android.Manifest.permission.TV_INPUT_HARDWARE,
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900264 component.getPackageName()) == PackageManager.PERMISSION_GRANTED;
Wonsik Kim187423c2014-06-25 14:12:48 +0900265 }
266
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900267 private void buildTvInputListLocked(int userId, String[] updatedPackages) {
Jae Seo39570912014-02-20 18:23:25 -0800268 UserState userState = getUserStateLocked(userId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900269 userState.packageSet.clear();
Jae Seo39570912014-02-20 18:23:25 -0800270
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900271 if (DEBUG) Slog.d(TAG, "buildTvInputList");
Jae Seo39570912014-02-20 18:23:25 -0800272 PackageManager pm = mContext.getPackageManager();
273 List<ResolveInfo> services = pm.queryIntentServices(
Chulwoo Leee7bb7d62014-05-27 14:10:37 +0900274 new Intent(TvInputService.SERVICE_INTERFACE),
275 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900276 List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
Jae Seo39570912014-02-20 18:23:25 -0800277 for (ResolveInfo ri : services) {
278 ServiceInfo si = ri.serviceInfo;
279 if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900280 Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
Jae Seo39570912014-02-20 18:23:25 -0800281 + android.Manifest.permission.BIND_TV_INPUT);
282 continue;
283 }
Jae Seo9cc28e52014-08-12 16:45:58 -0700284
285 ComponentName component = new ComponentName(si.packageName, si.name);
286 if (hasHardwarePermission(pm, component)) {
287 ServiceState serviceState = userState.serviceStateMap.get(component);
288 if (serviceState == null) {
289 // We see this hardware TV input service for the first time; we need to
290 // prepare the ServiceState object so that we can connect to the service and
291 // let it add TvInputInfo objects to mInputList if there's any.
292 serviceState = new ServiceState(component, userId);
293 userState.serviceStateMap.put(component, serviceState);
Wonsik Kimf271eac2014-08-30 12:55:10 +0900294 updateServiceConnectionLocked(component, userId);
Wonsik Kim187423c2014-06-25 14:12:48 +0900295 } else {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900296 inputList.addAll(serviceState.inputList);
Jae Seo9cc28e52014-08-12 16:45:58 -0700297 }
298 } else {
299 try {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900300 inputList.add(TvInputInfo.createTvInputInfo(mContext, ri));
Jae Seo9cc28e52014-08-12 16:45:58 -0700301 } catch (XmlPullParserException | IOException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700302 Slog.e(TAG, "failed to load TV input " + si.name, e);
Jae Seo9cc28e52014-08-12 16:45:58 -0700303 continue;
Wonsik Kim969167d2014-06-24 16:33:17 +0900304 }
Chulwoo Leee7bb7d62014-05-27 14:10:37 +0900305 }
Jae Seo9cc28e52014-08-12 16:45:58 -0700306 userState.packageSet.add(si.packageName);
307 }
308
309 Map<String, TvInputState> inputMap = new HashMap<String, TvInputState>();
310 for (TvInputInfo info : inputList) {
Jae Seofea8dd42014-08-26 13:57:41 -0700311 if (DEBUG) {
312 Slog.d(TAG, "add " + info.getId());
313 }
Jae Seo9cc28e52014-08-12 16:45:58 -0700314 TvInputState state = userState.inputMap.get(info.getId());
315 if (state == null) {
316 state = new TvInputState();
317 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900318 state.info = info;
Jae Seo9cc28e52014-08-12 16:45:58 -0700319 inputMap.put(info.getId(), state);
Jae Seo39570912014-02-20 18:23:25 -0800320 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900321
322 for (String inputId : inputMap.keySet()) {
323 if (!userState.inputMap.containsKey(inputId)) {
324 notifyInputAddedLocked(userState, inputId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900325 } else if (updatedPackages != null) {
326 // Notify the package updates
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +0900327 ComponentName component = inputMap.get(inputId).info.getComponent();
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900328 for (String updatedPackage : updatedPackages) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +0900329 if (component.getPackageName().equals(updatedPackage)) {
330 updateServiceConnectionLocked(component, userId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900331 notifyInputUpdatedLocked(userState, inputId);
332 break;
333 }
334 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900335 }
336 }
337
338 for (String inputId : userState.inputMap.keySet()) {
339 if (!inputMap.containsKey(inputId)) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900340 TvInputInfo info = userState.inputMap.get(inputId).info;
Dongwon Kang426c9a42014-08-26 17:39:21 +0900341 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
342 if (serviceState != null) {
343 abortPendingCreateSessionRequestsLocked(serviceState, inputId, userId);
344 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900345 notifyInputRemovedLocked(userState, inputId);
346 }
347 }
348
349 userState.inputMap.clear();
350 userState.inputMap = inputMap;
Jae Seo9c165d62014-08-25 14:39:26 -0700351 }
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900352
Jae Seo9c165d62014-08-25 14:39:26 -0700353 private void buildTvContentRatingSystemListLocked(int userId) {
354 UserState userState = getUserStateLocked(userId);
355 userState.contentRatingSystemList.clear();
356
357 final PackageManager pm = mContext.getPackageManager();
358 Intent intent = new Intent(TvInputManager.ACTION_QUERY_CONTENT_RATING_SYSTEMS);
359 for (ResolveInfo resolveInfo :
360 pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA)) {
361 ActivityInfo receiver = resolveInfo.activityInfo;
362 Bundle metaData = receiver.metaData;
363 if (metaData == null) {
364 continue;
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900365 }
Jae Seo9c165d62014-08-25 14:39:26 -0700366
367 int xmlResId = metaData.getInt(TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS);
368 if (xmlResId == 0) {
369 Slog.w(TAG, "Missing meta-data '"
370 + TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS + "' on receiver "
371 + receiver.packageName + "/" + receiver.name);
372 continue;
373 }
374 userState.contentRatingSystemList.add(
375 TvContentRatingSystemInfo.createTvContentRatingSystemInfo(xmlResId,
376 receiver.applicationInfo));
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900377 }
Jae Seo39570912014-02-20 18:23:25 -0800378 }
379
380 private void switchUser(int userId) {
381 synchronized (mLock) {
382 if (mCurrentUserId == userId) {
383 return;
384 }
385 // final int oldUserId = mCurrentUserId;
386 // TODO: Release services and sessions in the old user state, if needed.
387 mCurrentUserId = userId;
388
389 UserState userState = mUserStates.get(userId);
390 if (userState == null) {
Jae Seo783645e2014-07-28 17:30:50 +0900391 userState = new UserState(mContext, userId);
Jae Seo39570912014-02-20 18:23:25 -0800392 }
393 mUserStates.put(userId, userState);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900394 buildTvInputListLocked(userId, null);
Jae Seo9c165d62014-08-25 14:39:26 -0700395 buildTvContentRatingSystemListLocked(userId);
Jae Seo39570912014-02-20 18:23:25 -0800396 }
397 }
398
399 private void removeUser(int userId) {
400 synchronized (mLock) {
Jae Seob06cb882014-04-09 12:08:17 -0700401 UserState userState = mUserStates.get(userId);
402 if (userState == null) {
403 return;
404 }
Jae Seo39570912014-02-20 18:23:25 -0800405 // Release created sessions.
Jae Seo39570912014-02-20 18:23:25 -0800406 for (SessionState state : userState.sessionStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900407 if (state.session != null) {
Jae Seo39570912014-02-20 18:23:25 -0800408 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900409 state.session.release();
Jae Seo39570912014-02-20 18:23:25 -0800410 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900411 Slog.e(TAG, "error in release", e);
Jae Seo39570912014-02-20 18:23:25 -0800412 }
413 }
414 }
415 userState.sessionStateMap.clear();
416
417 // Unregister all callbacks and unbind all services.
418 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900419 if (serviceState.callback != null) {
Jae Seo39570912014-02-20 18:23:25 -0800420 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900421 serviceState.service.unregisterCallback(serviceState.callback);
Jae Seo39570912014-02-20 18:23:25 -0800422 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900423 Slog.e(TAG, "error in unregisterCallback", e);
Jae Seo39570912014-02-20 18:23:25 -0800424 }
425 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900426 mContext.unbindService(serviceState.connection);
Jae Seo39570912014-02-20 18:23:25 -0800427 }
428 userState.serviceStateMap.clear();
429
Jae Seofea8dd42014-08-26 13:57:41 -0700430 // Clear everything else.
431 userState.inputMap.clear();
432 userState.packageSet.clear();
Jae Seo9c165d62014-08-25 14:39:26 -0700433 userState.contentRatingSystemList.clear();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900434 userState.clientStateMap.clear();
Jae Seofea8dd42014-08-26 13:57:41 -0700435 userState.callbackSet.clear();
436 userState.mainSessionToken = null;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900437
Jae Seo39570912014-02-20 18:23:25 -0800438 mUserStates.remove(userId);
439 }
440 }
441
442 private UserState getUserStateLocked(int userId) {
443 UserState userState = mUserStates.get(userId);
444 if (userState == null) {
445 throw new IllegalStateException("User state not found for user ID " + userId);
446 }
447 return userState;
448 }
449
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900450 private ServiceState getServiceStateLocked(ComponentName component, int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800451 UserState userState = getUserStateLocked(userId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900452 ServiceState serviceState = userState.serviceStateMap.get(component);
Jae Seo39570912014-02-20 18:23:25 -0800453 if (serviceState == null) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900454 throw new IllegalStateException("Service state not found for " + component + " (userId="
Sungsoo Lim7de5e232014-04-12 16:51:27 +0900455 + userId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800456 }
457 return serviceState;
458 }
459
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900460 private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid, int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800461 UserState userState = getUserStateLocked(userId);
462 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
463 if (sessionState == null) {
464 throw new IllegalArgumentException("Session state not found for token " + sessionToken);
465 }
466 // Only the application that requested this session or the system can access it.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900467 if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) {
Jae Seo39570912014-02-20 18:23:25 -0800468 throw new SecurityException("Illegal access to the session with token " + sessionToken
469 + " from uid " + callingUid);
470 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900471 return sessionState;
472 }
473
474 private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900475 return getSessionLocked(getSessionStateLocked(sessionToken, callingUid, userId));
476 }
477
478 private ITvInputSession getSessionLocked(SessionState sessionState) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900479 ITvInputSession session = sessionState.session;
Jae Seo39570912014-02-20 18:23:25 -0800480 if (session == null) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900481 throw new IllegalStateException("Session not yet created for token "
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900482 + sessionState.sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800483 }
484 return session;
485 }
486
487 private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
488 String methodName) {
489 return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
490 false, methodName, null);
491 }
492
Wonsik Kim187423c2014-06-25 14:12:48 +0900493 private static boolean shouldMaintainConnection(ServiceState serviceState) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900494 return !serviceState.sessionTokens.isEmpty() || serviceState.isHardware;
495 // TODO: Find a way to maintain connection to hardware TV input service only when necessary.
Wonsik Kim187423c2014-06-25 14:12:48 +0900496 }
497
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900498 private void updateServiceConnectionLocked(ComponentName component, int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800499 UserState userState = getUserStateLocked(userId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900500 ServiceState serviceState = userState.serviceStateMap.get(component);
Jae Seo39570912014-02-20 18:23:25 -0800501 if (serviceState == null) {
502 return;
503 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900504 if (serviceState.reconnecting) {
505 if (!serviceState.sessionTokens.isEmpty()) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900506 // wait until all the sessions are removed.
507 return;
508 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900509 serviceState.reconnecting = false;
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900510 }
Wonsik Kim187423c2014-06-25 14:12:48 +0900511 boolean maintainConnection = shouldMaintainConnection(serviceState);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900512 if (serviceState.service == null && maintainConnection && userId == mCurrentUserId) {
Jae Seo39570912014-02-20 18:23:25 -0800513 // This means that the service is not yet connected but its state indicates that we
514 // have pending requests. Then, connect the service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900515 if (serviceState.bound) {
Jae Seo39570912014-02-20 18:23:25 -0800516 // We have already bound to the service so we don't try to bind again until after we
517 // unbind later on.
518 return;
519 }
520 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900521 Slog.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800522 }
Sungsoo Limd6672b52014-04-30 10:43:26 +0900523
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900524 Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900525 serviceState.bound = mContext.bindServiceAsUser(
526 i, serviceState.connection, Context.BIND_AUTO_CREATE, new UserHandle(userId));
527 } else if (serviceState.service != null && !maintainConnection) {
Jae Seo39570912014-02-20 18:23:25 -0800528 // This means that the service is already connected but its state indicates that we have
529 // nothing to do with it. Then, disconnect the service.
530 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900531 Slog.d(TAG, "unbindService(service=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -0800532 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900533 mContext.unbindService(serviceState.connection);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900534 userState.serviceStateMap.remove(component);
Jae Seo39570912014-02-20 18:23:25 -0800535 }
536 }
537
Dongwon Kang426c9a42014-08-26 17:39:21 +0900538 private void abortPendingCreateSessionRequestsLocked(ServiceState serviceState,
539 String inputId, int userId) {
540 // Let clients know the create session requests are failed.
541 UserState userState = getUserStateLocked(userId);
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900542 List<SessionState> sessionsToAbort = new ArrayList<>();
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900543 for (IBinder sessionToken : serviceState.sessionTokens) {
Dongwon Kang426c9a42014-08-26 17:39:21 +0900544 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900545 if (sessionState.session == null && (inputId == null
546 || sessionState.info.getId().equals(inputId))) {
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900547 sessionsToAbort.add(sessionState);
Dongwon Kang426c9a42014-08-26 17:39:21 +0900548 }
549 }
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900550 for (SessionState sessionState : sessionsToAbort) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900551 removeSessionStateLocked(sessionState.sessionToken, sessionState.userId);
552 sendSessionTokenToClientLocked(sessionState.client,
553 sessionState.info.getId(), null, null, sessionState.seq);
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900554 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900555 updateServiceConnectionLocked(serviceState.component, userId);
Dongwon Kang426c9a42014-08-26 17:39:21 +0900556 }
557
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900558 private void createSessionInternalLocked(ITvInputService service, IBinder sessionToken,
559 int userId) {
560 UserState userState = getUserStateLocked(userId);
561 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800562 if (DEBUG) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900563 Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.info.getId() + ")");
Jae Seo39570912014-02-20 18:23:25 -0800564 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900565 InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
Jae Seo6a6059a2014-04-17 21:35:29 -0700566
Jae Seo39570912014-02-20 18:23:25 -0800567 // Set up a callback to send the session token.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900568 ITvInputSessionCallback callback = new SessionCallback(sessionState, channels);
Jae Seo39570912014-02-20 18:23:25 -0800569
570 // Create a session. When failed, send a null token immediately.
571 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900572 service.createSession(channels[1], callback, sessionState.info.getId());
Jae Seo39570912014-02-20 18:23:25 -0800573 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900574 Slog.e(TAG, "error in createSession", e);
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900575 removeSessionStateLocked(sessionToken, userId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900576 sendSessionTokenToClientLocked(sessionState.client, sessionState.info.getId(), null,
577 null, sessionState.seq);
Jae Seo39570912014-02-20 18:23:25 -0800578 }
Jae Seo6a6059a2014-04-17 21:35:29 -0700579 channels[1].dispose();
Jae Seo39570912014-02-20 18:23:25 -0800580 }
581
Sungsoo Limd6672b52014-04-30 10:43:26 +0900582 private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId,
Jae Seo5c80ad22014-06-12 19:52:58 -0700583 IBinder sessionToken, InputChannel channel, int seq) {
Jae Seo39570912014-02-20 18:23:25 -0800584 try {
Sungsoo Limd6672b52014-04-30 10:43:26 +0900585 client.onSessionCreated(inputId, sessionToken, channel, seq);
Jae Seofea8dd42014-08-26 13:57:41 -0700586 } catch (RemoteException e) {
587 Slog.e(TAG, "error in onSessionCreated", e);
Jae Seo39570912014-02-20 18:23:25 -0800588 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900589 }
Jae Seo39570912014-02-20 18:23:25 -0800590
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900591 private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
592 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900593 if (sessionState.session != null) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900594 UserState userState = getUserStateLocked(userId);
595 if (sessionToken == userState.mainSessionToken) {
596 setMainLocked(sessionToken, false, callingUid, userId);
597 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900598 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900599 sessionState.session.release();
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900600 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700601 Slog.e(TAG, "session process has already died", e);
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900602 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900603 sessionState.session = null;
Jae Seo39570912014-02-20 18:23:25 -0800604 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900605 removeSessionStateLocked(sessionToken, userId);
Jae Seo39570912014-02-20 18:23:25 -0800606 }
607
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900608 private void removeSessionStateLocked(IBinder sessionToken, int userId) {
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900609 UserState userState = getUserStateLocked(userId);
Ji-Hwan Leeabca0ee2014-07-24 17:34:19 +0900610 if (sessionToken == userState.mainSessionToken) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900611 if (DEBUG) {
612 Slog.d(TAG, "mainSessionToken=null");
613 }
Ji-Hwan Leeabca0ee2014-07-24 17:34:19 +0900614 userState.mainSessionToken = null;
615 }
616
617 // Remove the session state from the global session state map of the current user.
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900618 SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
619
Chulwoo Lee8d4ded02014-07-10 03:56:39 +0900620 if (sessionState == null) {
621 return;
622 }
623
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900624 // Also remove the session token from the session token list of the current client and
625 // service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900626 ClientState clientState = userState.clientStateMap.get(sessionState.client.asBinder());
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900627 if (clientState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900628 clientState.sessionTokens.remove(sessionToken);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900629 if (clientState.isEmpty()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900630 userState.clientStateMap.remove(sessionState.client.asBinder());
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900631 }
632 }
633
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900634 TvInputInfo info = sessionState.info;
Wonsik Kim187423c2014-06-25 14:12:48 +0900635 if (info != null) {
636 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
637 if (serviceState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900638 serviceState.sessionTokens.remove(sessionToken);
Wonsik Kim187423c2014-06-25 14:12:48 +0900639 }
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900640 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900641 updateServiceConnectionLocked(sessionState.info.getComponent(), userId);
Jae Seo7eb75df2014-08-08 22:20:48 -0700642
643 // Log the end of watch.
644 SomeArgs args = SomeArgs.obtain();
645 args.arg1 = sessionToken;
646 args.arg2 = System.currentTimeMillis();
647 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_END, args).sendToTarget();
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900648 }
649
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900650 private void setMainLocked(IBinder sessionToken, boolean isMain, int callingUid, int userId) {
651 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900652 if (sessionState.hardwareSessionToken != null) {
653 sessionState = getSessionStateLocked(sessionState.hardwareSessionToken,
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900654 Process.SYSTEM_UID, userId);
655 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900656 ServiceState serviceState = getServiceStateLocked(sessionState.info.getComponent(), userId);
657 if (!serviceState.isHardware) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900658 return;
659 }
660 ITvInputSession session = getSessionLocked(sessionState);
661 try {
662 session.setMain(isMain);
663 } catch (RemoteException e) {
664 Slog.e(TAG, "error in setMain", e);
665 }
666 }
667
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900668 private void notifyInputAddedLocked(UserState userState, String inputId) {
669 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700670 Slog.d(TAG, "notifyInputAddedLocked(inputId=" + inputId + ")");
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900671 }
672 for (ITvInputManagerCallback callback : userState.callbackSet) {
673 try {
674 callback.onInputAdded(inputId);
675 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700676 Slog.e(TAG, "failed to report added input to callback", e);
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900677 }
678 }
679 }
680
681 private void notifyInputRemovedLocked(UserState userState, String inputId) {
682 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700683 Slog.d(TAG, "notifyInputRemovedLocked(inputId=" + inputId + ")");
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900684 }
685 for (ITvInputManagerCallback callback : userState.callbackSet) {
686 try {
687 callback.onInputRemoved(inputId);
688 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700689 Slog.e(TAG, "failed to report removed input to callback", e);
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900690 }
691 }
692 }
693
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900694 private void notifyInputUpdatedLocked(UserState userState, String inputId) {
695 if (DEBUG) {
696 Slog.d(TAG, "notifyInputUpdatedLocked(inputId=" + inputId + ")");
697 }
698 for (ITvInputManagerCallback callback : userState.callbackSet) {
699 try {
700 callback.onInputUpdated(inputId);
701 } catch (RemoteException e) {
702 Slog.e(TAG, "failed to report updated input to callback", e);
703 }
704 }
705 }
706
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900707 private void notifyInputStateChangedLocked(UserState userState, String inputId,
Wonsik Kim969167d2014-06-24 16:33:17 +0900708 int state, ITvInputManagerCallback targetCallback) {
709 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700710 Slog.d(TAG, "notifyInputStateChangedLocked(inputId=" + inputId
711 + ", state=" + state + ")");
Wonsik Kim969167d2014-06-24 16:33:17 +0900712 }
713 if (targetCallback == null) {
714 for (ITvInputManagerCallback callback : userState.callbackSet) {
715 try {
716 callback.onInputStateChanged(inputId, state);
717 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700718 Slog.e(TAG, "failed to report state change to callback", e);
Wonsik Kim969167d2014-06-24 16:33:17 +0900719 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900720 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900721 } else {
722 try {
723 targetCallback.onInputStateChanged(inputId, state);
724 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700725 Slog.e(TAG, "failed to report state change to callback", e);
Wonsik Kim969167d2014-06-24 16:33:17 +0900726 }
727 }
728 }
729
730 private void setStateLocked(String inputId, int state, int userId) {
731 UserState userState = getUserStateLocked(userId);
732 TvInputState inputState = userState.inputMap.get(inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900733 ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent());
734 int oldState = inputState.state;
735 inputState.state = state;
736 if (serviceState != null && serviceState.service == null
Wonsik Kim187423c2014-06-25 14:12:48 +0900737 && shouldMaintainConnection(serviceState)) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900738 // We don't notify state change while reconnecting. It should remain disconnected.
739 return;
740 }
741 if (oldState != state) {
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900742 notifyInputStateChangedLocked(userState, inputId, state, null);
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900743 }
744 }
745
Jae Seo39570912014-02-20 18:23:25 -0800746 private final class BinderService extends ITvInputManager.Stub {
747 @Override
748 public List<TvInputInfo> getTvInputList(int userId) {
749 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
750 Binder.getCallingUid(), userId, "getTvInputList");
751 final long identity = Binder.clearCallingIdentity();
752 try {
753 synchronized (mLock) {
754 UserState userState = getUserStateLocked(resolvedUserId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900755 List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
756 for (TvInputState state : userState.inputMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900757 inputList.add(state.info);
Jae Seo39570912014-02-20 18:23:25 -0800758 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900759 return inputList;
Jae Seo39570912014-02-20 18:23:25 -0800760 }
761 } finally {
762 Binder.restoreCallingIdentity(identity);
763 }
Jae Seo39570912014-02-20 18:23:25 -0800764 }
765
766 @Override
Jae Seob3758052014-07-12 19:25:24 -0700767 public TvInputInfo getTvInputInfo(String inputId, int userId) {
768 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
769 Binder.getCallingUid(), userId, "getTvInputInfo");
770 final long identity = Binder.clearCallingIdentity();
771 try {
772 synchronized (mLock) {
773 UserState userState = getUserStateLocked(resolvedUserId);
774 TvInputState state = userState.inputMap.get(inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900775 return state == null ? null : state.info;
Jae Seob3758052014-07-12 19:25:24 -0700776 }
777 } finally {
778 Binder.restoreCallingIdentity(identity);
779 }
780 }
781
782 @Override
Dongwon Kang993f81e2014-11-27 19:34:18 +0900783 public int getTvInputState(String inputId, int userId) {
784 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
785 Binder.getCallingUid(), userId, "getTvInputState");
786 final long identity = Binder.clearCallingIdentity();
787 try {
788 synchronized (mLock) {
789 UserState userState = getUserStateLocked(resolvedUserId);
790 TvInputState state = userState.inputMap.get(inputId);
791 return state == null ? INPUT_STATE_UNKNOWN : state.state;
792 }
793 } finally {
794 Binder.restoreCallingIdentity(identity);
795 }
796 }
797
798 @Override
Jae Seo9c165d62014-08-25 14:39:26 -0700799 public List<TvContentRatingSystemInfo> getTvContentRatingSystemList(int userId) {
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900800 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
Jae Seo9c165d62014-08-25 14:39:26 -0700801 Binder.getCallingUid(), userId, "getTvContentRatingSystemList");
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900802 final long identity = Binder.clearCallingIdentity();
803 try {
804 synchronized (mLock) {
805 UserState userState = getUserStateLocked(resolvedUserId);
Jae Seo9c165d62014-08-25 14:39:26 -0700806 return userState.contentRatingSystemList;
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900807 }
808 } finally {
809 Binder.restoreCallingIdentity(identity);
810 }
811 }
812
813 @Override
Wonsik Kim969167d2014-06-24 16:33:17 +0900814 public void registerCallback(final ITvInputManagerCallback callback, int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800815 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
816 Binder.getCallingUid(), userId, "registerCallback");
817 final long identity = Binder.clearCallingIdentity();
818 try {
819 synchronized (mLock) {
Jae Seofea8dd42014-08-26 13:57:41 -0700820 final UserState userState = getUserStateLocked(resolvedUserId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900821 userState.callbackSet.add(callback);
Jae Seofea8dd42014-08-26 13:57:41 -0700822 try {
823 callback.asBinder().linkToDeath(new IBinder.DeathRecipient() {
824 @Override
825 public void binderDied() {
826 synchronized (mLock) {
827 if (userState.callbackSet != null) {
828 userState.callbackSet.remove(callback);
829 }
830 }
831 }
832 }, 0);
833 } catch (RemoteException e) {
834 Slog.e(TAG, "client process has already died", e);
835 }
Jae Seo39570912014-02-20 18:23:25 -0800836 }
837 } finally {
838 Binder.restoreCallingIdentity(identity);
839 }
840 }
841
842 @Override
Wonsik Kim969167d2014-06-24 16:33:17 +0900843 public void unregisterCallback(ITvInputManagerCallback callback, int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800844 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
845 Binder.getCallingUid(), userId, "unregisterCallback");
846 final long identity = Binder.clearCallingIdentity();
847 try {
848 synchronized (mLock) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900849 UserState userState = getUserStateLocked(resolvedUserId);
850 userState.callbackSet.remove(callback);
Jae Seo39570912014-02-20 18:23:25 -0800851 }
852 } finally {
853 Binder.restoreCallingIdentity(identity);
854 }
855 }
856
857 @Override
Jae Seo783645e2014-07-28 17:30:50 +0900858 public boolean isParentalControlsEnabled(int userId) {
859 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
860 Binder.getCallingUid(), userId, "isParentalControlsEnabled");
861 final long identity = Binder.clearCallingIdentity();
862 try {
863 synchronized (mLock) {
864 UserState userState = getUserStateLocked(resolvedUserId);
865 return userState.persistentDataStore.isParentalControlsEnabled();
866 }
867 } finally {
868 Binder.restoreCallingIdentity(identity);
869 }
870 }
871
872 @Override
873 public void setParentalControlsEnabled(boolean enabled, int userId) {
874 ensureParentalControlsPermission();
875 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
876 Binder.getCallingUid(), userId, "setParentalControlsEnabled");
877 final long identity = Binder.clearCallingIdentity();
878 try {
879 synchronized (mLock) {
880 UserState userState = getUserStateLocked(resolvedUserId);
881 userState.persistentDataStore.setParentalControlsEnabled(enabled);
882 }
883 } finally {
884 Binder.restoreCallingIdentity(identity);
885 }
886 }
887
888 @Override
889 public boolean isRatingBlocked(String rating, int userId) {
890 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
891 Binder.getCallingUid(), userId, "isRatingBlocked");
892 final long identity = Binder.clearCallingIdentity();
893 try {
894 synchronized (mLock) {
895 UserState userState = getUserStateLocked(resolvedUserId);
896 return userState.persistentDataStore.isRatingBlocked(
897 TvContentRating.unflattenFromString(rating));
898 }
899 } finally {
900 Binder.restoreCallingIdentity(identity);
901 }
902 }
903
904 @Override
905 public List<String> getBlockedRatings(int userId) {
906 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
907 Binder.getCallingUid(), userId, "getBlockedRatings");
908 final long identity = Binder.clearCallingIdentity();
909 try {
910 synchronized (mLock) {
911 UserState userState = getUserStateLocked(resolvedUserId);
912 List<String> ratings = new ArrayList<String>();
913 for (TvContentRating rating
914 : userState.persistentDataStore.getBlockedRatings()) {
915 ratings.add(rating.flattenToString());
916 }
917 return ratings;
918 }
919 } finally {
920 Binder.restoreCallingIdentity(identity);
921 }
922 }
923
924 @Override
925 public void addBlockedRating(String rating, int userId) {
926 ensureParentalControlsPermission();
927 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
928 Binder.getCallingUid(), userId, "addBlockedRating");
929 final long identity = Binder.clearCallingIdentity();
930 try {
931 synchronized (mLock) {
932 UserState userState = getUserStateLocked(resolvedUserId);
933 userState.persistentDataStore.addBlockedRating(
934 TvContentRating.unflattenFromString(rating));
935 }
936 } finally {
937 Binder.restoreCallingIdentity(identity);
938 }
939 }
940
941 @Override
942 public void removeBlockedRating(String rating, int userId) {
943 ensureParentalControlsPermission();
944 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
945 Binder.getCallingUid(), userId, "removeBlockedRating");
946 final long identity = Binder.clearCallingIdentity();
947 try {
948 synchronized (mLock) {
949 UserState userState = getUserStateLocked(resolvedUserId);
950 userState.persistentDataStore.removeBlockedRating(
951 TvContentRating.unflattenFromString(rating));
952 }
953 } finally {
954 Binder.restoreCallingIdentity(identity);
955 }
956 }
957
958 private void ensureParentalControlsPermission() {
Jae Seofc836f62014-08-27 00:47:56 +0000959 if (mContext.checkCallingPermission(
960 android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
961 != PackageManager.PERMISSION_GRANTED) {
962 throw new SecurityException(
963 "The caller does not have parental controls permission");
964 }
Jae Seo783645e2014-07-28 17:30:50 +0900965 }
966
967 @Override
Sungsoo Limd6672b52014-04-30 10:43:26 +0900968 public void createSession(final ITvInputClient client, final String inputId,
Jae Seo39570912014-02-20 18:23:25 -0800969 int seq, int userId) {
970 final int callingUid = Binder.getCallingUid();
971 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
972 userId, "createSession");
973 final long identity = Binder.clearCallingIdentity();
974 try {
975 synchronized (mLock) {
Jae Seo39570912014-02-20 18:23:25 -0800976 UserState userState = getUserStateLocked(resolvedUserId);
Dongwon Kang426c9a42014-08-26 17:39:21 +0900977 TvInputState inputState = userState.inputMap.get(inputId);
978 if (inputState == null) {
979 Slog.w(TAG, "Failed to find input state for inputId=" + inputId);
980 sendSessionTokenToClientLocked(client, inputId, null, null, seq);
981 return;
982 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900983 TvInputInfo info = inputState.info;
Wonsik Kim187423c2014-06-25 14:12:48 +0900984 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
Jae Seo39570912014-02-20 18:23:25 -0800985 if (serviceState == null) {
Wonsik Kim187423c2014-06-25 14:12:48 +0900986 serviceState = new ServiceState(info.getComponent(), resolvedUserId);
987 userState.serviceStateMap.put(info.getComponent(), serviceState);
Jae Seo39570912014-02-20 18:23:25 -0800988 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900989 // Send a null token immediately while reconnecting.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900990 if (serviceState.reconnecting == true) {
Jae Seo5c80ad22014-06-12 19:52:58 -0700991 sendSessionTokenToClientLocked(client, inputId, null, null, seq);
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900992 return;
993 }
994
995 // Create a new session token and a session state.
996 IBinder sessionToken = new Binder();
Wonsik Kim187423c2014-06-25 14:12:48 +0900997 SessionState sessionState = new SessionState(sessionToken, info, client,
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900998 seq, callingUid, resolvedUserId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900999
1000 // Add them to the global session state map of the current user.
1001 userState.sessionStateMap.put(sessionToken, sessionState);
1002
1003 // Also, add them to the session state map of the current service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001004 serviceState.sessionTokens.add(sessionToken);
Jae Seo39570912014-02-20 18:23:25 -08001005
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001006 if (serviceState.service != null) {
1007 createSessionInternalLocked(serviceState.service, sessionToken,
Sungsoo Lim7de5e232014-04-12 16:51:27 +09001008 resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001009 } else {
Wonsik Kim187423c2014-06-25 14:12:48 +09001010 updateServiceConnectionLocked(info.getComponent(), resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001011 }
1012 }
1013 } finally {
1014 Binder.restoreCallingIdentity(identity);
1015 }
1016 }
1017
1018 @Override
1019 public void releaseSession(IBinder sessionToken, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001020 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -07001021 Slog.d(TAG, "releaseSession(sessionToken=" + sessionToken + ")");
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001022 }
Jae Seo39570912014-02-20 18:23:25 -08001023 final int callingUid = Binder.getCallingUid();
1024 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1025 userId, "releaseSession");
1026 final long identity = Binder.clearCallingIdentity();
1027 try {
1028 synchronized (mLock) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001029 releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001030 }
1031 } finally {
1032 Binder.restoreCallingIdentity(identity);
1033 }
1034 }
1035
1036 @Override
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001037 public void setMainSession(IBinder sessionToken, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001038 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -07001039 Slog.d(TAG, "setMainSession(sessionToken=" + sessionToken + ")");
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001040 }
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001041 final int callingUid = Binder.getCallingUid();
1042 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1043 userId, "setMainSession");
1044 final long identity = Binder.clearCallingIdentity();
1045 try {
1046 synchronized (mLock) {
Ji-Hwan Lee982abe62014-07-24 17:11:03 +09001047 UserState userState = getUserStateLocked(resolvedUserId);
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001048 if (userState.mainSessionToken == sessionToken) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001049 return;
1050 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001051 if (DEBUG) {
1052 Slog.d(TAG, "mainSessionToken=" + sessionToken);
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001053 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001054 IBinder oldMainSessionToken = userState.mainSessionToken;
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001055 userState.mainSessionToken = sessionToken;
1056
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001057 // Inform the new main session first.
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001058 // See {@link TvInputService.Session#onSetMain}.
1059 if (sessionToken != null) {
1060 setMainLocked(sessionToken, true, callingUid, userId);
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001061 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001062 if (oldMainSessionToken != null) {
1063 setMainLocked(oldMainSessionToken, false, Process.SYSTEM_UID, userId);
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001064 }
1065 }
1066 } finally {
1067 Binder.restoreCallingIdentity(identity);
1068 }
1069 }
1070
1071 @Override
Jae Seo39570912014-02-20 18:23:25 -08001072 public void setSurface(IBinder sessionToken, Surface surface, int userId) {
1073 final int callingUid = Binder.getCallingUid();
1074 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1075 userId, "setSurface");
1076 final long identity = Binder.clearCallingIdentity();
1077 try {
1078 synchronized (mLock) {
1079 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001080 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1081 resolvedUserId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001082 if (sessionState.hardwareSessionToken == null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001083 getSessionLocked(sessionState).setSurface(surface);
1084 } else {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001085 getSessionLocked(sessionState.hardwareSessionToken,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001086 Process.SYSTEM_UID, resolvedUserId).setSurface(surface);
1087 }
Jae Seo39570912014-02-20 18:23:25 -08001088 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001089 Slog.e(TAG, "error in setSurface", e);
Jae Seo39570912014-02-20 18:23:25 -08001090 }
1091 }
1092 } finally {
Youngsang Chof8362062014-04-30 17:24:20 +09001093 if (surface != null) {
1094 // surface is not used in TvInputManagerService.
1095 surface.release();
1096 }
Jae Seo39570912014-02-20 18:23:25 -08001097 Binder.restoreCallingIdentity(identity);
1098 }
1099 }
1100
1101 @Override
Youngsang Choe821d712014-07-16 14:22:19 -07001102 public void dispatchSurfaceChanged(IBinder sessionToken, int format, int width,
1103 int height, int userId) {
1104 final int callingUid = Binder.getCallingUid();
1105 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1106 userId, "dispatchSurfaceChanged");
1107 final long identity = Binder.clearCallingIdentity();
1108 try {
1109 synchronized (mLock) {
1110 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001111 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1112 resolvedUserId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001113 getSessionLocked(sessionState).dispatchSurfaceChanged(format, width,
1114 height);
1115 if (sessionState.hardwareSessionToken != null) {
1116 getSessionLocked(sessionState.hardwareSessionToken, Process.SYSTEM_UID,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001117 resolvedUserId).dispatchSurfaceChanged(format, width, height);
1118 }
Youngsang Choe821d712014-07-16 14:22:19 -07001119 } catch (RemoteException e) {
1120 Slog.e(TAG, "error in dispatchSurfaceChanged", e);
1121 }
1122 }
1123 } finally {
1124 Binder.restoreCallingIdentity(identity);
1125 }
1126 }
1127
1128 @Override
Jae Seo39570912014-02-20 18:23:25 -08001129 public void setVolume(IBinder sessionToken, float volume, int userId) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001130 final float REMOTE_VOLUME_ON = 1.0f;
1131 final float REMOTE_VOLUME_OFF = 0f;
Jae Seo39570912014-02-20 18:23:25 -08001132 final int callingUid = Binder.getCallingUid();
1133 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1134 userId, "setVolume");
1135 final long identity = Binder.clearCallingIdentity();
1136 try {
1137 synchronized (mLock) {
1138 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001139 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1140 resolvedUserId);
1141 getSessionLocked(sessionState).setVolume(volume);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001142 if (sessionState.hardwareSessionToken != null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001143 // Here, we let the hardware session know only whether volume is on or
1144 // off to prevent that the volume is controlled in the both side.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001145 getSessionLocked(sessionState.hardwareSessionToken,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001146 Process.SYSTEM_UID, resolvedUserId).setVolume((volume > 0.0f)
1147 ? REMOTE_VOLUME_ON : REMOTE_VOLUME_OFF);
1148 }
Jae Seo39570912014-02-20 18:23:25 -08001149 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001150 Slog.e(TAG, "error in setVolume", e);
Jae Seo39570912014-02-20 18:23:25 -08001151 }
1152 }
1153 } finally {
1154 Binder.restoreCallingIdentity(identity);
1155 }
1156 }
1157
1158 @Override
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +09001159 public void tune(IBinder sessionToken, final Uri channelUri, Bundle params, int userId) {
Jae Seo39570912014-02-20 18:23:25 -08001160 final int callingUid = Binder.getCallingUid();
1161 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1162 userId, "tune");
1163 final long identity = Binder.clearCallingIdentity();
1164 try {
1165 synchronized (mLock) {
Jae Seo39570912014-02-20 18:23:25 -08001166 try {
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +09001167 getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(
1168 channelUri, params);
Jae Seoc22d0c02014-08-15 13:03:21 -07001169 if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
Youngsang Cho008f6d42014-07-22 21:29:47 -07001170 // Do not log the watch history for passthrough inputs.
1171 return;
1172 }
Jae Seo31dc634be2014-04-15 17:40:23 -07001173
Jae Seo31dc634be2014-04-15 17:40:23 -07001174 UserState userState = getUserStateLocked(resolvedUserId);
1175 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Jae Seo31dc634be2014-04-15 17:40:23 -07001176
Jae Seo7eb75df2014-08-08 22:20:48 -07001177 // Log the start of watch.
Jae Seo31dc634be2014-04-15 17:40:23 -07001178 SomeArgs args = SomeArgs.obtain();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001179 args.arg1 = sessionState.info.getComponent().getPackageName();
Jae Seo7eb75df2014-08-08 22:20:48 -07001180 args.arg2 = System.currentTimeMillis();
1181 args.arg3 = ContentUris.parseId(channelUri);
1182 args.arg4 = params;
1183 args.arg5 = sessionToken;
1184 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_START, args)
1185 .sendToTarget();
Jae Seo39570912014-02-20 18:23:25 -08001186 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001187 Slog.e(TAG, "error in tune", e);
Jae Seo39570912014-02-20 18:23:25 -08001188 return;
1189 }
1190 }
1191 } finally {
1192 Binder.restoreCallingIdentity(identity);
1193 }
1194 }
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001195
1196 @Override
Sungsoo Lim9bf671f2014-07-19 12:59:51 +09001197 public void requestUnblockContent(
1198 IBinder sessionToken, String unblockedRating, int userId) {
Jaewan Kim903d6b72014-07-16 11:28:56 +09001199 final int callingUid = Binder.getCallingUid();
1200 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1201 userId, "unblockContent");
1202 final long identity = Binder.clearCallingIdentity();
1203 try {
1204 synchronized (mLock) {
1205 try {
1206 getSessionLocked(sessionToken, callingUid, resolvedUserId)
Sungsoo Lim9bf671f2014-07-19 12:59:51 +09001207 .requestUnblockContent(unblockedRating);
Jaewan Kim903d6b72014-07-16 11:28:56 +09001208 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -07001209 Slog.e(TAG, "error in requestUnblockContent", e);
Jaewan Kim903d6b72014-07-16 11:28:56 +09001210 }
1211 }
1212 } finally {
1213 Binder.restoreCallingIdentity(identity);
1214 }
1215 }
1216
1217 @Override
Jae Seo2c1c31c2014-07-10 14:57:01 -07001218 public void setCaptionEnabled(IBinder sessionToken, boolean enabled, int userId) {
1219 final int callingUid = Binder.getCallingUid();
1220 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1221 userId, "setCaptionEnabled");
1222 final long identity = Binder.clearCallingIdentity();
1223 try {
1224 synchronized (mLock) {
1225 try {
1226 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1227 .setCaptionEnabled(enabled);
1228 } catch (RemoteException e) {
1229 Slog.e(TAG, "error in setCaptionEnabled", e);
1230 }
1231 }
1232 } finally {
1233 Binder.restoreCallingIdentity(identity);
1234 }
1235 }
1236
1237 @Override
Jae Seo10d285a2014-07-31 22:46:47 +09001238 public void selectTrack(IBinder sessionToken, int type, String trackId, int userId) {
Dongwon Kang1f213912014-07-02 18:35:08 +09001239 final int callingUid = Binder.getCallingUid();
1240 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1241 userId, "selectTrack");
1242 final long identity = Binder.clearCallingIdentity();
1243 try {
1244 synchronized (mLock) {
1245 try {
1246 getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack(
Jae Seo10d285a2014-07-31 22:46:47 +09001247 type, trackId);
Dongwon Kang1f213912014-07-02 18:35:08 +09001248 } catch (RemoteException e) {
1249 Slog.e(TAG, "error in selectTrack", e);
1250 }
1251 }
1252 } finally {
1253 Binder.restoreCallingIdentity(identity);
1254 }
1255 }
1256
1257 @Override
Jae Seoa759b112014-07-18 22:16:08 -07001258 public void sendAppPrivateCommand(IBinder sessionToken, String command, Bundle data,
1259 int userId) {
1260 final int callingUid = Binder.getCallingUid();
1261 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1262 userId, "sendAppPrivateCommand");
1263 final long identity = Binder.clearCallingIdentity();
1264 try {
1265 synchronized (mLock) {
1266 try {
1267 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1268 .appPrivateCommand(command, data);
1269 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -07001270 Slog.e(TAG, "error in appPrivateCommand", e);
Jae Seoa759b112014-07-18 22:16:08 -07001271 }
1272 }
1273 } finally {
1274 Binder.restoreCallingIdentity(identity);
1275 }
1276 }
1277
1278 @Override
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001279 public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
1280 int userId) {
1281 final int callingUid = Binder.getCallingUid();
1282 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1283 userId, "createOverlayView");
1284 final long identity = Binder.clearCallingIdentity();
1285 try {
1286 synchronized (mLock) {
1287 try {
1288 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1289 .createOverlayView(windowToken, frame);
1290 } catch (RemoteException e) {
1291 Slog.e(TAG, "error in createOverlayView", e);
1292 }
1293 }
1294 } finally {
1295 Binder.restoreCallingIdentity(identity);
1296 }
1297 }
1298
1299 @Override
1300 public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
1301 final int callingUid = Binder.getCallingUid();
1302 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1303 userId, "relayoutOverlayView");
1304 final long identity = Binder.clearCallingIdentity();
1305 try {
1306 synchronized (mLock) {
1307 try {
1308 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1309 .relayoutOverlayView(frame);
1310 } catch (RemoteException e) {
1311 Slog.e(TAG, "error in relayoutOverlayView", e);
1312 }
1313 }
1314 } finally {
1315 Binder.restoreCallingIdentity(identity);
1316 }
1317 }
1318
1319 @Override
1320 public void removeOverlayView(IBinder sessionToken, int userId) {
1321 final int callingUid = Binder.getCallingUid();
1322 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1323 userId, "removeOverlayView");
1324 final long identity = Binder.clearCallingIdentity();
1325 try {
1326 synchronized (mLock) {
1327 try {
1328 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1329 .removeOverlayView();
1330 } catch (RemoteException e) {
1331 Slog.e(TAG, "error in removeOverlayView", e);
1332 }
1333 }
1334 } finally {
1335 Binder.restoreCallingIdentity(identity);
1336 }
1337 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001338
1339 @Override
1340 public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
Wonsik Kim969167d2014-06-24 16:33:17 +09001341 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001342 != PackageManager.PERMISSION_GRANTED) {
1343 return null;
1344 }
1345
1346 final long identity = Binder.clearCallingIdentity();
1347 try {
1348 return mTvInputHardwareManager.getHardwareList();
1349 } finally {
1350 Binder.restoreCallingIdentity(identity);
1351 }
1352 }
1353
1354 @Override
1355 public ITvInputHardware acquireTvInputHardware(int deviceId,
Wonsik Kim969167d2014-06-24 16:33:17 +09001356 ITvInputHardwareCallback callback, TvInputInfo info, int userId)
1357 throws RemoteException {
1358 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001359 != PackageManager.PERMISSION_GRANTED) {
1360 return null;
1361 }
1362
1363 final long identity = Binder.clearCallingIdentity();
1364 final int callingUid = Binder.getCallingUid();
1365 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1366 userId, "acquireTvInputHardware");
1367 try {
1368 return mTvInputHardwareManager.acquireHardware(
Wonsik Kim969167d2014-06-24 16:33:17 +09001369 deviceId, callback, info, callingUid, resolvedUserId);
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001370 } finally {
1371 Binder.restoreCallingIdentity(identity);
1372 }
1373 }
1374
1375 @Override
1376 public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
1377 throws RemoteException {
Wonsik Kim969167d2014-06-24 16:33:17 +09001378 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001379 != PackageManager.PERMISSION_GRANTED) {
1380 return;
1381 }
1382
1383 final long identity = Binder.clearCallingIdentity();
1384 final int callingUid = Binder.getCallingUid();
1385 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1386 userId, "releaseTvInputHardware");
1387 try {
1388 mTvInputHardwareManager.releaseHardware(
1389 deviceId, hardware, callingUid, resolvedUserId);
1390 } finally {
1391 Binder.restoreCallingIdentity(identity);
1392 }
1393 }
Jaewan Kime14c3f42014-06-27 13:47:48 +09001394
1395 @Override
Terry Heoc086a3d2014-06-18 14:26:44 +09001396 public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int userId)
1397 throws RemoteException {
1398 if (mContext.checkCallingPermission(
1399 android.Manifest.permission.CAPTURE_TV_INPUT)
1400 != PackageManager.PERMISSION_GRANTED) {
1401 throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1402 }
1403
1404 final long identity = Binder.clearCallingIdentity();
1405 final int callingUid = Binder.getCallingUid();
1406 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1407 userId, "getAvailableTvStreamConfigList");
1408 try {
1409 return mTvInputHardwareManager.getAvailableTvStreamConfigList(
1410 inputId, callingUid, resolvedUserId);
1411 } finally {
1412 Binder.restoreCallingIdentity(identity);
1413 }
1414 }
1415
1416 @Override
1417 public boolean captureFrame(String inputId, Surface surface, TvStreamConfig config,
1418 int userId)
1419 throws RemoteException {
1420 if (mContext.checkCallingPermission(
1421 android.Manifest.permission.CAPTURE_TV_INPUT)
1422 != PackageManager.PERMISSION_GRANTED) {
1423 throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1424 }
1425
1426 final long identity = Binder.clearCallingIdentity();
1427 final int callingUid = Binder.getCallingUid();
1428 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1429 userId, "captureFrame");
1430 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001431 String hardwareInputId = null;
Terry Heo79124a72014-07-21 15:17:17 +09001432 synchronized (mLock) {
1433 UserState userState = getUserStateLocked(resolvedUserId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001434 if (userState.inputMap.get(inputId) == null) {
Jae Seofea8dd42014-08-26 13:57:41 -07001435 Slog.e(TAG, "input not found for " + inputId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001436 return false;
1437 }
1438 for (SessionState sessionState : userState.sessionStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001439 if (sessionState.info.getId().equals(inputId)
1440 && sessionState.hardwareSessionToken != null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001441 hardwareInputId = userState.sessionStateMap.get(
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001442 sessionState.hardwareSessionToken).info.getId();
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001443 break;
1444 }
1445 }
Terry Heo79124a72014-07-21 15:17:17 +09001446 }
Terry Heoc086a3d2014-06-18 14:26:44 +09001447 return mTvInputHardwareManager.captureFrame(
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001448 (hardwareInputId != null) ? hardwareInputId : inputId,
Terry Heo79124a72014-07-21 15:17:17 +09001449 surface, config, callingUid, resolvedUserId);
Terry Heoc086a3d2014-06-18 14:26:44 +09001450 } finally {
1451 Binder.restoreCallingIdentity(identity);
1452 }
1453 }
1454
1455 @Override
Terry Heodf9f0a32014-08-06 13:53:33 +09001456 public boolean isSingleSessionActive(int userId) throws RemoteException {
1457 final long identity = Binder.clearCallingIdentity();
1458 final int callingUid = Binder.getCallingUid();
1459 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1460 userId, "isSingleSessionActive");
1461 try {
1462 synchronized (mLock) {
1463 UserState userState = getUserStateLocked(resolvedUserId);
1464 if (userState.sessionStateMap.size() == 1) {
1465 return true;
1466 }
1467 else if (userState.sessionStateMap.size() == 2) {
1468 SessionState[] sessionStates = userState.sessionStateMap.values().toArray(
1469 new SessionState[0]);
1470 // Check if there is a wrapper input.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001471 if (sessionStates[0].hardwareSessionToken != null
1472 || sessionStates[1].hardwareSessionToken != null) {
Terry Heodf9f0a32014-08-06 13:53:33 +09001473 return true;
1474 }
1475 }
1476 return false;
1477 }
1478 } finally {
1479 Binder.restoreCallingIdentity(identity);
1480 }
1481 }
1482
1483 @Override
Jae Seo0f8fc342014-07-02 10:47:08 -07001484 @SuppressWarnings("resource")
Jaewan Kime14c3f42014-06-27 13:47:48 +09001485 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1486 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
Jae Seo0f8fc342014-07-02 10:47:08 -07001487 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
Jaewan Kime14c3f42014-06-27 13:47:48 +09001488 != PackageManager.PERMISSION_GRANTED) {
Jae Seo0f8fc342014-07-02 10:47:08 -07001489 pw.println("Permission Denial: can't dump TvInputManager from pid="
1490 + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
Jaewan Kime14c3f42014-06-27 13:47:48 +09001491 return;
1492 }
1493
1494 synchronized (mLock) {
1495 pw.println("User Ids (Current user: " + mCurrentUserId + "):");
1496 pw.increaseIndent();
1497 for (int i = 0; i < mUserStates.size(); i++) {
1498 int userId = mUserStates.keyAt(i);
1499 pw.println(Integer.valueOf(userId));
1500 }
1501 pw.decreaseIndent();
1502
1503 for (int i = 0; i < mUserStates.size(); i++) {
1504 int userId = mUserStates.keyAt(i);
1505 UserState userState = getUserStateLocked(userId);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001506 pw.println("UserState (" + userId + "):");
1507 pw.increaseIndent();
1508
Wonsik Kim969167d2014-06-24 16:33:17 +09001509 pw.println("inputMap: inputId -> TvInputState");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001510 pw.increaseIndent();
Jaewan Kim8e6b51b2014-07-15 13:01:57 +09001511 for (Map.Entry<String, TvInputState> entry: userState.inputMap.entrySet()) {
1512 pw.println(entry.getKey() + ": " + entry.getValue());
Jaewan Kime14c3f42014-06-27 13:47:48 +09001513 }
1514 pw.decreaseIndent();
1515
Wonsik Kim969167d2014-06-24 16:33:17 +09001516 pw.println("packageSet:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001517 pw.increaseIndent();
Wonsik Kim969167d2014-06-24 16:33:17 +09001518 for (String packageName : userState.packageSet) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09001519 pw.println(packageName);
1520 }
1521 pw.decreaseIndent();
1522
1523 pw.println("clientStateMap: ITvInputClient -> ClientState");
1524 pw.increaseIndent();
1525 for (Map.Entry<IBinder, ClientState> entry :
1526 userState.clientStateMap.entrySet()) {
1527 ClientState client = entry.getValue();
1528 pw.println(entry.getKey() + ": " + client);
1529
1530 pw.increaseIndent();
1531
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001532 pw.println("sessionTokens:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001533 pw.increaseIndent();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001534 for (IBinder token : client.sessionTokens) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09001535 pw.println("" + token);
1536 }
1537 pw.decreaseIndent();
1538
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001539 pw.println("clientTokens: " + client.clientToken);
1540 pw.println("userId: " + client.userId);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001541
1542 pw.decreaseIndent();
1543 }
1544 pw.decreaseIndent();
1545
Wonsik Kim187423c2014-06-25 14:12:48 +09001546 pw.println("serviceStateMap: ComponentName -> ServiceState");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001547 pw.increaseIndent();
Wonsik Kim187423c2014-06-25 14:12:48 +09001548 for (Map.Entry<ComponentName, ServiceState> entry :
Jaewan Kime14c3f42014-06-27 13:47:48 +09001549 userState.serviceStateMap.entrySet()) {
1550 ServiceState service = entry.getValue();
1551 pw.println(entry.getKey() + ": " + service);
1552
1553 pw.increaseIndent();
1554
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001555 pw.println("sessionTokens:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001556 pw.increaseIndent();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001557 for (IBinder token : service.sessionTokens) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09001558 pw.println("" + token);
1559 }
1560 pw.decreaseIndent();
1561
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001562 pw.println("service: " + service.service);
1563 pw.println("callback: " + service.callback);
1564 pw.println("bound: " + service.bound);
1565 pw.println("reconnecting: " + service.reconnecting);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001566
1567 pw.decreaseIndent();
1568 }
1569 pw.decreaseIndent();
1570
1571 pw.println("sessionStateMap: ITvInputSession -> SessionState");
1572 pw.increaseIndent();
1573 for (Map.Entry<IBinder, SessionState> entry :
1574 userState.sessionStateMap.entrySet()) {
1575 SessionState session = entry.getValue();
1576 pw.println(entry.getKey() + ": " + session);
1577
1578 pw.increaseIndent();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001579 pw.println("info: " + session.info);
1580 pw.println("client: " + session.client);
1581 pw.println("seq: " + session.seq);
1582 pw.println("callingUid: " + session.callingUid);
1583 pw.println("userId: " + session.userId);
1584 pw.println("sessionToken: " + session.sessionToken);
1585 pw.println("session: " + session.session);
1586 pw.println("logUri: " + session.logUri);
1587 pw.println("hardwareSessionToken: " + session.hardwareSessionToken);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001588 pw.decreaseIndent();
1589 }
1590 pw.decreaseIndent();
1591
Wonsik Kim969167d2014-06-24 16:33:17 +09001592 pw.println("callbackSet:");
1593 pw.increaseIndent();
1594 for (ITvInputManagerCallback callback : userState.callbackSet) {
1595 pw.println(callback.toString());
1596 }
1597 pw.decreaseIndent();
1598
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001599 pw.println("mainSessionToken: " + userState.mainSessionToken);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001600 pw.decreaseIndent();
1601 }
1602 }
1603 }
Jae Seo39570912014-02-20 18:23:25 -08001604 }
1605
Wonsik Kim969167d2014-06-24 16:33:17 +09001606 private static final class UserState {
1607 // A mapping from the TV input id to its TvInputState.
1608 private Map<String, TvInputState> inputMap = new HashMap<String, TvInputState>();
1609
1610 // A set of all TV input packages.
1611 private final Set<String> packageSet = new HashSet<String>();
Jae Seo5c80ad22014-06-12 19:52:58 -07001612
Jae Seo9c165d62014-08-25 14:39:26 -07001613 // A list of all TV content rating systems defined.
1614 private final List<TvContentRatingSystemInfo>
1615 contentRatingSystemList = new ArrayList<TvContentRatingSystemInfo>();
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +09001616
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001617 // A mapping from the token of a client to its state.
1618 private final Map<IBinder, ClientState> clientStateMap =
1619 new HashMap<IBinder, ClientState>();
1620
Jae Seo39570912014-02-20 18:23:25 -08001621 // A mapping from the name of a TV input service to its state.
Wonsik Kim187423c2014-06-25 14:12:48 +09001622 private final Map<ComponentName, ServiceState> serviceStateMap =
1623 new HashMap<ComponentName, ServiceState>();
Jae Seo39570912014-02-20 18:23:25 -08001624
1625 // A mapping from the token of a TV input session to its state.
1626 private final Map<IBinder, SessionState> sessionStateMap =
1627 new HashMap<IBinder, SessionState>();
Wonsik Kim969167d2014-06-24 16:33:17 +09001628
1629 // A set of callbacks.
1630 private final Set<ITvInputManagerCallback> callbackSet =
1631 new HashSet<ITvInputManagerCallback>();
Terry Heo79124a72014-07-21 15:17:17 +09001632
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001633 // The token of a "main" TV input session.
1634 private IBinder mainSessionToken = null;
Jae Seo783645e2014-07-28 17:30:50 +09001635
1636 // Persistent data store for all internal settings maintained by the TV input manager
1637 // service.
1638 private final PersistentDataStore persistentDataStore;
1639
1640 private UserState(Context context, int userId) {
1641 persistentDataStore = new PersistentDataStore(context, userId);
1642 }
Jae Seo39570912014-02-20 18:23:25 -08001643 }
1644
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001645 private final class ClientState implements IBinder.DeathRecipient {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001646 private final List<IBinder> sessionTokens = new ArrayList<IBinder>();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001647
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001648 private IBinder clientToken;
1649 private final int userId;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001650
1651 ClientState(IBinder clientToken, int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001652 this.clientToken = clientToken;
1653 this.userId = userId;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001654 }
1655
1656 public boolean isEmpty() {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001657 return sessionTokens.isEmpty();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001658 }
1659
1660 @Override
1661 public void binderDied() {
1662 synchronized (mLock) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001663 UserState userState = getUserStateLocked(userId);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001664 // DO NOT remove the client state of clientStateMap in this method. It will be
Ji-Hwan Leea65118e2014-07-24 16:30:02 +09001665 // removed in releaseSessionLocked().
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001666 ClientState clientState = userState.clientStateMap.get(clientToken);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001667 if (clientState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001668 while (clientState.sessionTokens.size() > 0) {
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001669 releaseSessionLocked(
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001670 clientState.sessionTokens.get(0), Process.SYSTEM_UID, userId);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001671 }
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001672 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001673 clientToken = null;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001674 }
1675 }
1676 }
1677
Jae Seo39570912014-02-20 18:23:25 -08001678 private final class ServiceState {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001679 private final List<IBinder> sessionTokens = new ArrayList<IBinder>();
1680 private final ServiceConnection connection;
1681 private final ComponentName component;
1682 private final boolean isHardware;
1683 private final List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
Jae Seo39570912014-02-20 18:23:25 -08001684
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001685 private ITvInputService service;
1686 private ServiceCallback callback;
1687 private boolean bound;
1688 private boolean reconnecting;
Jae Seo39570912014-02-20 18:23:25 -08001689
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001690 private ServiceState(ComponentName component, int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001691 this.component = component;
1692 this.connection = new InputServiceConnection(component, userId);
1693 this.isHardware = hasHardwarePermission(mContext.getPackageManager(), component);
1694 }
1695 }
1696
1697 private static final class TvInputState {
1698 // A TvInputInfo object which represents the TV input.
1699 private TvInputInfo info;
1700
1701 // The state of TV input. Connected by default.
1702 private int state = INPUT_STATE_CONNECTED;
1703
1704 @Override
1705 public String toString() {
1706 return "info: " + info + "; state: " + state;
Jae Seo39570912014-02-20 18:23:25 -08001707 }
1708 }
1709
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001710 private final class SessionState implements IBinder.DeathRecipient {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001711 private final TvInputInfo info;
1712 private final ITvInputClient client;
1713 private final int seq;
1714 private final int callingUid;
1715 private final int userId;
1716 private final IBinder sessionToken;
1717 private ITvInputSession session;
1718 private Uri logUri;
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001719 // Not null if this session represents an external device connected to a hardware TV input.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001720 private IBinder hardwareSessionToken;
Jae Seo39570912014-02-20 18:23:25 -08001721
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001722 private SessionState(IBinder sessionToken, TvInputInfo info, ITvInputClient client,
1723 int seq, int callingUid, int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001724 this.sessionToken = sessionToken;
1725 this.info = info;
1726 this.client = client;
1727 this.seq = seq;
1728 this.callingUid = callingUid;
1729 this.userId = userId;
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001730 }
1731
1732 @Override
1733 public void binderDied() {
1734 synchronized (mLock) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001735 session = null;
1736 if (client != null) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001737 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001738 client.onSessionReleased(seq);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001739 } catch(RemoteException e) {
1740 Slog.e(TAG, "error in onSessionReleased", e);
1741 }
1742 }
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001743 // If there are any other sessions based on this session, they should be released.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001744 UserState userState = getUserStateLocked(userId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001745 for (SessionState sessionState : userState.sessionStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001746 if (sessionToken == sessionState.hardwareSessionToken) {
1747 releaseSessionLocked(sessionState.sessionToken, Process.SYSTEM_UID,
1748 userId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001749 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001750 sessionState.client.onSessionReleased(sessionState.seq);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001751 } catch (RemoteException e) {
1752 Slog.e(TAG, "error in onSessionReleased", e);
1753 }
1754 }
1755 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001756 removeSessionStateLocked(sessionToken, userId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001757 }
Jae Seo39570912014-02-20 18:23:25 -08001758 }
1759 }
1760
1761 private final class InputServiceConnection implements ServiceConnection {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001762 private final ComponentName mComponent;
Jae Seo39570912014-02-20 18:23:25 -08001763 private final int mUserId;
1764
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001765 private InputServiceConnection(ComponentName component, int userId) {
1766 mComponent = component;
Jae Seo39570912014-02-20 18:23:25 -08001767 mUserId = userId;
1768 }
1769
1770 @Override
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001771 public void onServiceConnected(ComponentName component, IBinder service) {
Jae Seo39570912014-02-20 18:23:25 -08001772 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001773 Slog.d(TAG, "onServiceConnected(component=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -08001774 }
1775 synchronized (mLock) {
Wonsik Kim969167d2014-06-24 16:33:17 +09001776 UserState userState = getUserStateLocked(mUserId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001777 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001778 serviceState.service = ITvInputService.Stub.asInterface(service);
Jae Seo39570912014-02-20 18:23:25 -08001779
1780 // Register a callback, if we need to.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001781 if (serviceState.isHardware && serviceState.callback == null) {
1782 serviceState.callback = new ServiceCallback(mComponent, mUserId);
Jae Seo39570912014-02-20 18:23:25 -08001783 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001784 serviceState.service.registerCallback(serviceState.callback);
Jae Seo39570912014-02-20 18:23:25 -08001785 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001786 Slog.e(TAG, "error in registerCallback", e);
Jae Seo39570912014-02-20 18:23:25 -08001787 }
1788 }
1789
1790 // And create sessions, if any.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001791 for (IBinder sessionToken : serviceState.sessionTokens) {
1792 createSessionInternalLocked(serviceState.service, sessionToken, mUserId);
Jae Seo39570912014-02-20 18:23:25 -08001793 }
Wonsik Kim969167d2014-06-24 16:33:17 +09001794
Wonsik Kim187423c2014-06-25 14:12:48 +09001795 for (TvInputState inputState : userState.inputMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001796 if (inputState.info.getComponent().equals(component)
1797 && inputState.state != INPUT_STATE_DISCONNECTED) {
1798 notifyInputStateChangedLocked(userState, inputState.info.getId(),
1799 inputState.state, null);
Wonsik Kim187423c2014-06-25 14:12:48 +09001800 }
1801 }
1802
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001803 if (serviceState.isHardware) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001804 List<TvInputHardwareInfo> hardwareInfoList =
1805 mTvInputHardwareManager.getHardwareList();
Wonsik Kim187423c2014-06-25 14:12:48 +09001806 for (TvInputHardwareInfo hardwareInfo : hardwareInfoList) {
1807 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001808 serviceState.service.notifyHardwareAdded(hardwareInfo);
Wonsik Kim187423c2014-06-25 14:12:48 +09001809 } catch (RemoteException e) {
1810 Slog.e(TAG, "error in notifyHardwareAdded", e);
1811 }
1812 }
1813
Jae Seo546c6352014-08-07 11:57:01 -07001814 List<HdmiDeviceInfo> deviceInfoList =
1815 mTvInputHardwareManager.getHdmiDeviceList();
1816 for (HdmiDeviceInfo deviceInfo : deviceInfoList) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001817 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001818 serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001819 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07001820 Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001821 }
1822 }
Wonsik Kim969167d2014-06-24 16:33:17 +09001823 }
Jae Seo39570912014-02-20 18:23:25 -08001824 }
1825 }
1826
1827 @Override
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001828 public void onServiceDisconnected(ComponentName component) {
Jae Seo39570912014-02-20 18:23:25 -08001829 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001830 Slog.d(TAG, "onServiceDisconnected(component=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -08001831 }
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001832 if (!mComponent.equals(component)) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001833 throw new IllegalArgumentException("Mismatched ComponentName: "
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001834 + mComponent + " (expected), " + component + " (actual).");
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001835 }
1836 synchronized (mLock) {
1837 UserState userState = getUserStateLocked(mUserId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001838 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001839 if (serviceState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001840 serviceState.reconnecting = true;
1841 serviceState.bound = false;
1842 serviceState.service = null;
1843 serviceState.callback = null;
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001844
Dongwon Kang426c9a42014-08-26 17:39:21 +09001845 abortPendingCreateSessionRequestsLocked(serviceState, null, mUserId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001846
Wonsik Kim187423c2014-06-25 14:12:48 +09001847 for (TvInputState inputState : userState.inputMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001848 if (inputState.info.getComponent().equals(component)) {
1849 notifyInputStateChangedLocked(userState, inputState.info.getId(),
Wonsik Kim187423c2014-06-25 14:12:48 +09001850 INPUT_STATE_DISCONNECTED, null);
1851 }
1852 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001853 }
1854 }
Jae Seo39570912014-02-20 18:23:25 -08001855 }
1856 }
1857
1858 private final class ServiceCallback extends ITvInputServiceCallback.Stub {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001859 private final ComponentName mComponent;
Jae Seo39570912014-02-20 18:23:25 -08001860 private final int mUserId;
1861
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001862 ServiceCallback(ComponentName component, int userId) {
1863 mComponent = component;
Jae Seo39570912014-02-20 18:23:25 -08001864 mUserId = userId;
1865 }
1866
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001867 private void ensureHardwarePermission() {
1868 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1869 != PackageManager.PERMISSION_GRANTED) {
1870 throw new SecurityException("The caller does not have hardware permission");
1871 }
1872 }
Wonsik Kim187423c2014-06-25 14:12:48 +09001873
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001874 private void ensureValidInput(TvInputInfo inputInfo) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001875 if (inputInfo.getId() == null || !mComponent.equals(inputInfo.getComponent())) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001876 throw new IllegalArgumentException("Invalid TvInputInfo");
1877 }
1878 }
1879
1880 private void addTvInputLocked(TvInputInfo inputInfo) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001881 ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001882 serviceState.inputList.add(inputInfo);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +09001883 buildTvInputListLocked(mUserId, null);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001884 }
1885
1886 @Override
1887 public void addHardwareTvInput(int deviceId, TvInputInfo inputInfo) {
1888 ensureHardwarePermission();
1889 ensureValidInput(inputInfo);
1890 synchronized (mLock) {
1891 mTvInputHardwareManager.addHardwareTvInput(deviceId, inputInfo);
1892 addTvInputLocked(inputInfo);
1893 }
1894 }
1895
1896 @Override
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001897 public void addHdmiTvInput(int id, TvInputInfo inputInfo) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001898 ensureHardwarePermission();
1899 ensureValidInput(inputInfo);
1900 synchronized (mLock) {
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001901 mTvInputHardwareManager.addHdmiTvInput(id, inputInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001902 addTvInputLocked(inputInfo);
Wonsik Kim187423c2014-06-25 14:12:48 +09001903 }
1904 }
1905
1906 @Override
1907 public void removeTvInput(String inputId) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001908 ensureHardwarePermission();
Wonsik Kim187423c2014-06-25 14:12:48 +09001909 synchronized (mLock) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09001910 ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
Wonsik Kim187423c2014-06-25 14:12:48 +09001911 boolean removed = false;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001912 for (Iterator<TvInputInfo> it = serviceState.inputList.iterator();
Wonsik Kim187423c2014-06-25 14:12:48 +09001913 it.hasNext(); ) {
1914 if (it.next().getId().equals(inputId)) {
1915 it.remove();
1916 removed = true;
1917 break;
1918 }
1919 }
1920 if (removed) {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +09001921 buildTvInputListLocked(mUserId, null);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001922 mTvInputHardwareManager.removeTvInput(inputId);
Wonsik Kim187423c2014-06-25 14:12:48 +09001923 } else {
Jae Seofea8dd42014-08-26 13:57:41 -07001924 Slog.e(TAG, "failed to remove input " + inputId);
Wonsik Kim187423c2014-06-25 14:12:48 +09001925 }
Jae Seo39570912014-02-20 18:23:25 -08001926 }
1927 }
1928 }
Jae Seo31dc634be2014-04-15 17:40:23 -07001929
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001930 private final class SessionCallback extends ITvInputSessionCallback.Stub {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09001931 private final SessionState mSessionState;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001932 private final InputChannel[] mChannels;
1933
1934 SessionCallback(SessionState sessionState, InputChannel[] channels) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09001935 mSessionState = sessionState;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001936 mChannels = channels;
1937 }
1938
1939 @Override
1940 public void onSessionCreated(ITvInputSession session, IBinder harewareSessionToken) {
1941 if (DEBUG) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09001942 Slog.d(TAG, "onSessionCreated(inputId=" + mSessionState.info.getId() + ")");
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001943 }
1944 synchronized (mLock) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09001945 mSessionState.session = session;
1946 mSessionState.hardwareSessionToken = harewareSessionToken;
1947 if (session != null && addSessionTokenToClientStateLocked(session)) {
1948 sendSessionTokenToClientLocked(mSessionState.client,
1949 mSessionState.info.getId(), mSessionState.sessionToken, mChannels[0],
1950 mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001951 } else {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09001952 removeSessionStateLocked(mSessionState.sessionToken, mSessionState.userId);
1953 sendSessionTokenToClientLocked(mSessionState.client,
1954 mSessionState.info.getId(), null, null, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001955 }
1956 mChannels[0].dispose();
1957 }
1958 }
1959
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09001960 private boolean addSessionTokenToClientStateLocked(ITvInputSession session) {
1961 try {
1962 session.asBinder().linkToDeath(mSessionState, 0);
1963 } catch (RemoteException e) {
1964 Slog.e(TAG, "session process has already died", e);
1965 return false;
1966 }
1967
1968 IBinder clientToken = mSessionState.client.asBinder();
1969 UserState userState = getUserStateLocked(mSessionState.userId);
1970 ClientState clientState = userState.clientStateMap.get(clientToken);
1971 if (clientState == null) {
1972 clientState = new ClientState(clientToken, mSessionState.userId);
1973 try {
1974 clientToken.linkToDeath(clientState, 0);
1975 } catch (RemoteException e) {
1976 Slog.e(TAG, "client process has already died", e);
1977 return false;
1978 }
1979 userState.clientStateMap.put(clientToken, clientState);
1980 }
1981 clientState.sessionTokens.add(mSessionState.sessionToken);
1982 return true;
1983 }
1984
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001985 @Override
1986 public void onChannelRetuned(Uri channelUri) {
1987 synchronized (mLock) {
1988 if (DEBUG) {
1989 Slog.d(TAG, "onChannelRetuned(" + channelUri + ")");
1990 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09001991 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001992 return;
1993 }
1994 try {
1995 // TODO: Consider adding this channel change in the watch log. When we do
1996 // that, how we can protect the watch log from malicious tv inputs should
1997 // be addressed. e.g. add a field which represents where the channel change
1998 // originated from.
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09001999 mSessionState.client.onChannelRetuned(channelUri, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002000 } catch (RemoteException e) {
2001 Slog.e(TAG, "error in onChannelRetuned", e);
2002 }
2003 }
2004 }
2005
2006 @Override
2007 public void onTracksChanged(List<TvTrackInfo> tracks) {
2008 synchronized (mLock) {
2009 if (DEBUG) {
2010 Slog.d(TAG, "onTracksChanged(" + tracks + ")");
2011 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002012 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002013 return;
2014 }
2015 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002016 mSessionState.client.onTracksChanged(tracks, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002017 } catch (RemoteException e) {
2018 Slog.e(TAG, "error in onTracksChanged", e);
2019 }
2020 }
2021 }
2022
2023 @Override
2024 public void onTrackSelected(int type, String trackId) {
2025 synchronized (mLock) {
2026 if (DEBUG) {
2027 Slog.d(TAG, "onTrackSelected(type=" + type + ", trackId=" + trackId + ")");
2028 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002029 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002030 return;
2031 }
2032 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002033 mSessionState.client.onTrackSelected(type, trackId, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002034 } catch (RemoteException e) {
2035 Slog.e(TAG, "error in onTrackSelected", e);
2036 }
2037 }
2038 }
2039
2040 @Override
2041 public void onVideoAvailable() {
2042 synchronized (mLock) {
2043 if (DEBUG) {
2044 Slog.d(TAG, "onVideoAvailable()");
2045 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002046 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002047 return;
2048 }
2049 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002050 mSessionState.client.onVideoAvailable(mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002051 } catch (RemoteException e) {
2052 Slog.e(TAG, "error in onVideoAvailable", e);
2053 }
2054 }
2055 }
2056
2057 @Override
2058 public void onVideoUnavailable(int reason) {
2059 synchronized (mLock) {
2060 if (DEBUG) {
2061 Slog.d(TAG, "onVideoUnavailable(" + reason + ")");
2062 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002063 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002064 return;
2065 }
2066 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002067 mSessionState.client.onVideoUnavailable(reason, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002068 } catch (RemoteException e) {
2069 Slog.e(TAG, "error in onVideoUnavailable", e);
2070 }
2071 }
2072 }
2073
2074 @Override
2075 public void onContentAllowed() {
2076 synchronized (mLock) {
2077 if (DEBUG) {
2078 Slog.d(TAG, "onContentAllowed()");
2079 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002080 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002081 return;
2082 }
2083 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002084 mSessionState.client.onContentAllowed(mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002085 } catch (RemoteException e) {
2086 Slog.e(TAG, "error in onContentAllowed", e);
2087 }
2088 }
2089 }
2090
2091 @Override
2092 public void onContentBlocked(String rating) {
2093 synchronized (mLock) {
2094 if (DEBUG) {
2095 Slog.d(TAG, "onContentBlocked()");
2096 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002097 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002098 return;
2099 }
2100 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002101 mSessionState.client.onContentBlocked(rating, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002102 } catch (RemoteException e) {
2103 Slog.e(TAG, "error in onContentBlocked", e);
2104 }
2105 }
2106 }
2107
2108 @Override
2109 public void onLayoutSurface(int left, int top, int right, int bottom) {
2110 synchronized (mLock) {
2111 if (DEBUG) {
2112 Slog.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top
2113 + ", right=" + right + ", bottom=" + bottom + ",)");
2114 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002115 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002116 return;
2117 }
2118 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002119 mSessionState.client.onLayoutSurface(left, top, right, bottom,
2120 mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002121 } catch (RemoteException e) {
2122 Slog.e(TAG, "error in onLayoutSurface", e);
2123 }
2124 }
2125 }
2126
2127 @Override
2128 public void onSessionEvent(String eventType, Bundle eventArgs) {
2129 synchronized (mLock) {
2130 if (DEBUG) {
2131 Slog.d(TAG, "onEvent(what=" + eventType + ", data=" + eventArgs + ")");
2132 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002133 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002134 return;
2135 }
2136 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002137 mSessionState.client.onSessionEvent(eventType, eventArgs, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002138 } catch (RemoteException e) {
2139 Slog.e(TAG, "error in onSessionEvent", e);
2140 }
2141 }
2142 }
2143 }
2144
2145 private static final class WatchLogHandler extends Handler {
Jae Seo7eb75df2014-08-08 22:20:48 -07002146 // There are only two kinds of watch events that can happen on the system:
2147 // 1. The current TV input session is tuned to a new channel.
2148 // 2. The session is released for some reason.
2149 // The former indicates the end of the previous log entry, if any, followed by the start of
2150 // a new entry. The latter indicates the end of the most recent entry for the given session.
2151 // Here the system supplies the database the smallest set of information only that is
2152 // sufficient to consolidate the log entries while minimizing database operations in the
2153 // system service.
2154 private static final int MSG_LOG_WATCH_START = 1;
2155 private static final int MSG_LOG_WATCH_END = 2;
Jae Seo31dc634be2014-04-15 17:40:23 -07002156
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002157 private final ContentResolver mContentResolver;
2158
2159 public WatchLogHandler(ContentResolver contentResolver, Looper looper) {
Jae Seo31dc634be2014-04-15 17:40:23 -07002160 super(looper);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002161 mContentResolver = contentResolver;
Jae Seo31dc634be2014-04-15 17:40:23 -07002162 }
2163
2164 @Override
2165 public void handleMessage(Message msg) {
2166 switch (msg.what) {
Jae Seo7eb75df2014-08-08 22:20:48 -07002167 case MSG_LOG_WATCH_START: {
Jae Seo31dc634be2014-04-15 17:40:23 -07002168 SomeArgs args = (SomeArgs) msg.obj;
Jae Seo7eb75df2014-08-08 22:20:48 -07002169 String packageName = (String) args.arg1;
2170 long watchStartTime = (long) args.arg2;
2171 long channelId = (long) args.arg3;
2172 Bundle tuneParams = (Bundle) args.arg4;
2173 IBinder sessionToken = (IBinder) args.arg5;
2174
2175 ContentValues values = new ContentValues();
2176 values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
2177 values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
2178 watchStartTime);
2179 values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
2180 if (tuneParams != null) {
2181 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS,
2182 encodeTuneParams(tuneParams));
2183 }
2184 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
2185 sessionToken.toString());
2186
2187 mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
Jae Seo31dc634be2014-04-15 17:40:23 -07002188 args.recycle();
2189 return;
2190 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002191 case MSG_LOG_WATCH_END: {
Jae Seo31dc634be2014-04-15 17:40:23 -07002192 SomeArgs args = (SomeArgs) msg.obj;
Jae Seo7eb75df2014-08-08 22:20:48 -07002193 IBinder sessionToken = (IBinder) args.arg1;
2194 long watchEndTime = (long) args.arg2;
2195
2196 ContentValues values = new ContentValues();
2197 values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
2198 watchEndTime);
2199 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
2200 sessionToken.toString());
2201
2202 mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
Jae Seo31dc634be2014-04-15 17:40:23 -07002203 args.recycle();
2204 return;
2205 }
2206 default: {
Jae Seo6a6059a2014-04-17 21:35:29 -07002207 Slog.w(TAG, "Unhandled message code: " + msg.what);
Jae Seo31dc634be2014-04-15 17:40:23 -07002208 return;
2209 }
2210 }
2211 }
2212
Jae Seo7eb75df2014-08-08 22:20:48 -07002213 private String encodeTuneParams(Bundle tuneParams) {
2214 StringBuilder builder = new StringBuilder();
2215 Set<String> keySet = tuneParams.keySet();
2216 Iterator<String> it = keySet.iterator();
2217 while (it.hasNext()) {
2218 String key = it.next();
2219 Object value = tuneParams.get(key);
2220 if (value == null) {
2221 continue;
Jae Seo579befe2014-08-06 19:18:37 -07002222 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002223 builder.append(replaceEscapeCharacters(key));
2224 builder.append("=");
2225 builder.append(replaceEscapeCharacters(value.toString()));
2226 if (it.hasNext()) {
2227 builder.append(", ");
Jae Seo31dc634be2014-04-15 17:40:23 -07002228 }
2229 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002230 return builder.toString();
Jae Seo31dc634be2014-04-15 17:40:23 -07002231 }
2232
Jae Seo7eb75df2014-08-08 22:20:48 -07002233 private String replaceEscapeCharacters(String src) {
2234 final char ESCAPE_CHARACTER = '%';
2235 final String ENCODING_TARGET_CHARACTERS = "%=,";
2236 StringBuilder builder = new StringBuilder();
2237 for (char ch : src.toCharArray()) {
2238 if (ENCODING_TARGET_CHARACTERS.indexOf(ch) >= 0) {
2239 builder.append(ESCAPE_CHARACTER);
Chulwoo Lee8d4ded02014-07-10 03:56:39 +09002240 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002241 builder.append(ch);
Jae Seo31dc634be2014-04-15 17:40:23 -07002242 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002243 return builder.toString();
Jae Seo579befe2014-08-06 19:18:37 -07002244 }
Jae Seo31dc634be2014-04-15 17:40:23 -07002245 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002246
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002247 private final class HardwareListener implements TvInputHardwareManager.Listener {
Wonsik Kim187423c2014-06-25 14:12:48 +09002248 @Override
2249 public void onStateChanged(String inputId, int state) {
Wonsik Kim969167d2014-06-24 16:33:17 +09002250 synchronized (mLock) {
2251 setStateLocked(inputId, state, mCurrentUserId);
2252 }
2253 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002254
2255 @Override
2256 public void onHardwareDeviceAdded(TvInputHardwareInfo info) {
2257 synchronized (mLock) {
2258 UserState userState = getUserStateLocked(mCurrentUserId);
2259 // Broadcast the event to all hardware inputs.
2260 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002261 if (!serviceState.isHardware || serviceState.service == null) continue;
Wonsik Kim187423c2014-06-25 14:12:48 +09002262 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002263 serviceState.service.notifyHardwareAdded(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09002264 } catch (RemoteException e) {
2265 Slog.e(TAG, "error in notifyHardwareAdded", e);
2266 }
2267 }
2268 }
2269 }
2270
2271 @Override
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002272 public void onHardwareDeviceRemoved(TvInputHardwareInfo info) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002273 synchronized (mLock) {
2274 UserState userState = getUserStateLocked(mCurrentUserId);
2275 // Broadcast the event to all hardware inputs.
2276 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002277 if (!serviceState.isHardware || serviceState.service == null) continue;
Wonsik Kim187423c2014-06-25 14:12:48 +09002278 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002279 serviceState.service.notifyHardwareRemoved(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09002280 } catch (RemoteException e) {
2281 Slog.e(TAG, "error in notifyHardwareRemoved", e);
2282 }
2283 }
2284 }
2285 }
2286
2287 @Override
Jae Seo546c6352014-08-07 11:57:01 -07002288 public void onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002289 synchronized (mLock) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002290 UserState userState = getUserStateLocked(mCurrentUserId);
2291 // Broadcast the event to all hardware inputs.
2292 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002293 if (!serviceState.isHardware || serviceState.service == null) continue;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002294 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002295 serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002296 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002297 Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002298 }
2299 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002300 }
2301 }
2302
2303 @Override
Jae Seo546c6352014-08-07 11:57:01 -07002304 public void onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002305 synchronized (mLock) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002306 UserState userState = getUserStateLocked(mCurrentUserId);
2307 // Broadcast the event to all hardware inputs.
2308 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002309 if (!serviceState.isHardware || serviceState.service == null) continue;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002310 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002311 serviceState.service.notifyHdmiDeviceRemoved(deviceInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002312 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002313 Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002314 }
2315 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002316 }
2317 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09002318
2319 @Override
Wonsik Kime92f8572014-08-12 18:30:24 +09002320 public void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo deviceInfo) {
2321 synchronized (mLock) {
2322 Integer state = null;
2323 switch (deviceInfo.getDevicePowerStatus()) {
2324 case HdmiControlManager.POWER_STATUS_ON:
2325 state = INPUT_STATE_CONNECTED;
2326 break;
2327 case HdmiControlManager.POWER_STATUS_STANDBY:
2328 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON:
2329 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY:
2330 state = INPUT_STATE_CONNECTED_STANDBY;
2331 break;
2332 case HdmiControlManager.POWER_STATUS_UNKNOWN:
2333 default:
2334 state = null;
2335 break;
2336 }
2337 if (state != null) {
2338 setStateLocked(inputId, state.intValue(), mCurrentUserId);
2339 }
2340 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09002341 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002342 }
Jae Seo39570912014-02-20 18:23:25 -08002343}