blob: 0f51c82eba375103c7e869ab5deb1ba530a6335e [file] [log] [blame]
Jae Seo39570912014-02-20 18:23:25 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.tv;
18
Wonsik Kim969167d2014-06-24 16:33:17 +090019import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
Wonsik Kime92f8572014-08-12 18:30:24 +090020import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
Wonsik Kim969167d2014-06-24 16:33:17 +090021
Jae Seo4eee6a72016-02-06 11:11:35 +090022import android.annotation.Nullable;
Jae Seo39570912014-02-20 18:23:25 -080023import android.app.ActivityManager;
24import android.content.BroadcastReceiver;
25import android.content.ComponentName;
Jae Seo5c80ad22014-06-12 19:52:58 -070026import android.content.ContentProviderOperation;
27import android.content.ContentProviderResult;
Jae Seo31dc634be2014-04-15 17:40:23 -070028import android.content.ContentResolver;
29import android.content.ContentUris;
30import android.content.ContentValues;
Jae Seo39570912014-02-20 18:23:25 -080031import android.content.Context;
32import android.content.Intent;
33import android.content.IntentFilter;
Jae Seo5c80ad22014-06-12 19:52:58 -070034import android.content.OperationApplicationException;
Jae Seo39570912014-02-20 18:23:25 -080035import android.content.ServiceConnection;
Jae Seo9c165d62014-08-25 14:39:26 -070036import android.content.pm.ActivityInfo;
Jae Seo39570912014-02-20 18:23:25 -080037import android.content.pm.PackageManager;
Jae Seo8c375fe2015-06-23 20:33:25 -070038import android.content.pm.PackageManager.NameNotFoundException;
Jae Seo39570912014-02-20 18:23:25 -080039import 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 Seo4b34cc72015-05-15 17:29:39 -070044import android.media.PlaybackParams;
Jaesung Chung58739e72015-04-24 19:39:59 +090045import android.media.tv.DvbDeviceInfo;
Jae Seod5cc4a22014-05-30 16:57:43 -070046import android.media.tv.ITvInputClient;
47import android.media.tv.ITvInputHardware;
48import android.media.tv.ITvInputHardwareCallback;
49import android.media.tv.ITvInputManager;
Wonsik Kim969167d2014-06-24 16:33:17 +090050import android.media.tv.ITvInputManagerCallback;
Jae Seod5cc4a22014-05-30 16:57:43 -070051import android.media.tv.ITvInputService;
52import android.media.tv.ITvInputServiceCallback;
53import android.media.tv.ITvInputSession;
54import android.media.tv.ITvInputSessionCallback;
Jae Seo783645e2014-07-28 17:30:50 +090055import android.media.tv.TvContentRating;
Jae Seo9c165d62014-08-25 14:39:26 -070056import android.media.tv.TvContentRatingSystemInfo;
Jae Seod5cc4a22014-05-30 16:57:43 -070057import android.media.tv.TvContract;
58import android.media.tv.TvInputHardwareInfo;
59import android.media.tv.TvInputInfo;
Jae Seo9c165d62014-08-25 14:39:26 -070060import android.media.tv.TvInputManager;
Jae Seod5cc4a22014-05-30 16:57:43 -070061import android.media.tv.TvInputService;
Terry Heoc086a3d2014-06-18 14:26:44 +090062import android.media.tv.TvStreamConfig;
Dongwon Kang1f213912014-07-02 18:35:08 +090063import android.media.tv.TvTrackInfo;
Jae Seo39570912014-02-20 18:23:25 -080064import android.net.Uri;
65import android.os.Binder;
Youngsang Cho832860f2014-05-21 20:54:03 +090066import android.os.Bundle;
Jae Seo31dc634be2014-04-15 17:40:23 -070067import android.os.Handler;
Jae Seo39570912014-02-20 18:23:25 -080068import android.os.IBinder;
Jae Seo31dc634be2014-04-15 17:40:23 -070069import android.os.Looper;
70import android.os.Message;
Jaesung Chung58739e72015-04-24 19:39:59 +090071import android.os.ParcelFileDescriptor;
Jae Seo39570912014-02-20 18:23:25 -080072import android.os.Process;
73import android.os.RemoteException;
74import android.os.UserHandle;
Jae Seoc2a89512016-01-28 10:38:11 -080075import android.text.TextUtils;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090076import android.util.Slog;
Jae Seo39570912014-02-20 18:23:25 -080077import android.util.SparseArray;
Jae Seo6a6059a2014-04-17 21:35:29 -070078import android.view.InputChannel;
Jae Seo39570912014-02-20 18:23:25 -080079import android.view.Surface;
80
81import com.android.internal.content.PackageMonitor;
Jae Seo31dc634be2014-04-15 17:40:23 -070082import com.android.internal.os.SomeArgs;
Jaewan Kime14c3f42014-06-27 13:47:48 +090083import com.android.internal.util.IndentingPrintWriter;
Jae Seo31dc634be2014-04-15 17:40:23 -070084import com.android.server.IoThread;
Jae Seo39570912014-02-20 18:23:25 -080085import com.android.server.SystemService;
86
Chulwoo Leee7bb7d62014-05-27 14:10:37 +090087import org.xmlpull.v1.XmlPullParserException;
88
Jaesung Chung58739e72015-04-24 19:39:59 +090089import java.io.File;
Jaewan Kime14c3f42014-06-27 13:47:48 +090090import java.io.FileDescriptor;
Jaesung Chung58739e72015-04-24 19:39:59 +090091import java.io.FileNotFoundException;
Chulwoo Leee7bb7d62014-05-27 14:10:37 +090092import java.io.IOException;
Jaewan Kime14c3f42014-06-27 13:47:48 +090093import java.io.PrintWriter;
Jae Seo39570912014-02-20 18:23:25 -080094import java.util.ArrayList;
Chulwoo Lee19ba61a2014-09-03 00:59:35 +090095import java.util.Arrays;
Jaesung Chung58739e72015-04-24 19:39:59 +090096import java.util.Collections;
Jae Seo39570912014-02-20 18:23:25 -080097import java.util.HashMap;
Jae Seo5c80ad22014-06-12 19:52:58 -070098import java.util.HashSet;
Wonsik Kim187423c2014-06-25 14:12:48 +090099import java.util.Iterator;
Jae Seo39570912014-02-20 18:23:25 -0800100import java.util.List;
101import java.util.Map;
Jae Seo5c80ad22014-06-12 19:52:58 -0700102import java.util.Set;
Jaesung Chung58739e72015-04-24 19:39:59 +0900103import java.util.regex.Matcher;
104import java.util.regex.Pattern;
Jae Seo39570912014-02-20 18:23:25 -0800105
106/** This class provides a system service that manages television inputs. */
107public final class TvInputManagerService extends SystemService {
Jae Seoee2ec052014-09-14 10:30:05 -0700108 private static final boolean DEBUG = false;
Jae Seo39570912014-02-20 18:23:25 -0800109 private static final String TAG = "TvInputManagerService";
110
Jaesung Chung58739e72015-04-24 19:39:59 +0900111 // Pattern for selecting the DVB frontend devices from the list of files in the /dev directory.
112 private static final Pattern sFrontEndDevicePattern =
113 Pattern.compile("^dvb([0-9]+)\\.frontend([0-9]+)$");
114
Jae Seo39570912014-02-20 18:23:25 -0800115 private final Context mContext;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000116 private final TvInputHardwareManager mTvInputHardwareManager;
Jae Seo39570912014-02-20 18:23:25 -0800117
118 // A global lock.
119 private final Object mLock = new Object();
120
121 // ID of the current user.
Xiaohui Chen233d94c2015-07-30 15:08:00 -0700122 private int mCurrentUserId = UserHandle.USER_SYSTEM;
Jae Seo39570912014-02-20 18:23:25 -0800123
124 // A map from user id to UserState.
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700125 private final SparseArray<UserState> mUserStates = new SparseArray<>();
Jae Seo39570912014-02-20 18:23:25 -0800126
Jae Seo7eb75df2014-08-08 22:20:48 -0700127 private final WatchLogHandler mWatchLogHandler;
Jae Seo31dc634be2014-04-15 17:40:23 -0700128
Jae Seo39570912014-02-20 18:23:25 -0800129 public TvInputManagerService(Context context) {
130 super(context);
Jae Seo31dc634be2014-04-15 17:40:23 -0700131
Jae Seo39570912014-02-20 18:23:25 -0800132 mContext = context;
Jae Seo8c375fe2015-06-23 20:33:25 -0700133 mWatchLogHandler = new WatchLogHandler(mContext.getContentResolver(),
134 IoThread.get().getLooper());
Wonsik Kim187423c2014-06-25 14:12:48 +0900135 mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
Jae Seo31dc634be2014-04-15 17:40:23 -0700136
Jae Seo39570912014-02-20 18:23:25 -0800137 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700138 getOrCreateUserStateLocked(mCurrentUserId);
Jae Seo39570912014-02-20 18:23:25 -0800139 }
140 }
141
142 @Override
143 public void onStart() {
144 publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());
145 }
146
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900147 @Override
148 public void onBootPhase(int phase) {
149 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
150 registerBroadcastReceivers();
Wonsik Kim187423c2014-06-25 14:12:48 +0900151 } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900152 synchronized (mLock) {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900153 buildTvInputListLocked(mCurrentUserId, null);
Jae Seo9c165d62014-08-25 14:39:26 -0700154 buildTvContentRatingSystemListLocked(mCurrentUserId);
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900155 }
156 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900157 mTvInputHardwareManager.onBootPhase(phase);
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900158 }
159
Jae Seo39570912014-02-20 18:23:25 -0800160 private void registerBroadcastReceivers() {
161 PackageMonitor monitor = new PackageMonitor() {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900162 private void buildTvInputList(String[] packages) {
163 synchronized (mLock) {
164 buildTvInputListLocked(getChangingUserId(), packages);
165 buildTvContentRatingSystemListLocked(getChangingUserId());
166 }
167 }
168
169 @Override
170 public void onPackageUpdateFinished(String packageName, int uid) {
171 if (DEBUG) Slog.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")");
172 // This callback is invoked when the TV input is reinstalled.
173 // In this case, isReplacing() always returns true.
174 buildTvInputList(new String[] { packageName });
175 }
176
177 @Override
178 public void onPackagesAvailable(String[] packages) {
179 if (DEBUG) {
180 Slog.d(TAG, "onPackagesAvailable(packages=" + Arrays.toString(packages) + ")");
181 }
182 // This callback is invoked when the media on which some packages exist become
183 // available.
184 if (isReplacing()) {
185 buildTvInputList(packages);
186 }
187 }
188
189 @Override
190 public void onPackagesUnavailable(String[] packages) {
191 // This callback is invoked when the media on which some packages exist become
192 // unavailable.
193 if (DEBUG) {
194 Slog.d(TAG, "onPackagesUnavailable(packages=" + Arrays.toString(packages)
195 + ")");
196 }
197 if (isReplacing()) {
198 buildTvInputList(packages);
199 }
200 }
201
Jae Seo39570912014-02-20 18:23:25 -0800202 @Override
203 public void onSomePackagesChanged() {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900204 // TODO: Use finer-grained methods(e.g. onPackageAdded, onPackageRemoved) to manage
205 // the TV inputs.
Dongwon Kang426c9a42014-08-26 17:39:21 +0900206 if (DEBUG) Slog.d(TAG, "onSomePackagesChanged()");
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900207 if (isReplacing()) {
208 if (DEBUG) Slog.d(TAG, "Skipped building TV input list due to replacing");
209 // When the package is updated, buildTvInputListLocked is called in other
210 // methods instead.
211 return;
Jae Seo39570912014-02-20 18:23:25 -0800212 }
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900213 buildTvInputList(null);
Jae Seo39570912014-02-20 18:23:25 -0800214 }
Jae Seo5c80ad22014-06-12 19:52:58 -0700215
216 @Override
Dongwon Kang31a8f842015-04-08 18:26:23 -0700217 public boolean onPackageChanged(String packageName, int uid, String[] components) {
218 // The input list needs to be updated in any cases, regardless of whether
219 // it happened to the whole package or a specific component. Returning true so that
220 // the update can be handled in {@link #onSomePackagesChanged}.
221 return true;
222 }
223
224 @Override
Jae Seo5c80ad22014-06-12 19:52:58 -0700225 public void onPackageRemoved(String packageName, int uid) {
226 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700227 UserState userState = getOrCreateUserStateLocked(getChangingUserId());
Wonsik Kim969167d2014-06-24 16:33:17 +0900228 if (!userState.packageSet.contains(packageName)) {
Jae Seo5c80ad22014-06-12 19:52:58 -0700229 // Not a TV input package.
230 return;
231 }
232 }
233
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700234 ArrayList<ContentProviderOperation> operations = new ArrayList<>();
Jae Seo5c80ad22014-06-12 19:52:58 -0700235
236 String selection = TvContract.BaseTvColumns.COLUMN_PACKAGE_NAME + "=?";
237 String[] selectionArgs = { packageName };
238
239 operations.add(ContentProviderOperation.newDelete(TvContract.Channels.CONTENT_URI)
240 .withSelection(selection, selectionArgs).build());
241 operations.add(ContentProviderOperation.newDelete(TvContract.Programs.CONTENT_URI)
242 .withSelection(selection, selectionArgs).build());
243 operations.add(ContentProviderOperation
244 .newDelete(TvContract.WatchedPrograms.CONTENT_URI)
245 .withSelection(selection, selectionArgs).build());
246
247 ContentProviderResult[] results = null;
248 try {
Jae Seo8c375fe2015-06-23 20:33:25 -0700249 ContentResolver cr = getContentResolverForUser(getChangingUserId());
250 results = cr.applyBatch(TvContract.AUTHORITY, operations);
Jae Seo5c80ad22014-06-12 19:52:58 -0700251 } catch (RemoteException | OperationApplicationException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700252 Slog.e(TAG, "error in applyBatch", e);
Jae Seo5c80ad22014-06-12 19:52:58 -0700253 }
254
255 if (DEBUG) {
256 Slog.d(TAG, "onPackageRemoved(packageName=" + packageName + ", uid=" + uid
257 + ")");
258 Slog.d(TAG, "results=" + results);
259 }
260 }
Jae Seo39570912014-02-20 18:23:25 -0800261 };
262 monitor.register(mContext, null, UserHandle.ALL, true);
263
264 IntentFilter intentFilter = new IntentFilter();
265 intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
266 intentFilter.addAction(Intent.ACTION_USER_REMOVED);
267 mContext.registerReceiverAsUser(new BroadcastReceiver() {
268 @Override
269 public void onReceive(Context context, Intent intent) {
270 String action = intent.getAction();
271 if (Intent.ACTION_USER_SWITCHED.equals(action)) {
272 switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
273 } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
274 removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
275 }
276 }
277 }, UserHandle.ALL, intentFilter, null, null);
278 }
279
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900280 private static boolean hasHardwarePermission(PackageManager pm, ComponentName component) {
Wonsik Kim187423c2014-06-25 14:12:48 +0900281 return pm.checkPermission(android.Manifest.permission.TV_INPUT_HARDWARE,
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900282 component.getPackageName()) == PackageManager.PERMISSION_GRANTED;
Wonsik Kim187423c2014-06-25 14:12:48 +0900283 }
284
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900285 private void buildTvInputListLocked(int userId, String[] updatedPackages) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700286 UserState userState = getOrCreateUserStateLocked(userId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900287 userState.packageSet.clear();
Jae Seo39570912014-02-20 18:23:25 -0800288
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900289 if (DEBUG) Slog.d(TAG, "buildTvInputList");
Jae Seo39570912014-02-20 18:23:25 -0800290 PackageManager pm = mContext.getPackageManager();
Jae Seo76976fa2015-05-20 21:51:16 -0700291 List<ResolveInfo> services = pm.queryIntentServicesAsUser(
Chulwoo Leee7bb7d62014-05-27 14:10:37 +0900292 new Intent(TvInputService.SERVICE_INTERFACE),
Jae Seo76976fa2015-05-20 21:51:16 -0700293 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
294 userId);
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700295 List<TvInputInfo> inputList = new ArrayList<>();
Jae Seo39570912014-02-20 18:23:25 -0800296 for (ResolveInfo ri : services) {
297 ServiceInfo si = ri.serviceInfo;
298 if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900299 Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
Jae Seo39570912014-02-20 18:23:25 -0800300 + android.Manifest.permission.BIND_TV_INPUT);
301 continue;
302 }
Jae Seo9cc28e52014-08-12 16:45:58 -0700303
304 ComponentName component = new ComponentName(si.packageName, si.name);
305 if (hasHardwarePermission(pm, component)) {
306 ServiceState serviceState = userState.serviceStateMap.get(component);
307 if (serviceState == null) {
308 // We see this hardware TV input service for the first time; we need to
309 // prepare the ServiceState object so that we can connect to the service and
310 // let it add TvInputInfo objects to mInputList if there's any.
311 serviceState = new ServiceState(component, userId);
312 userState.serviceStateMap.put(component, serviceState);
Wonsik Kimf271eac2014-08-30 12:55:10 +0900313 updateServiceConnectionLocked(component, userId);
Wonsik Kim187423c2014-06-25 14:12:48 +0900314 } else {
Jae Seo1abbbcd2016-01-28 22:20:41 -0800315 inputList.addAll(serviceState.hardwareInputList);
Jae Seo9cc28e52014-08-12 16:45:58 -0700316 }
317 } else {
318 try {
Jae Seoc03671f2016-01-26 15:15:22 -0800319 TvInputInfo info = new TvInputInfo.Builder(mContext, ri).build();
320 inputList.add(info);
Jae Seo9cc28e52014-08-12 16:45:58 -0700321 } catch (XmlPullParserException | IOException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700322 Slog.e(TAG, "failed to load TV input " + si.name, e);
Jae Seo9cc28e52014-08-12 16:45:58 -0700323 continue;
Wonsik Kim969167d2014-06-24 16:33:17 +0900324 }
Chulwoo Leee7bb7d62014-05-27 14:10:37 +0900325 }
Jae Seo9cc28e52014-08-12 16:45:58 -0700326 userState.packageSet.add(si.packageName);
327 }
328
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700329 Map<String, TvInputState> inputMap = new HashMap<>();
Jae Seo9cc28e52014-08-12 16:45:58 -0700330 for (TvInputInfo info : inputList) {
Jae Seofea8dd42014-08-26 13:57:41 -0700331 if (DEBUG) {
332 Slog.d(TAG, "add " + info.getId());
333 }
Jae Seoabda4202016-01-28 19:13:04 -0800334 TvInputState inputState = userState.inputMap.get(info.getId());
335 if (inputState == null) {
336 inputState = new TvInputState();
337 inputState.info = info;
Jae Seo9cc28e52014-08-12 16:45:58 -0700338 }
Jae Seoabda4202016-01-28 19:13:04 -0800339 inputMap.put(info.getId(), inputState);
Jae Seo39570912014-02-20 18:23:25 -0800340 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900341
342 for (String inputId : inputMap.keySet()) {
343 if (!userState.inputMap.containsKey(inputId)) {
344 notifyInputAddedLocked(userState, inputId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900345 } else if (updatedPackages != null) {
346 // Notify the package updates
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +0900347 ComponentName component = inputMap.get(inputId).info.getComponent();
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900348 for (String updatedPackage : updatedPackages) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +0900349 if (component.getPackageName().equals(updatedPackage)) {
350 updateServiceConnectionLocked(component, userId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900351 notifyInputUpdatedLocked(userState, inputId);
352 break;
353 }
354 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900355 }
356 }
357
358 for (String inputId : userState.inputMap.keySet()) {
359 if (!inputMap.containsKey(inputId)) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900360 TvInputInfo info = userState.inputMap.get(inputId).info;
Dongwon Kang426c9a42014-08-26 17:39:21 +0900361 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
362 if (serviceState != null) {
363 abortPendingCreateSessionRequestsLocked(serviceState, inputId, userId);
364 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900365 notifyInputRemovedLocked(userState, inputId);
366 }
367 }
368
369 userState.inputMap.clear();
370 userState.inputMap = inputMap;
Jae Seo9c165d62014-08-25 14:39:26 -0700371 }
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900372
Jae Seo9c165d62014-08-25 14:39:26 -0700373 private void buildTvContentRatingSystemListLocked(int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700374 UserState userState = getOrCreateUserStateLocked(userId);
Jae Seo9c165d62014-08-25 14:39:26 -0700375 userState.contentRatingSystemList.clear();
376
377 final PackageManager pm = mContext.getPackageManager();
378 Intent intent = new Intent(TvInputManager.ACTION_QUERY_CONTENT_RATING_SYSTEMS);
379 for (ResolveInfo resolveInfo :
380 pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA)) {
381 ActivityInfo receiver = resolveInfo.activityInfo;
382 Bundle metaData = receiver.metaData;
383 if (metaData == null) {
384 continue;
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900385 }
Jae Seo9c165d62014-08-25 14:39:26 -0700386
387 int xmlResId = metaData.getInt(TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS);
388 if (xmlResId == 0) {
389 Slog.w(TAG, "Missing meta-data '"
390 + TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS + "' on receiver "
391 + receiver.packageName + "/" + receiver.name);
392 continue;
393 }
394 userState.contentRatingSystemList.add(
395 TvContentRatingSystemInfo.createTvContentRatingSystemInfo(xmlResId,
396 receiver.applicationInfo));
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900397 }
Jae Seo39570912014-02-20 18:23:25 -0800398 }
399
400 private void switchUser(int userId) {
401 synchronized (mLock) {
402 if (mCurrentUserId == userId) {
403 return;
404 }
Jae Seo8c375fe2015-06-23 20:33:25 -0700405 clearSessionAndServiceStatesLocked(mUserStates.get(mCurrentUserId));
Jae Seo39570912014-02-20 18:23:25 -0800406
Jae Seo8c375fe2015-06-23 20:33:25 -0700407 mCurrentUserId = userId;
Jae Seo4f1a6d42015-07-20 16:15:01 -0700408 getOrCreateUserStateLocked(userId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900409 buildTvInputListLocked(userId, null);
Jae Seo9c165d62014-08-25 14:39:26 -0700410 buildTvContentRatingSystemListLocked(userId);
Jae Seo8c375fe2015-06-23 20:33:25 -0700411 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_SWITCH_CONTENT_RESOLVER,
412 getContentResolverForUser(userId)).sendToTarget();
Jae Seo39570912014-02-20 18:23:25 -0800413 }
414 }
415
416 private void removeUser(int userId) {
417 synchronized (mLock) {
Jae Seob06cb882014-04-09 12:08:17 -0700418 UserState userState = mUserStates.get(userId);
419 if (userState == null) {
420 return;
421 }
Jae Seo8c375fe2015-06-23 20:33:25 -0700422 clearSessionAndServiceStatesLocked(userState);
Jae Seo39570912014-02-20 18:23:25 -0800423
Jae Seofea8dd42014-08-26 13:57:41 -0700424 // Clear everything else.
425 userState.inputMap.clear();
426 userState.packageSet.clear();
Jae Seo9c165d62014-08-25 14:39:26 -0700427 userState.contentRatingSystemList.clear();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900428 userState.clientStateMap.clear();
Jae Seofea8dd42014-08-26 13:57:41 -0700429 userState.callbackSet.clear();
430 userState.mainSessionToken = null;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900431
Jae Seo39570912014-02-20 18:23:25 -0800432 mUserStates.remove(userId);
433 }
434 }
435
Jae Seo8c375fe2015-06-23 20:33:25 -0700436 private void clearSessionAndServiceStatesLocked(UserState userState) {
437 // Release created sessions.
438 for (SessionState state : userState.sessionStateMap.values()) {
439 if (state.session != null) {
440 try {
Jae Seoa826d012016-01-18 13:03:35 -0800441 if (state.isRecordingSession) {
442 state.session.disconnect();
443 } else {
444 state.session.release();
445 }
Jae Seo8c375fe2015-06-23 20:33:25 -0700446 } catch (RemoteException e) {
447 Slog.e(TAG, "error in release", e);
448 }
449 }
450 }
451 userState.sessionStateMap.clear();
452
453 // Unregister all callbacks and unbind all services.
454 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kang81e3c3e2015-09-11 15:24:25 -0700455 if (serviceState.service != null) {
456 if (serviceState.callback != null) {
457 try {
458 serviceState.service.unregisterCallback(serviceState.callback);
459 } catch (RemoteException e) {
460 Slog.e(TAG, "error in unregisterCallback", e);
461 }
Jae Seo8c375fe2015-06-23 20:33:25 -0700462 }
Dongwon Kang81e3c3e2015-09-11 15:24:25 -0700463 mContext.unbindService(serviceState.connection);
Jae Seo8c375fe2015-06-23 20:33:25 -0700464 }
Jae Seo8c375fe2015-06-23 20:33:25 -0700465 }
466 userState.serviceStateMap.clear();
467 }
468
469 private ContentResolver getContentResolverForUser(int userId) {
470 UserHandle user = new UserHandle(userId);
471 Context context;
472 try {
473 context = mContext.createPackageContextAsUser("android", 0, user);
474 } catch (NameNotFoundException e) {
Jae Seo2a2b2992016-01-12 23:13:14 -0800475 Slog.e(TAG, "failed to create package context as user " + user);
Jae Seo8c375fe2015-06-23 20:33:25 -0700476 context = mContext;
477 }
478 return context.getContentResolver();
479 }
480
Jae Seo4f1a6d42015-07-20 16:15:01 -0700481 private UserState getOrCreateUserStateLocked(int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800482 UserState userState = mUserStates.get(userId);
483 if (userState == null) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700484 userState = new UserState(mContext, userId);
485 mUserStates.put(userId, userState);
Jae Seo39570912014-02-20 18:23:25 -0800486 }
487 return userState;
488 }
489
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900490 private ServiceState getServiceStateLocked(ComponentName component, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700491 UserState userState = getOrCreateUserStateLocked(userId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900492 ServiceState serviceState = userState.serviceStateMap.get(component);
Jae Seo39570912014-02-20 18:23:25 -0800493 if (serviceState == null) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900494 throw new IllegalStateException("Service state not found for " + component + " (userId="
Sungsoo Lim7de5e232014-04-12 16:51:27 +0900495 + userId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800496 }
497 return serviceState;
498 }
499
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900500 private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700501 UserState userState = getOrCreateUserStateLocked(userId);
Jae Seo39570912014-02-20 18:23:25 -0800502 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
503 if (sessionState == null) {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900504 throw new SessionNotFoundException("Session state not found for token " + sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800505 }
506 // Only the application that requested this session or the system can access it.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900507 if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) {
Jae Seo39570912014-02-20 18:23:25 -0800508 throw new SecurityException("Illegal access to the session with token " + sessionToken
509 + " from uid " + callingUid);
510 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900511 return sessionState;
512 }
513
514 private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900515 return getSessionLocked(getSessionStateLocked(sessionToken, callingUid, userId));
516 }
517
518 private ITvInputSession getSessionLocked(SessionState sessionState) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900519 ITvInputSession session = sessionState.session;
Jae Seo39570912014-02-20 18:23:25 -0800520 if (session == null) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900521 throw new IllegalStateException("Session not yet created for token "
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900522 + sessionState.sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800523 }
524 return session;
525 }
526
527 private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
528 String methodName) {
529 return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
530 false, methodName, null);
531 }
532
Wonsik Kim187423c2014-06-25 14:12:48 +0900533 private static boolean shouldMaintainConnection(ServiceState serviceState) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900534 return !serviceState.sessionTokens.isEmpty() || serviceState.isHardware;
535 // TODO: Find a way to maintain connection to hardware TV input service only when necessary.
Wonsik Kim187423c2014-06-25 14:12:48 +0900536 }
537
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900538 private void updateServiceConnectionLocked(ComponentName component, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700539 UserState userState = getOrCreateUserStateLocked(userId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900540 ServiceState serviceState = userState.serviceStateMap.get(component);
Jae Seo39570912014-02-20 18:23:25 -0800541 if (serviceState == null) {
542 return;
543 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900544 if (serviceState.reconnecting) {
545 if (!serviceState.sessionTokens.isEmpty()) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900546 // wait until all the sessions are removed.
547 return;
548 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900549 serviceState.reconnecting = false;
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900550 }
Wonsik Kim187423c2014-06-25 14:12:48 +0900551 boolean maintainConnection = shouldMaintainConnection(serviceState);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900552 if (serviceState.service == null && maintainConnection && userId == mCurrentUserId) {
Jae Seo39570912014-02-20 18:23:25 -0800553 // This means that the service is not yet connected but its state indicates that we
554 // have pending requests. Then, connect the service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900555 if (serviceState.bound) {
Jae Seo39570912014-02-20 18:23:25 -0800556 // We have already bound to the service so we don't try to bind again until after we
557 // unbind later on.
558 return;
559 }
560 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900561 Slog.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800562 }
Sungsoo Limd6672b52014-04-30 10:43:26 +0900563
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900564 Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900565 serviceState.bound = mContext.bindServiceAsUser(
Dianne Hackbornd69e4c12015-04-24 09:54:54 -0700566 i, serviceState.connection,
567 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
568 new UserHandle(userId));
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900569 } else if (serviceState.service != null && !maintainConnection) {
Jae Seo39570912014-02-20 18:23:25 -0800570 // This means that the service is already connected but its state indicates that we have
571 // nothing to do with it. Then, disconnect the service.
572 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900573 Slog.d(TAG, "unbindService(service=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -0800574 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900575 mContext.unbindService(serviceState.connection);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900576 userState.serviceStateMap.remove(component);
Jae Seo39570912014-02-20 18:23:25 -0800577 }
578 }
579
Dongwon Kang426c9a42014-08-26 17:39:21 +0900580 private void abortPendingCreateSessionRequestsLocked(ServiceState serviceState,
581 String inputId, int userId) {
582 // Let clients know the create session requests are failed.
Jae Seo4f1a6d42015-07-20 16:15:01 -0700583 UserState userState = getOrCreateUserStateLocked(userId);
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900584 List<SessionState> sessionsToAbort = new ArrayList<>();
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900585 for (IBinder sessionToken : serviceState.sessionTokens) {
Dongwon Kang426c9a42014-08-26 17:39:21 +0900586 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900587 if (sessionState.session == null && (inputId == null
Jae Seo2cdb05e2016-02-04 22:17:13 +0900588 || sessionState.inputId.equals(inputId))) {
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900589 sessionsToAbort.add(sessionState);
Dongwon Kang426c9a42014-08-26 17:39:21 +0900590 }
591 }
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900592 for (SessionState sessionState : sessionsToAbort) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900593 removeSessionStateLocked(sessionState.sessionToken, sessionState.userId);
594 sendSessionTokenToClientLocked(sessionState.client,
Jae Seo2cdb05e2016-02-04 22:17:13 +0900595 sessionState.inputId, null, null, sessionState.seq);
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900596 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900597 updateServiceConnectionLocked(serviceState.component, userId);
Dongwon Kang426c9a42014-08-26 17:39:21 +0900598 }
599
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900600 private void createSessionInternalLocked(ITvInputService service, IBinder sessionToken,
601 int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700602 UserState userState = getOrCreateUserStateLocked(userId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900603 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800604 if (DEBUG) {
Jae Seo2cdb05e2016-02-04 22:17:13 +0900605 Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.inputId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800606 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900607 InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
Jae Seo6a6059a2014-04-17 21:35:29 -0700608
Jae Seo39570912014-02-20 18:23:25 -0800609 // Set up a callback to send the session token.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900610 ITvInputSessionCallback callback = new SessionCallback(sessionState, channels);
Jae Seo39570912014-02-20 18:23:25 -0800611
612 // Create a session. When failed, send a null token immediately.
613 try {
Jae Seoa826d012016-01-18 13:03:35 -0800614 if (sessionState.isRecordingSession) {
Jae Seo2cdb05e2016-02-04 22:17:13 +0900615 service.createRecordingSession(callback, sessionState.inputId);
Jae Seoa826d012016-01-18 13:03:35 -0800616 } else {
Jae Seo2cdb05e2016-02-04 22:17:13 +0900617 service.createSession(channels[1], callback, sessionState.inputId);
Jae Seoa826d012016-01-18 13:03:35 -0800618 }
Jae Seo39570912014-02-20 18:23:25 -0800619 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900620 Slog.e(TAG, "error in createSession", e);
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900621 removeSessionStateLocked(sessionToken, userId);
Jae Seo2cdb05e2016-02-04 22:17:13 +0900622 sendSessionTokenToClientLocked(sessionState.client, sessionState.inputId, null,
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900623 null, sessionState.seq);
Jae Seo39570912014-02-20 18:23:25 -0800624 }
Jae Seo6a6059a2014-04-17 21:35:29 -0700625 channels[1].dispose();
Jae Seo39570912014-02-20 18:23:25 -0800626 }
627
Sungsoo Limd6672b52014-04-30 10:43:26 +0900628 private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId,
Jae Seo5c80ad22014-06-12 19:52:58 -0700629 IBinder sessionToken, InputChannel channel, int seq) {
Jae Seo39570912014-02-20 18:23:25 -0800630 try {
Sungsoo Limd6672b52014-04-30 10:43:26 +0900631 client.onSessionCreated(inputId, sessionToken, channel, seq);
Jae Seofea8dd42014-08-26 13:57:41 -0700632 } catch (RemoteException e) {
633 Slog.e(TAG, "error in onSessionCreated", e);
Jae Seo39570912014-02-20 18:23:25 -0800634 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900635 }
Jae Seo39570912014-02-20 18:23:25 -0800636
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900637 private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900638 SessionState sessionState = null;
639 try {
640 sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
641 if (sessionState.session != null) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700642 UserState userState = getOrCreateUserStateLocked(userId);
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900643 if (sessionToken == userState.mainSessionToken) {
644 setMainLocked(sessionToken, false, callingUid, userId);
645 }
Jae Seoa826d012016-01-18 13:03:35 -0800646 if (sessionState.isRecordingSession) {
647 sessionState.session.disconnect();
648 } else {
649 sessionState.session.release();
650 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900651 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900652 } catch (RemoteException | SessionNotFoundException e) {
653 Slog.e(TAG, "error in releaseSession", e);
654 } finally {
655 if (sessionState != null) {
656 sessionState.session = null;
657 }
Jae Seo39570912014-02-20 18:23:25 -0800658 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900659 removeSessionStateLocked(sessionToken, userId);
Jae Seo39570912014-02-20 18:23:25 -0800660 }
661
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900662 private void removeSessionStateLocked(IBinder sessionToken, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700663 UserState userState = getOrCreateUserStateLocked(userId);
Ji-Hwan Leeabca0ee2014-07-24 17:34:19 +0900664 if (sessionToken == userState.mainSessionToken) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900665 if (DEBUG) {
666 Slog.d(TAG, "mainSessionToken=null");
667 }
Ji-Hwan Leeabca0ee2014-07-24 17:34:19 +0900668 userState.mainSessionToken = null;
669 }
670
671 // Remove the session state from the global session state map of the current user.
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900672 SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
673
Chulwoo Lee8d4ded02014-07-10 03:56:39 +0900674 if (sessionState == null) {
675 return;
676 }
677
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900678 // Also remove the session token from the session token list of the current client and
679 // service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900680 ClientState clientState = userState.clientStateMap.get(sessionState.client.asBinder());
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900681 if (clientState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900682 clientState.sessionTokens.remove(sessionToken);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900683 if (clientState.isEmpty()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900684 userState.clientStateMap.remove(sessionState.client.asBinder());
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900685 }
686 }
687
Jae Seo2cdb05e2016-02-04 22:17:13 +0900688 ServiceState serviceState = userState.serviceStateMap.get(sessionState.componentName);
689 if (serviceState != null) {
690 serviceState.sessionTokens.remove(sessionToken);
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900691 }
Jae Seo2cdb05e2016-02-04 22:17:13 +0900692 updateServiceConnectionLocked(sessionState.componentName, userId);
Jae Seo7eb75df2014-08-08 22:20:48 -0700693
694 // Log the end of watch.
695 SomeArgs args = SomeArgs.obtain();
696 args.arg1 = sessionToken;
697 args.arg2 = System.currentTimeMillis();
698 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_END, args).sendToTarget();
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900699 }
700
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900701 private void setMainLocked(IBinder sessionToken, boolean isMain, int callingUid, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900702 try {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900703 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
704 if (sessionState.hardwareSessionToken != null) {
705 sessionState = getSessionStateLocked(sessionState.hardwareSessionToken,
706 Process.SYSTEM_UID, userId);
707 }
Jae Seo2cdb05e2016-02-04 22:17:13 +0900708 ServiceState serviceState = getServiceStateLocked(sessionState.componentName, userId);
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900709 if (!serviceState.isHardware) {
710 return;
711 }
712 ITvInputSession session = getSessionLocked(sessionState);
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900713 session.setMain(isMain);
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900714 } catch (RemoteException | SessionNotFoundException e) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900715 Slog.e(TAG, "error in setMain", e);
716 }
717 }
718
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900719 private void notifyInputAddedLocked(UserState userState, String inputId) {
720 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700721 Slog.d(TAG, "notifyInputAddedLocked(inputId=" + inputId + ")");
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900722 }
723 for (ITvInputManagerCallback callback : userState.callbackSet) {
724 try {
725 callback.onInputAdded(inputId);
726 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700727 Slog.e(TAG, "failed to report added input to callback", e);
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900728 }
729 }
730 }
731
732 private void notifyInputRemovedLocked(UserState userState, String inputId) {
733 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700734 Slog.d(TAG, "notifyInputRemovedLocked(inputId=" + inputId + ")");
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900735 }
736 for (ITvInputManagerCallback callback : userState.callbackSet) {
737 try {
738 callback.onInputRemoved(inputId);
739 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700740 Slog.e(TAG, "failed to report removed input to callback", e);
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900741 }
742 }
743 }
744
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900745 private void notifyInputUpdatedLocked(UserState userState, String inputId) {
746 if (DEBUG) {
747 Slog.d(TAG, "notifyInputUpdatedLocked(inputId=" + inputId + ")");
748 }
749 for (ITvInputManagerCallback callback : userState.callbackSet) {
750 try {
751 callback.onInputUpdated(inputId);
752 } catch (RemoteException e) {
753 Slog.e(TAG, "failed to report updated input to callback", e);
754 }
755 }
756 }
757
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900758 private void notifyInputStateChangedLocked(UserState userState, String inputId,
Wonsik Kim969167d2014-06-24 16:33:17 +0900759 int state, ITvInputManagerCallback targetCallback) {
760 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700761 Slog.d(TAG, "notifyInputStateChangedLocked(inputId=" + inputId
762 + ", state=" + state + ")");
Wonsik Kim969167d2014-06-24 16:33:17 +0900763 }
764 if (targetCallback == null) {
765 for (ITvInputManagerCallback callback : userState.callbackSet) {
766 try {
767 callback.onInputStateChanged(inputId, state);
768 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700769 Slog.e(TAG, "failed to report state change to callback", e);
Wonsik Kim969167d2014-06-24 16:33:17 +0900770 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900771 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900772 } else {
773 try {
774 targetCallback.onInputStateChanged(inputId, state);
775 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700776 Slog.e(TAG, "failed to report state change to callback", e);
Wonsik Kim969167d2014-06-24 16:33:17 +0900777 }
778 }
779 }
780
Jae Seoc2a89512016-01-28 10:38:11 -0800781 private void setTvInputInfoLocked(UserState userState, TvInputInfo inputInfo) {
Jae Seoa826d012016-01-18 13:03:35 -0800782 if (DEBUG) {
Jae Seoc2a89512016-01-28 10:38:11 -0800783 Slog.d(TAG, "setTvInputInfoLocked(inputInfo=" + inputInfo + ")");
Jae Seoa826d012016-01-18 13:03:35 -0800784 }
Jae Seoabda4202016-01-28 19:13:04 -0800785 String inputId = inputInfo.getId();
786 TvInputState inputState = userState.inputMap.get(inputId);
787 if (inputState == null) {
788 Slog.e(TAG, "failed to set input info - unknown input id " + inputId);
789 return;
790 }
791 if (inputState.info.equals(inputInfo)) {
792 return;
793 }
794 inputState.info = inputInfo;
795
Jae Seoa826d012016-01-18 13:03:35 -0800796 for (ITvInputManagerCallback callback : userState.callbackSet) {
797 try {
Jae Seoc2a89512016-01-28 10:38:11 -0800798 callback.onTvInputInfoChanged(inputInfo);
Jae Seoa826d012016-01-18 13:03:35 -0800799 } catch (RemoteException e) {
800 Slog.e(TAG, "failed to report changed input info to callback", e);
801 }
802 }
803 }
804
Wonsik Kim969167d2014-06-24 16:33:17 +0900805 private void setStateLocked(String inputId, int state, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700806 UserState userState = getOrCreateUserStateLocked(userId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900807 TvInputState inputState = userState.inputMap.get(inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900808 ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent());
809 int oldState = inputState.state;
810 inputState.state = state;
811 if (serviceState != null && serviceState.service == null
Wonsik Kim187423c2014-06-25 14:12:48 +0900812 && shouldMaintainConnection(serviceState)) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900813 // We don't notify state change while reconnecting. It should remain disconnected.
814 return;
815 }
816 if (oldState != state) {
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900817 notifyInputStateChangedLocked(userState, inputId, state, null);
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900818 }
819 }
820
Jae Seo39570912014-02-20 18:23:25 -0800821 private final class BinderService extends ITvInputManager.Stub {
822 @Override
823 public List<TvInputInfo> getTvInputList(int userId) {
824 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
825 Binder.getCallingUid(), userId, "getTvInputList");
826 final long identity = Binder.clearCallingIdentity();
827 try {
828 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700829 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700830 List<TvInputInfo> inputList = new ArrayList<>();
Wonsik Kim969167d2014-06-24 16:33:17 +0900831 for (TvInputState state : userState.inputMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900832 inputList.add(state.info);
Jae Seo39570912014-02-20 18:23:25 -0800833 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900834 return inputList;
Jae Seo39570912014-02-20 18:23:25 -0800835 }
836 } finally {
837 Binder.restoreCallingIdentity(identity);
838 }
Jae Seo39570912014-02-20 18:23:25 -0800839 }
840
841 @Override
Jae Seob3758052014-07-12 19:25:24 -0700842 public TvInputInfo getTvInputInfo(String inputId, int userId) {
843 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
844 Binder.getCallingUid(), userId, "getTvInputInfo");
845 final long identity = Binder.clearCallingIdentity();
846 try {
847 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700848 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seob3758052014-07-12 19:25:24 -0700849 TvInputState state = userState.inputMap.get(inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900850 return state == null ? null : state.info;
Jae Seob3758052014-07-12 19:25:24 -0700851 }
852 } finally {
853 Binder.restoreCallingIdentity(identity);
854 }
855 }
856
Jae Seoc2a89512016-01-28 10:38:11 -0800857 public void setTvInputInfo(TvInputInfo inputInfo, int userId) {
858 String inputInfoPackageName = inputInfo.getServiceInfo().packageName;
859 String callingPackageName = getCallingPackageName();
860 if (!TextUtils.equals(inputInfoPackageName, callingPackageName)) {
861 throw new IllegalArgumentException("calling package " + callingPackageName
862 + " is not allowed to change TvInputInfo for " + inputInfoPackageName);
863 }
864
865 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
Jae Seoabda4202016-01-28 19:13:04 -0800866 Binder.getCallingUid(), userId, "setTvInputInfo");
Jae Seoc2a89512016-01-28 10:38:11 -0800867 final long identity = Binder.clearCallingIdentity();
868 try {
869 synchronized (mLock) {
870 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
871 setTvInputInfoLocked(userState, inputInfo);
872 }
873 } finally {
874 Binder.restoreCallingIdentity(identity);
875 }
876 }
877
878 private String getCallingPackageName() {
879 final String[] packages = mContext.getPackageManager().getPackagesForUid(
880 Binder.getCallingUid());
881 if (packages != null && packages.length > 0) {
882 return packages[0];
883 }
884 return "unknown";
885 }
886
Jae Seob3758052014-07-12 19:25:24 -0700887 @Override
Dongwon Kang993f81e2014-11-27 19:34:18 +0900888 public int getTvInputState(String inputId, int userId) {
889 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
890 Binder.getCallingUid(), userId, "getTvInputState");
891 final long identity = Binder.clearCallingIdentity();
892 try {
893 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700894 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Dongwon Kang993f81e2014-11-27 19:34:18 +0900895 TvInputState state = userState.inputMap.get(inputId);
Jae Seo82fce642015-04-20 15:37:50 -0700896 return state == null ? INPUT_STATE_CONNECTED : state.state;
Dongwon Kang993f81e2014-11-27 19:34:18 +0900897 }
898 } finally {
899 Binder.restoreCallingIdentity(identity);
900 }
901 }
902
903 @Override
Jae Seo9c165d62014-08-25 14:39:26 -0700904 public List<TvContentRatingSystemInfo> getTvContentRatingSystemList(int userId) {
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900905 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
Jae Seo9c165d62014-08-25 14:39:26 -0700906 Binder.getCallingUid(), userId, "getTvContentRatingSystemList");
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900907 final long identity = Binder.clearCallingIdentity();
908 try {
909 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700910 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo9c165d62014-08-25 14:39:26 -0700911 return userState.contentRatingSystemList;
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900912 }
913 } finally {
914 Binder.restoreCallingIdentity(identity);
915 }
916 }
917
918 @Override
Wonsik Kim969167d2014-06-24 16:33:17 +0900919 public void registerCallback(final ITvInputManagerCallback callback, int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800920 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
921 Binder.getCallingUid(), userId, "registerCallback");
922 final long identity = Binder.clearCallingIdentity();
923 try {
924 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700925 final UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900926 userState.callbackSet.add(callback);
Jae Seofea8dd42014-08-26 13:57:41 -0700927 try {
928 callback.asBinder().linkToDeath(new IBinder.DeathRecipient() {
929 @Override
930 public void binderDied() {
931 synchronized (mLock) {
932 if (userState.callbackSet != null) {
933 userState.callbackSet.remove(callback);
934 }
935 }
936 }
937 }, 0);
938 } catch (RemoteException e) {
939 Slog.e(TAG, "client process has already died", e);
940 }
Jae Seo39570912014-02-20 18:23:25 -0800941 }
942 } finally {
943 Binder.restoreCallingIdentity(identity);
944 }
945 }
946
947 @Override
Wonsik Kim969167d2014-06-24 16:33:17 +0900948 public void unregisterCallback(ITvInputManagerCallback callback, int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800949 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
950 Binder.getCallingUid(), userId, "unregisterCallback");
951 final long identity = Binder.clearCallingIdentity();
952 try {
953 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700954 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900955 userState.callbackSet.remove(callback);
Jae Seo39570912014-02-20 18:23:25 -0800956 }
957 } finally {
958 Binder.restoreCallingIdentity(identity);
959 }
960 }
961
962 @Override
Jae Seo783645e2014-07-28 17:30:50 +0900963 public boolean isParentalControlsEnabled(int userId) {
964 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
965 Binder.getCallingUid(), userId, "isParentalControlsEnabled");
966 final long identity = Binder.clearCallingIdentity();
967 try {
968 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700969 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +0900970 return userState.persistentDataStore.isParentalControlsEnabled();
971 }
972 } finally {
973 Binder.restoreCallingIdentity(identity);
974 }
975 }
976
977 @Override
978 public void setParentalControlsEnabled(boolean enabled, int userId) {
979 ensureParentalControlsPermission();
980 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
981 Binder.getCallingUid(), userId, "setParentalControlsEnabled");
982 final long identity = Binder.clearCallingIdentity();
983 try {
984 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700985 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +0900986 userState.persistentDataStore.setParentalControlsEnabled(enabled);
987 }
988 } finally {
989 Binder.restoreCallingIdentity(identity);
990 }
991 }
992
993 @Override
994 public boolean isRatingBlocked(String rating, int userId) {
995 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
996 Binder.getCallingUid(), userId, "isRatingBlocked");
997 final long identity = Binder.clearCallingIdentity();
998 try {
999 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001000 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +09001001 return userState.persistentDataStore.isRatingBlocked(
1002 TvContentRating.unflattenFromString(rating));
1003 }
1004 } finally {
1005 Binder.restoreCallingIdentity(identity);
1006 }
1007 }
1008
1009 @Override
1010 public List<String> getBlockedRatings(int userId) {
1011 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1012 Binder.getCallingUid(), userId, "getBlockedRatings");
1013 final long identity = Binder.clearCallingIdentity();
1014 try {
1015 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001016 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001017 List<String> ratings = new ArrayList<>();
Jae Seo783645e2014-07-28 17:30:50 +09001018 for (TvContentRating rating
1019 : userState.persistentDataStore.getBlockedRatings()) {
1020 ratings.add(rating.flattenToString());
1021 }
1022 return ratings;
1023 }
1024 } finally {
1025 Binder.restoreCallingIdentity(identity);
1026 }
1027 }
1028
1029 @Override
1030 public void addBlockedRating(String rating, int userId) {
1031 ensureParentalControlsPermission();
1032 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1033 Binder.getCallingUid(), userId, "addBlockedRating");
1034 final long identity = Binder.clearCallingIdentity();
1035 try {
1036 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001037 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +09001038 userState.persistentDataStore.addBlockedRating(
1039 TvContentRating.unflattenFromString(rating));
1040 }
1041 } finally {
1042 Binder.restoreCallingIdentity(identity);
1043 }
1044 }
1045
1046 @Override
1047 public void removeBlockedRating(String rating, int userId) {
1048 ensureParentalControlsPermission();
1049 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1050 Binder.getCallingUid(), userId, "removeBlockedRating");
1051 final long identity = Binder.clearCallingIdentity();
1052 try {
1053 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001054 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +09001055 userState.persistentDataStore.removeBlockedRating(
1056 TvContentRating.unflattenFromString(rating));
1057 }
1058 } finally {
1059 Binder.restoreCallingIdentity(identity);
1060 }
1061 }
1062
1063 private void ensureParentalControlsPermission() {
Jae Seofc836f62014-08-27 00:47:56 +00001064 if (mContext.checkCallingPermission(
1065 android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
1066 != PackageManager.PERMISSION_GRANTED) {
1067 throw new SecurityException(
1068 "The caller does not have parental controls permission");
1069 }
Jae Seo783645e2014-07-28 17:30:50 +09001070 }
1071
1072 @Override
Sungsoo Limd6672b52014-04-30 10:43:26 +09001073 public void createSession(final ITvInputClient client, final String inputId,
Jae Seoa826d012016-01-18 13:03:35 -08001074 boolean isRecordingSession, int seq, int userId) {
Jae Seo39570912014-02-20 18:23:25 -08001075 final int callingUid = Binder.getCallingUid();
1076 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1077 userId, "createSession");
1078 final long identity = Binder.clearCallingIdentity();
1079 try {
1080 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001081 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Dongwon Kang426c9a42014-08-26 17:39:21 +09001082 TvInputState inputState = userState.inputMap.get(inputId);
1083 if (inputState == null) {
1084 Slog.w(TAG, "Failed to find input state for inputId=" + inputId);
1085 sendSessionTokenToClientLocked(client, inputId, null, null, seq);
1086 return;
1087 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001088 TvInputInfo info = inputState.info;
Wonsik Kim187423c2014-06-25 14:12:48 +09001089 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
Jae Seo39570912014-02-20 18:23:25 -08001090 if (serviceState == null) {
Wonsik Kim187423c2014-06-25 14:12:48 +09001091 serviceState = new ServiceState(info.getComponent(), resolvedUserId);
1092 userState.serviceStateMap.put(info.getComponent(), serviceState);
Jae Seo39570912014-02-20 18:23:25 -08001093 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001094 // Send a null token immediately while reconnecting.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001095 if (serviceState.reconnecting) {
Jae Seo5c80ad22014-06-12 19:52:58 -07001096 sendSessionTokenToClientLocked(client, inputId, null, null, seq);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001097 return;
1098 }
1099
1100 // Create a new session token and a session state.
1101 IBinder sessionToken = new Binder();
Jae Seo2cdb05e2016-02-04 22:17:13 +09001102 SessionState sessionState = new SessionState(sessionToken, info.getId(),
1103 info.getComponent(), isRecordingSession, client, seq, callingUid,
1104 resolvedUserId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001105
1106 // Add them to the global session state map of the current user.
1107 userState.sessionStateMap.put(sessionToken, sessionState);
1108
1109 // Also, add them to the session state map of the current service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001110 serviceState.sessionTokens.add(sessionToken);
Jae Seo39570912014-02-20 18:23:25 -08001111
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001112 if (serviceState.service != null) {
1113 createSessionInternalLocked(serviceState.service, sessionToken,
Sungsoo Lim7de5e232014-04-12 16:51:27 +09001114 resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001115 } else {
Wonsik Kim187423c2014-06-25 14:12:48 +09001116 updateServiceConnectionLocked(info.getComponent(), resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001117 }
1118 }
1119 } finally {
1120 Binder.restoreCallingIdentity(identity);
1121 }
1122 }
1123
1124 @Override
1125 public void releaseSession(IBinder sessionToken, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001126 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -07001127 Slog.d(TAG, "releaseSession(sessionToken=" + sessionToken + ")");
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001128 }
Jae Seo39570912014-02-20 18:23:25 -08001129 final int callingUid = Binder.getCallingUid();
1130 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1131 userId, "releaseSession");
1132 final long identity = Binder.clearCallingIdentity();
1133 try {
1134 synchronized (mLock) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001135 releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001136 }
1137 } finally {
1138 Binder.restoreCallingIdentity(identity);
1139 }
1140 }
1141
1142 @Override
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001143 public void setMainSession(IBinder sessionToken, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001144 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -07001145 Slog.d(TAG, "setMainSession(sessionToken=" + sessionToken + ")");
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001146 }
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001147 final int callingUid = Binder.getCallingUid();
1148 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1149 userId, "setMainSession");
1150 final long identity = Binder.clearCallingIdentity();
1151 try {
1152 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001153 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001154 if (userState.mainSessionToken == sessionToken) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001155 return;
1156 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001157 if (DEBUG) {
1158 Slog.d(TAG, "mainSessionToken=" + sessionToken);
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001159 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001160 IBinder oldMainSessionToken = userState.mainSessionToken;
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001161 userState.mainSessionToken = sessionToken;
1162
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001163 // Inform the new main session first.
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001164 // See {@link TvInputService.Session#onSetMain}.
1165 if (sessionToken != null) {
1166 setMainLocked(sessionToken, true, callingUid, userId);
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001167 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001168 if (oldMainSessionToken != null) {
1169 setMainLocked(oldMainSessionToken, false, Process.SYSTEM_UID, userId);
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001170 }
1171 }
1172 } finally {
1173 Binder.restoreCallingIdentity(identity);
1174 }
1175 }
1176
1177 @Override
Jae Seo39570912014-02-20 18:23:25 -08001178 public void setSurface(IBinder sessionToken, Surface surface, int userId) {
1179 final int callingUid = Binder.getCallingUid();
1180 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1181 userId, "setSurface");
1182 final long identity = Binder.clearCallingIdentity();
1183 try {
1184 synchronized (mLock) {
1185 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001186 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1187 resolvedUserId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001188 if (sessionState.hardwareSessionToken == null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001189 getSessionLocked(sessionState).setSurface(surface);
1190 } else {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001191 getSessionLocked(sessionState.hardwareSessionToken,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001192 Process.SYSTEM_UID, resolvedUserId).setSurface(surface);
1193 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001194 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001195 Slog.e(TAG, "error in setSurface", e);
Jae Seo39570912014-02-20 18:23:25 -08001196 }
1197 }
1198 } finally {
Youngsang Chof8362062014-04-30 17:24:20 +09001199 if (surface != null) {
1200 // surface is not used in TvInputManagerService.
1201 surface.release();
1202 }
Jae Seo39570912014-02-20 18:23:25 -08001203 Binder.restoreCallingIdentity(identity);
1204 }
1205 }
1206
1207 @Override
Youngsang Choe821d712014-07-16 14:22:19 -07001208 public void dispatchSurfaceChanged(IBinder sessionToken, int format, int width,
1209 int height, int userId) {
1210 final int callingUid = Binder.getCallingUid();
1211 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1212 userId, "dispatchSurfaceChanged");
1213 final long identity = Binder.clearCallingIdentity();
1214 try {
1215 synchronized (mLock) {
1216 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001217 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1218 resolvedUserId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001219 getSessionLocked(sessionState).dispatchSurfaceChanged(format, width,
1220 height);
1221 if (sessionState.hardwareSessionToken != null) {
1222 getSessionLocked(sessionState.hardwareSessionToken, Process.SYSTEM_UID,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001223 resolvedUserId).dispatchSurfaceChanged(format, width, height);
1224 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001225 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Choe821d712014-07-16 14:22:19 -07001226 Slog.e(TAG, "error in dispatchSurfaceChanged", e);
1227 }
1228 }
1229 } finally {
1230 Binder.restoreCallingIdentity(identity);
1231 }
1232 }
1233
1234 @Override
Jae Seo39570912014-02-20 18:23:25 -08001235 public void setVolume(IBinder sessionToken, float volume, int userId) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001236 final float REMOTE_VOLUME_ON = 1.0f;
1237 final float REMOTE_VOLUME_OFF = 0f;
Jae Seo39570912014-02-20 18:23:25 -08001238 final int callingUid = Binder.getCallingUid();
1239 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1240 userId, "setVolume");
1241 final long identity = Binder.clearCallingIdentity();
1242 try {
1243 synchronized (mLock) {
1244 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001245 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1246 resolvedUserId);
1247 getSessionLocked(sessionState).setVolume(volume);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001248 if (sessionState.hardwareSessionToken != null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001249 // Here, we let the hardware session know only whether volume is on or
1250 // off to prevent that the volume is controlled in the both side.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001251 getSessionLocked(sessionState.hardwareSessionToken,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001252 Process.SYSTEM_UID, resolvedUserId).setVolume((volume > 0.0f)
1253 ? REMOTE_VOLUME_ON : REMOTE_VOLUME_OFF);
1254 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001255 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001256 Slog.e(TAG, "error in setVolume", e);
Jae Seo39570912014-02-20 18:23:25 -08001257 }
1258 }
1259 } finally {
1260 Binder.restoreCallingIdentity(identity);
1261 }
1262 }
1263
1264 @Override
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +09001265 public void tune(IBinder sessionToken, final Uri channelUri, Bundle params, int userId) {
Jae Seo39570912014-02-20 18:23:25 -08001266 final int callingUid = Binder.getCallingUid();
1267 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1268 userId, "tune");
1269 final long identity = Binder.clearCallingIdentity();
1270 try {
1271 synchronized (mLock) {
Jae Seo39570912014-02-20 18:23:25 -08001272 try {
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +09001273 getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(
1274 channelUri, params);
Jae Seoc22d0c02014-08-15 13:03:21 -07001275 if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
Youngsang Cho008f6d42014-07-22 21:29:47 -07001276 // Do not log the watch history for passthrough inputs.
1277 return;
1278 }
Jae Seo31dc634be2014-04-15 17:40:23 -07001279
Jae Seo4f1a6d42015-07-20 16:15:01 -07001280 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo31dc634be2014-04-15 17:40:23 -07001281 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Jae Seo31dc634be2014-04-15 17:40:23 -07001282
Jae Seo7eb75df2014-08-08 22:20:48 -07001283 // Log the start of watch.
Jae Seo31dc634be2014-04-15 17:40:23 -07001284 SomeArgs args = SomeArgs.obtain();
Jae Seo2cdb05e2016-02-04 22:17:13 +09001285 args.arg1 = sessionState.componentName.getPackageName();
Jae Seo7eb75df2014-08-08 22:20:48 -07001286 args.arg2 = System.currentTimeMillis();
1287 args.arg3 = ContentUris.parseId(channelUri);
1288 args.arg4 = params;
1289 args.arg5 = sessionToken;
1290 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_START, args)
1291 .sendToTarget();
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001292 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001293 Slog.e(TAG, "error in tune", e);
Jae Seo39570912014-02-20 18:23:25 -08001294 }
1295 }
1296 } finally {
1297 Binder.restoreCallingIdentity(identity);
1298 }
1299 }
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001300
1301 @Override
Jae Seoa9033832015-03-11 19:29:46 -07001302 public void unblockContent(
Sungsoo Lim9bf671f2014-07-19 12:59:51 +09001303 IBinder sessionToken, String unblockedRating, int userId) {
Jaewan Kim903d6b72014-07-16 11:28:56 +09001304 final int callingUid = Binder.getCallingUid();
1305 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1306 userId, "unblockContent");
1307 final long identity = Binder.clearCallingIdentity();
1308 try {
1309 synchronized (mLock) {
1310 try {
1311 getSessionLocked(sessionToken, callingUid, resolvedUserId)
Jae Seoa9033832015-03-11 19:29:46 -07001312 .unblockContent(unblockedRating);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001313 } catch (RemoteException | SessionNotFoundException e) {
Jae Seoa9033832015-03-11 19:29:46 -07001314 Slog.e(TAG, "error in unblockContent", e);
Jaewan Kim903d6b72014-07-16 11:28:56 +09001315 }
1316 }
1317 } finally {
1318 Binder.restoreCallingIdentity(identity);
1319 }
1320 }
1321
1322 @Override
Jae Seo2c1c31c2014-07-10 14:57:01 -07001323 public void setCaptionEnabled(IBinder sessionToken, boolean enabled, int userId) {
1324 final int callingUid = Binder.getCallingUid();
1325 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1326 userId, "setCaptionEnabled");
1327 final long identity = Binder.clearCallingIdentity();
1328 try {
1329 synchronized (mLock) {
1330 try {
1331 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1332 .setCaptionEnabled(enabled);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001333 } catch (RemoteException | SessionNotFoundException e) {
Jae Seo2c1c31c2014-07-10 14:57:01 -07001334 Slog.e(TAG, "error in setCaptionEnabled", e);
1335 }
1336 }
1337 } finally {
1338 Binder.restoreCallingIdentity(identity);
1339 }
1340 }
1341
1342 @Override
Jae Seo10d285a2014-07-31 22:46:47 +09001343 public void selectTrack(IBinder sessionToken, int type, String trackId, int userId) {
Dongwon Kang1f213912014-07-02 18:35:08 +09001344 final int callingUid = Binder.getCallingUid();
1345 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1346 userId, "selectTrack");
1347 final long identity = Binder.clearCallingIdentity();
1348 try {
1349 synchronized (mLock) {
1350 try {
1351 getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack(
Jae Seo10d285a2014-07-31 22:46:47 +09001352 type, trackId);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001353 } catch (RemoteException | SessionNotFoundException e) {
Dongwon Kang1f213912014-07-02 18:35:08 +09001354 Slog.e(TAG, "error in selectTrack", e);
1355 }
1356 }
1357 } finally {
1358 Binder.restoreCallingIdentity(identity);
1359 }
1360 }
1361
1362 @Override
Jae Seoa759b112014-07-18 22:16:08 -07001363 public void sendAppPrivateCommand(IBinder sessionToken, String command, Bundle data,
1364 int userId) {
1365 final int callingUid = Binder.getCallingUid();
1366 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1367 userId, "sendAppPrivateCommand");
1368 final long identity = Binder.clearCallingIdentity();
1369 try {
1370 synchronized (mLock) {
1371 try {
1372 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1373 .appPrivateCommand(command, data);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001374 } catch (RemoteException | SessionNotFoundException e) {
Jae Seofea8dd42014-08-26 13:57:41 -07001375 Slog.e(TAG, "error in appPrivateCommand", e);
Jae Seoa759b112014-07-18 22:16:08 -07001376 }
1377 }
1378 } finally {
1379 Binder.restoreCallingIdentity(identity);
1380 }
1381 }
1382
1383 @Override
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001384 public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
1385 int userId) {
1386 final int callingUid = Binder.getCallingUid();
1387 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1388 userId, "createOverlayView");
1389 final long identity = Binder.clearCallingIdentity();
1390 try {
1391 synchronized (mLock) {
1392 try {
1393 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1394 .createOverlayView(windowToken, frame);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001395 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001396 Slog.e(TAG, "error in createOverlayView", e);
1397 }
1398 }
1399 } finally {
1400 Binder.restoreCallingIdentity(identity);
1401 }
1402 }
1403
1404 @Override
1405 public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
1406 final int callingUid = Binder.getCallingUid();
1407 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1408 userId, "relayoutOverlayView");
1409 final long identity = Binder.clearCallingIdentity();
1410 try {
1411 synchronized (mLock) {
1412 try {
1413 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1414 .relayoutOverlayView(frame);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001415 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001416 Slog.e(TAG, "error in relayoutOverlayView", e);
1417 }
1418 }
1419 } finally {
1420 Binder.restoreCallingIdentity(identity);
1421 }
1422 }
1423
1424 @Override
1425 public void removeOverlayView(IBinder sessionToken, int userId) {
1426 final int callingUid = Binder.getCallingUid();
1427 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1428 userId, "removeOverlayView");
1429 final long identity = Binder.clearCallingIdentity();
1430 try {
1431 synchronized (mLock) {
1432 try {
1433 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1434 .removeOverlayView();
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001435 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001436 Slog.e(TAG, "error in removeOverlayView", e);
1437 }
1438 }
1439 } finally {
1440 Binder.restoreCallingIdentity(identity);
1441 }
1442 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001443
1444 @Override
Jae Seoa826d012016-01-18 13:03:35 -08001445 public void timeShiftPlay(IBinder sessionToken, final Uri recordedProgramUri, int userId) {
1446 final int callingUid = Binder.getCallingUid();
1447 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1448 userId, "timeShiftPlay");
1449 final long identity = Binder.clearCallingIdentity();
1450 try {
1451 synchronized (mLock) {
1452 try {
1453 getSessionLocked(sessionToken, callingUid, resolvedUserId).timeShiftPlay(
1454 recordedProgramUri);
1455 } catch (RemoteException | SessionNotFoundException e) {
1456 Slog.e(TAG, "error in timeShiftPlay", e);
1457 }
1458 }
1459 } finally {
1460 Binder.restoreCallingIdentity(identity);
1461 }
1462 }
1463
1464 @Override
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001465 public void timeShiftPause(IBinder sessionToken, int userId) {
1466 final int callingUid = Binder.getCallingUid();
1467 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1468 userId, "timeShiftPause");
1469 final long identity = Binder.clearCallingIdentity();
1470 try {
1471 synchronized (mLock) {
1472 try {
Jae Seoa826d012016-01-18 13:03:35 -08001473 getSessionLocked(sessionToken, callingUid, resolvedUserId).timeShiftPause();
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001474 } catch (RemoteException | SessionNotFoundException e) {
1475 Slog.e(TAG, "error in timeShiftPause", e);
1476 }
1477 }
1478 } finally {
1479 Binder.restoreCallingIdentity(identity);
1480 }
1481 }
1482
1483 @Override
1484 public void timeShiftResume(IBinder sessionToken, int userId) {
1485 final int callingUid = Binder.getCallingUid();
1486 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1487 userId, "timeShiftResume");
1488 final long identity = Binder.clearCallingIdentity();
1489 try {
1490 synchronized (mLock) {
1491 try {
1492 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1493 .timeShiftResume();
1494 } catch (RemoteException | SessionNotFoundException e) {
1495 Slog.e(TAG, "error in timeShiftResume", e);
1496 }
1497 }
1498 } finally {
1499 Binder.restoreCallingIdentity(identity);
1500 }
1501 }
1502
1503 @Override
1504 public void timeShiftSeekTo(IBinder sessionToken, long timeMs, int userId) {
1505 final int callingUid = Binder.getCallingUid();
1506 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1507 userId, "timeShiftSeekTo");
1508 final long identity = Binder.clearCallingIdentity();
1509 try {
1510 synchronized (mLock) {
1511 try {
1512 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1513 .timeShiftSeekTo(timeMs);
1514 } catch (RemoteException | SessionNotFoundException e) {
1515 Slog.e(TAG, "error in timeShiftSeekTo", e);
1516 }
1517 }
1518 } finally {
1519 Binder.restoreCallingIdentity(identity);
1520 }
1521 }
1522
1523 @Override
Jae Seo4b34cc72015-05-15 17:29:39 -07001524 public void timeShiftSetPlaybackParams(IBinder sessionToken, PlaybackParams params,
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001525 int userId) {
1526 final int callingUid = Binder.getCallingUid();
1527 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
Jae Seo4b34cc72015-05-15 17:29:39 -07001528 userId, "timeShiftSetPlaybackParams");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001529 final long identity = Binder.clearCallingIdentity();
1530 try {
1531 synchronized (mLock) {
1532 try {
1533 getSessionLocked(sessionToken, callingUid, resolvedUserId)
Jae Seo4b34cc72015-05-15 17:29:39 -07001534 .timeShiftSetPlaybackParams(params);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001535 } catch (RemoteException | SessionNotFoundException e) {
Jae Seo4b34cc72015-05-15 17:29:39 -07001536 Slog.e(TAG, "error in timeShiftSetPlaybackParams", e);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001537 }
1538 }
1539 } finally {
1540 Binder.restoreCallingIdentity(identity);
1541 }
1542 }
1543
1544 @Override
Jae Seo465f0d62015-04-06 18:40:46 -07001545 public void timeShiftEnablePositionTracking(IBinder sessionToken, boolean enable,
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001546 int userId) {
1547 final int callingUid = Binder.getCallingUid();
1548 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
Jae Seo465f0d62015-04-06 18:40:46 -07001549 userId, "timeShiftEnablePositionTracking");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001550 final long identity = Binder.clearCallingIdentity();
1551 try {
1552 synchronized (mLock) {
1553 try {
1554 getSessionLocked(sessionToken, callingUid, resolvedUserId)
Jae Seo465f0d62015-04-06 18:40:46 -07001555 .timeShiftEnablePositionTracking(enable);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001556 } catch (RemoteException | SessionNotFoundException e) {
Jae Seo465f0d62015-04-06 18:40:46 -07001557 Slog.e(TAG, "error in timeShiftEnablePositionTracking", e);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001558 }
1559 }
1560 } finally {
1561 Binder.restoreCallingIdentity(identity);
1562 }
1563 }
1564
1565 @Override
Jae Seoa826d012016-01-18 13:03:35 -08001566 public void connect(IBinder sessionToken, final Uri channelUri, Bundle params, int userId) {
1567 final int callingUid = Binder.getCallingUid();
1568 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1569 userId, "connect");
1570 final long identity = Binder.clearCallingIdentity();
1571 try {
1572 synchronized (mLock) {
1573 try {
1574 getSessionLocked(sessionToken, callingUid, resolvedUserId).connect(
1575 channelUri, params);
1576 } catch (RemoteException | SessionNotFoundException e) {
1577 Slog.e(TAG, "error in connect", e);
1578 }
1579 }
1580 } finally {
1581 Binder.restoreCallingIdentity(identity);
1582 }
1583 }
1584
1585 @Override
Jae Seo4eee6a72016-02-06 11:11:35 +09001586 public void startRecording(IBinder sessionToken, @Nullable Uri programHint, int userId) {
Jae Seoa826d012016-01-18 13:03:35 -08001587 final int callingUid = Binder.getCallingUid();
1588 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1589 userId, "startRecording");
1590 final long identity = Binder.clearCallingIdentity();
1591 try {
1592 synchronized (mLock) {
1593 try {
Jae Seo4eee6a72016-02-06 11:11:35 +09001594 getSessionLocked(sessionToken, callingUid, resolvedUserId).startRecording(
1595 programHint);
Jae Seoa826d012016-01-18 13:03:35 -08001596 } catch (RemoteException | SessionNotFoundException e) {
1597 Slog.e(TAG, "error in startRecording", e);
1598 }
1599 }
1600 } finally {
1601 Binder.restoreCallingIdentity(identity);
1602 }
1603 }
1604
1605 @Override
1606 public void stopRecording(IBinder sessionToken, int userId) {
1607 final int callingUid = Binder.getCallingUid();
1608 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1609 userId, "stopRecording");
1610 final long identity = Binder.clearCallingIdentity();
1611 try {
1612 synchronized (mLock) {
1613 try {
1614 getSessionLocked(sessionToken, callingUid, resolvedUserId).stopRecording();
1615 } catch (RemoteException | SessionNotFoundException e) {
1616 Slog.e(TAG, "error in stopRecording", e);
1617 }
1618 }
1619 } finally {
1620 Binder.restoreCallingIdentity(identity);
1621 }
1622 }
1623
1624 @Override
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001625 public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
Wonsik Kim969167d2014-06-24 16:33:17 +09001626 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001627 != PackageManager.PERMISSION_GRANTED) {
1628 return null;
1629 }
1630
1631 final long identity = Binder.clearCallingIdentity();
1632 try {
1633 return mTvInputHardwareManager.getHardwareList();
1634 } finally {
1635 Binder.restoreCallingIdentity(identity);
1636 }
1637 }
1638
1639 @Override
1640 public ITvInputHardware acquireTvInputHardware(int deviceId,
Wonsik Kim969167d2014-06-24 16:33:17 +09001641 ITvInputHardwareCallback callback, TvInputInfo info, int userId)
1642 throws RemoteException {
1643 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001644 != PackageManager.PERMISSION_GRANTED) {
1645 return null;
1646 }
1647
1648 final long identity = Binder.clearCallingIdentity();
1649 final int callingUid = Binder.getCallingUid();
1650 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1651 userId, "acquireTvInputHardware");
1652 try {
1653 return mTvInputHardwareManager.acquireHardware(
Wonsik Kim969167d2014-06-24 16:33:17 +09001654 deviceId, callback, info, callingUid, resolvedUserId);
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001655 } finally {
1656 Binder.restoreCallingIdentity(identity);
1657 }
1658 }
1659
1660 @Override
1661 public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
1662 throws RemoteException {
Wonsik Kim969167d2014-06-24 16:33:17 +09001663 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001664 != PackageManager.PERMISSION_GRANTED) {
1665 return;
1666 }
1667
1668 final long identity = Binder.clearCallingIdentity();
1669 final int callingUid = Binder.getCallingUid();
1670 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1671 userId, "releaseTvInputHardware");
1672 try {
1673 mTvInputHardwareManager.releaseHardware(
1674 deviceId, hardware, callingUid, resolvedUserId);
1675 } finally {
1676 Binder.restoreCallingIdentity(identity);
1677 }
1678 }
Jaewan Kime14c3f42014-06-27 13:47:48 +09001679
1680 @Override
Jaesung Chung58739e72015-04-24 19:39:59 +09001681 public List<DvbDeviceInfo> getDvbDeviceList() throws RemoteException {
1682 if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)
1683 != PackageManager.PERMISSION_GRANTED) {
1684 throw new SecurityException("Requires DVB_DEVICE permission");
1685 }
1686
1687 final long identity = Binder.clearCallingIdentity();
1688 try {
1689 ArrayList<DvbDeviceInfo> deviceInfos = new ArrayList<>();
1690 File devDirectory = new File("/dev");
1691 for (String fileName : devDirectory.list()) {
1692 Matcher matcher = sFrontEndDevicePattern.matcher(fileName);
1693 if (matcher.find()) {
1694 int adapterId = Integer.parseInt(matcher.group(1));
1695 int deviceId = Integer.parseInt(matcher.group(2));
1696 deviceInfos.add(new DvbDeviceInfo(adapterId, deviceId));
1697 }
1698 }
1699 return Collections.unmodifiableList(deviceInfos);
1700 } finally {
1701 Binder.restoreCallingIdentity(identity);
1702 }
1703 }
1704
1705 @Override
1706 public ParcelFileDescriptor openDvbDevice(DvbDeviceInfo info, int device)
1707 throws RemoteException {
1708 if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)
1709 != PackageManager.PERMISSION_GRANTED) {
1710 throw new SecurityException("Requires DVB_DEVICE permission");
1711 }
1712
1713 final long identity = Binder.clearCallingIdentity();
1714 try {
1715 String deviceFileName;
1716 switch (device) {
1717 case TvInputManager.DVB_DEVICE_DEMUX:
1718 deviceFileName = String.format("/dev/dvb%d.demux%d", info.getAdapterId(),
1719 info.getDeviceId());
1720 break;
1721 case TvInputManager.DVB_DEVICE_DVR:
1722 deviceFileName = String.format("/dev/dvb%d.dvr%d", info.getAdapterId(),
1723 info.getDeviceId());
1724 break;
1725 case TvInputManager.DVB_DEVICE_FRONTEND:
1726 deviceFileName = String.format("/dev/dvb%d.frontend%d", info.getAdapterId(),
1727 info.getDeviceId());
1728 break;
1729 default:
1730 throw new IllegalArgumentException("Invalid DVB device: " + device);
1731 }
1732 try {
1733 // The DVB frontend device only needs to be opened in read/write mode, which
1734 // allows performing tuning operations. The DVB demux and DVR device are enough
1735 // to be opened in read only mode.
1736 return ParcelFileDescriptor.open(new File(deviceFileName),
1737 TvInputManager.DVB_DEVICE_FRONTEND == device
1738 ? ParcelFileDescriptor.MODE_READ_WRITE
1739 : ParcelFileDescriptor.MODE_READ_ONLY);
1740 } catch (FileNotFoundException e) {
1741 return null;
1742 }
1743 } finally {
1744 Binder.restoreCallingIdentity(identity);
1745 }
1746 }
1747
1748 @Override
Terry Heoc086a3d2014-06-18 14:26:44 +09001749 public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int userId)
1750 throws RemoteException {
1751 if (mContext.checkCallingPermission(
1752 android.Manifest.permission.CAPTURE_TV_INPUT)
1753 != PackageManager.PERMISSION_GRANTED) {
1754 throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1755 }
1756
1757 final long identity = Binder.clearCallingIdentity();
1758 final int callingUid = Binder.getCallingUid();
1759 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1760 userId, "getAvailableTvStreamConfigList");
1761 try {
1762 return mTvInputHardwareManager.getAvailableTvStreamConfigList(
1763 inputId, callingUid, resolvedUserId);
1764 } finally {
1765 Binder.restoreCallingIdentity(identity);
1766 }
1767 }
1768
1769 @Override
1770 public boolean captureFrame(String inputId, Surface surface, TvStreamConfig config,
1771 int userId)
1772 throws RemoteException {
1773 if (mContext.checkCallingPermission(
1774 android.Manifest.permission.CAPTURE_TV_INPUT)
1775 != PackageManager.PERMISSION_GRANTED) {
1776 throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1777 }
1778
1779 final long identity = Binder.clearCallingIdentity();
1780 final int callingUid = Binder.getCallingUid();
1781 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1782 userId, "captureFrame");
1783 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001784 String hardwareInputId = null;
Terry Heo79124a72014-07-21 15:17:17 +09001785 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001786 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001787 if (userState.inputMap.get(inputId) == null) {
Jae Seofea8dd42014-08-26 13:57:41 -07001788 Slog.e(TAG, "input not found for " + inputId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001789 return false;
1790 }
1791 for (SessionState sessionState : userState.sessionStateMap.values()) {
Jae Seo2cdb05e2016-02-04 22:17:13 +09001792 if (sessionState.inputId.equals(inputId)
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001793 && sessionState.hardwareSessionToken != null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001794 hardwareInputId = userState.sessionStateMap.get(
Jae Seo2cdb05e2016-02-04 22:17:13 +09001795 sessionState.hardwareSessionToken).inputId;
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001796 break;
1797 }
1798 }
Terry Heo79124a72014-07-21 15:17:17 +09001799 }
Terry Heoc086a3d2014-06-18 14:26:44 +09001800 return mTvInputHardwareManager.captureFrame(
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001801 (hardwareInputId != null) ? hardwareInputId : inputId,
Terry Heo79124a72014-07-21 15:17:17 +09001802 surface, config, callingUid, resolvedUserId);
Terry Heoc086a3d2014-06-18 14:26:44 +09001803 } finally {
1804 Binder.restoreCallingIdentity(identity);
1805 }
1806 }
1807
1808 @Override
Terry Heodf9f0a32014-08-06 13:53:33 +09001809 public boolean isSingleSessionActive(int userId) throws RemoteException {
1810 final long identity = Binder.clearCallingIdentity();
1811 final int callingUid = Binder.getCallingUid();
1812 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1813 userId, "isSingleSessionActive");
1814 try {
1815 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001816 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Terry Heodf9f0a32014-08-06 13:53:33 +09001817 if (userState.sessionStateMap.size() == 1) {
1818 return true;
Jae Seo93ff14b2015-06-21 14:08:54 -07001819 } else if (userState.sessionStateMap.size() == 2) {
Terry Heodf9f0a32014-08-06 13:53:33 +09001820 SessionState[] sessionStates = userState.sessionStateMap.values().toArray(
Jae Seo93ff14b2015-06-21 14:08:54 -07001821 new SessionState[2]);
Terry Heodf9f0a32014-08-06 13:53:33 +09001822 // Check if there is a wrapper input.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001823 if (sessionStates[0].hardwareSessionToken != null
1824 || sessionStates[1].hardwareSessionToken != null) {
Terry Heodf9f0a32014-08-06 13:53:33 +09001825 return true;
1826 }
1827 }
1828 return false;
1829 }
1830 } finally {
1831 Binder.restoreCallingIdentity(identity);
1832 }
1833 }
1834
1835 @Override
Jae Seo0f8fc342014-07-02 10:47:08 -07001836 @SuppressWarnings("resource")
Jaewan Kime14c3f42014-06-27 13:47:48 +09001837 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1838 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
Jae Seo0f8fc342014-07-02 10:47:08 -07001839 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
Jaewan Kime14c3f42014-06-27 13:47:48 +09001840 != PackageManager.PERMISSION_GRANTED) {
Jae Seo0f8fc342014-07-02 10:47:08 -07001841 pw.println("Permission Denial: can't dump TvInputManager from pid="
1842 + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
Jaewan Kime14c3f42014-06-27 13:47:48 +09001843 return;
1844 }
1845
1846 synchronized (mLock) {
1847 pw.println("User Ids (Current user: " + mCurrentUserId + "):");
1848 pw.increaseIndent();
1849 for (int i = 0; i < mUserStates.size(); i++) {
1850 int userId = mUserStates.keyAt(i);
1851 pw.println(Integer.valueOf(userId));
1852 }
1853 pw.decreaseIndent();
1854
1855 for (int i = 0; i < mUserStates.size(); i++) {
1856 int userId = mUserStates.keyAt(i);
Jae Seo4f1a6d42015-07-20 16:15:01 -07001857 UserState userState = getOrCreateUserStateLocked(userId);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001858 pw.println("UserState (" + userId + "):");
1859 pw.increaseIndent();
1860
Wonsik Kim969167d2014-06-24 16:33:17 +09001861 pw.println("inputMap: inputId -> TvInputState");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001862 pw.increaseIndent();
Jaewan Kim8e6b51b2014-07-15 13:01:57 +09001863 for (Map.Entry<String, TvInputState> entry: userState.inputMap.entrySet()) {
1864 pw.println(entry.getKey() + ": " + entry.getValue());
Jaewan Kime14c3f42014-06-27 13:47:48 +09001865 }
1866 pw.decreaseIndent();
1867
Wonsik Kim969167d2014-06-24 16:33:17 +09001868 pw.println("packageSet:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001869 pw.increaseIndent();
Wonsik Kim969167d2014-06-24 16:33:17 +09001870 for (String packageName : userState.packageSet) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09001871 pw.println(packageName);
1872 }
1873 pw.decreaseIndent();
1874
1875 pw.println("clientStateMap: ITvInputClient -> ClientState");
1876 pw.increaseIndent();
1877 for (Map.Entry<IBinder, ClientState> entry :
1878 userState.clientStateMap.entrySet()) {
1879 ClientState client = entry.getValue();
1880 pw.println(entry.getKey() + ": " + client);
1881
1882 pw.increaseIndent();
1883
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001884 pw.println("sessionTokens:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001885 pw.increaseIndent();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001886 for (IBinder token : client.sessionTokens) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09001887 pw.println("" + token);
1888 }
1889 pw.decreaseIndent();
1890
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001891 pw.println("clientTokens: " + client.clientToken);
1892 pw.println("userId: " + client.userId);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001893
1894 pw.decreaseIndent();
1895 }
1896 pw.decreaseIndent();
1897
Wonsik Kim187423c2014-06-25 14:12:48 +09001898 pw.println("serviceStateMap: ComponentName -> ServiceState");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001899 pw.increaseIndent();
Wonsik Kim187423c2014-06-25 14:12:48 +09001900 for (Map.Entry<ComponentName, ServiceState> entry :
Jaewan Kime14c3f42014-06-27 13:47:48 +09001901 userState.serviceStateMap.entrySet()) {
1902 ServiceState service = entry.getValue();
1903 pw.println(entry.getKey() + ": " + service);
1904
1905 pw.increaseIndent();
1906
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001907 pw.println("sessionTokens:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001908 pw.increaseIndent();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001909 for (IBinder token : service.sessionTokens) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09001910 pw.println("" + token);
1911 }
1912 pw.decreaseIndent();
1913
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001914 pw.println("service: " + service.service);
1915 pw.println("callback: " + service.callback);
1916 pw.println("bound: " + service.bound);
1917 pw.println("reconnecting: " + service.reconnecting);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001918
1919 pw.decreaseIndent();
1920 }
1921 pw.decreaseIndent();
1922
1923 pw.println("sessionStateMap: ITvInputSession -> SessionState");
1924 pw.increaseIndent();
1925 for (Map.Entry<IBinder, SessionState> entry :
1926 userState.sessionStateMap.entrySet()) {
1927 SessionState session = entry.getValue();
1928 pw.println(entry.getKey() + ": " + session);
1929
1930 pw.increaseIndent();
Jae Seo2cdb05e2016-02-04 22:17:13 +09001931 pw.println("inputId: " + session.inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001932 pw.println("client: " + session.client);
1933 pw.println("seq: " + session.seq);
1934 pw.println("callingUid: " + session.callingUid);
1935 pw.println("userId: " + session.userId);
1936 pw.println("sessionToken: " + session.sessionToken);
1937 pw.println("session: " + session.session);
1938 pw.println("logUri: " + session.logUri);
1939 pw.println("hardwareSessionToken: " + session.hardwareSessionToken);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001940 pw.decreaseIndent();
1941 }
1942 pw.decreaseIndent();
1943
Wonsik Kim969167d2014-06-24 16:33:17 +09001944 pw.println("callbackSet:");
1945 pw.increaseIndent();
1946 for (ITvInputManagerCallback callback : userState.callbackSet) {
1947 pw.println(callback.toString());
1948 }
1949 pw.decreaseIndent();
1950
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001951 pw.println("mainSessionToken: " + userState.mainSessionToken);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001952 pw.decreaseIndent();
1953 }
1954 }
1955 }
Jae Seo39570912014-02-20 18:23:25 -08001956 }
1957
Wonsik Kim969167d2014-06-24 16:33:17 +09001958 private static final class UserState {
1959 // A mapping from the TV input id to its TvInputState.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001960 private Map<String, TvInputState> inputMap = new HashMap<>();
Wonsik Kim969167d2014-06-24 16:33:17 +09001961
1962 // A set of all TV input packages.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001963 private final Set<String> packageSet = new HashSet<>();
Jae Seo5c80ad22014-06-12 19:52:58 -07001964
Jae Seo9c165d62014-08-25 14:39:26 -07001965 // A list of all TV content rating systems defined.
1966 private final List<TvContentRatingSystemInfo>
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001967 contentRatingSystemList = new ArrayList<>();
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +09001968
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001969 // A mapping from the token of a client to its state.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001970 private final Map<IBinder, ClientState> clientStateMap = new HashMap<>();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001971
Jae Seo39570912014-02-20 18:23:25 -08001972 // A mapping from the name of a TV input service to its state.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001973 private final Map<ComponentName, ServiceState> serviceStateMap = new HashMap<>();
Jae Seo39570912014-02-20 18:23:25 -08001974
1975 // A mapping from the token of a TV input session to its state.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001976 private final Map<IBinder, SessionState> sessionStateMap = new HashMap<>();
Wonsik Kim969167d2014-06-24 16:33:17 +09001977
1978 // A set of callbacks.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001979 private final Set<ITvInputManagerCallback> callbackSet = new HashSet<>();
Terry Heo79124a72014-07-21 15:17:17 +09001980
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001981 // The token of a "main" TV input session.
1982 private IBinder mainSessionToken = null;
Jae Seo783645e2014-07-28 17:30:50 +09001983
1984 // Persistent data store for all internal settings maintained by the TV input manager
1985 // service.
1986 private final PersistentDataStore persistentDataStore;
1987
1988 private UserState(Context context, int userId) {
1989 persistentDataStore = new PersistentDataStore(context, userId);
1990 }
Jae Seo39570912014-02-20 18:23:25 -08001991 }
1992
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001993 private final class ClientState implements IBinder.DeathRecipient {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001994 private final List<IBinder> sessionTokens = new ArrayList<>();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001995
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001996 private IBinder clientToken;
1997 private final int userId;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001998
1999 ClientState(IBinder clientToken, int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002000 this.clientToken = clientToken;
2001 this.userId = userId;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002002 }
2003
2004 public boolean isEmpty() {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002005 return sessionTokens.isEmpty();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002006 }
2007
2008 @Override
2009 public void binderDied() {
2010 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002011 UserState userState = getOrCreateUserStateLocked(userId);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002012 // DO NOT remove the client state of clientStateMap in this method. It will be
Ji-Hwan Leea65118e2014-07-24 16:30:02 +09002013 // removed in releaseSessionLocked().
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002014 ClientState clientState = userState.clientStateMap.get(clientToken);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002015 if (clientState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002016 while (clientState.sessionTokens.size() > 0) {
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002017 releaseSessionLocked(
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002018 clientState.sessionTokens.get(0), Process.SYSTEM_UID, userId);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002019 }
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002020 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002021 clientToken = null;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002022 }
2023 }
2024 }
2025
Jae Seo39570912014-02-20 18:23:25 -08002026 private final class ServiceState {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002027 private final List<IBinder> sessionTokens = new ArrayList<>();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002028 private final ServiceConnection connection;
2029 private final ComponentName component;
2030 private final boolean isHardware;
Jae Seo1abbbcd2016-01-28 22:20:41 -08002031 private final List<TvInputInfo> hardwareInputList = new ArrayList<>();
Jae Seo39570912014-02-20 18:23:25 -08002032
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002033 private ITvInputService service;
2034 private ServiceCallback callback;
2035 private boolean bound;
2036 private boolean reconnecting;
Jae Seo39570912014-02-20 18:23:25 -08002037
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002038 private ServiceState(ComponentName component, int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002039 this.component = component;
2040 this.connection = new InputServiceConnection(component, userId);
2041 this.isHardware = hasHardwarePermission(mContext.getPackageManager(), component);
2042 }
2043 }
2044
2045 private static final class TvInputState {
2046 // A TvInputInfo object which represents the TV input.
2047 private TvInputInfo info;
2048
2049 // The state of TV input. Connected by default.
2050 private int state = INPUT_STATE_CONNECTED;
2051
2052 @Override
2053 public String toString() {
2054 return "info: " + info + "; state: " + state;
Jae Seo39570912014-02-20 18:23:25 -08002055 }
2056 }
2057
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002058 private final class SessionState implements IBinder.DeathRecipient {
Jae Seo2cdb05e2016-02-04 22:17:13 +09002059 private final String inputId;
2060 private final ComponentName componentName;
Jae Seoa826d012016-01-18 13:03:35 -08002061 private final boolean isRecordingSession;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002062 private final ITvInputClient client;
2063 private final int seq;
2064 private final int callingUid;
2065 private final int userId;
2066 private final IBinder sessionToken;
2067 private ITvInputSession session;
2068 private Uri logUri;
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09002069 // Not null if this session represents an external device connected to a hardware TV input.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002070 private IBinder hardwareSessionToken;
Jae Seo39570912014-02-20 18:23:25 -08002071
Jae Seo2cdb05e2016-02-04 22:17:13 +09002072 private SessionState(IBinder sessionToken, String inputId, ComponentName componentName,
2073 boolean isRecordingSession, ITvInputClient client, int seq, int callingUid,
2074 int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002075 this.sessionToken = sessionToken;
Jae Seo2cdb05e2016-02-04 22:17:13 +09002076 this.inputId = inputId;
2077 this.componentName = componentName;
Jae Seoa826d012016-01-18 13:03:35 -08002078 this.isRecordingSession = isRecordingSession;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002079 this.client = client;
2080 this.seq = seq;
2081 this.callingUid = callingUid;
2082 this.userId = userId;
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002083 }
2084
2085 @Override
2086 public void binderDied() {
2087 synchronized (mLock) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002088 session = null;
2089 if (client != null) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002090 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002091 client.onSessionReleased(seq);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002092 } catch(RemoteException e) {
2093 Slog.e(TAG, "error in onSessionReleased", e);
2094 }
2095 }
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09002096 // If there are any other sessions based on this session, they should be released.
Jae Seo4f1a6d42015-07-20 16:15:01 -07002097 UserState userState = getOrCreateUserStateLocked(userId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09002098 for (SessionState sessionState : userState.sessionStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002099 if (sessionToken == sessionState.hardwareSessionToken) {
2100 releaseSessionLocked(sessionState.sessionToken, Process.SYSTEM_UID,
2101 userId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09002102 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002103 sessionState.client.onSessionReleased(sessionState.seq);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09002104 } catch (RemoteException e) {
2105 Slog.e(TAG, "error in onSessionReleased", e);
2106 }
2107 }
2108 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002109 removeSessionStateLocked(sessionToken, userId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002110 }
Jae Seo39570912014-02-20 18:23:25 -08002111 }
2112 }
2113
2114 private final class InputServiceConnection implements ServiceConnection {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002115 private final ComponentName mComponent;
Jae Seo39570912014-02-20 18:23:25 -08002116 private final int mUserId;
2117
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002118 private InputServiceConnection(ComponentName component, int userId) {
2119 mComponent = component;
Jae Seo39570912014-02-20 18:23:25 -08002120 mUserId = userId;
2121 }
2122
2123 @Override
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002124 public void onServiceConnected(ComponentName component, IBinder service) {
Jae Seo39570912014-02-20 18:23:25 -08002125 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002126 Slog.d(TAG, "onServiceConnected(component=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -08002127 }
2128 synchronized (mLock) {
Dongwon Kang81e3c3e2015-09-11 15:24:25 -07002129 UserState userState = mUserStates.get(mUserId);
2130 if (userState == null) {
2131 // The user was removed while connecting.
2132 mContext.unbindService(this);
2133 return;
2134 }
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002135 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002136 serviceState.service = ITvInputService.Stub.asInterface(service);
Jae Seo39570912014-02-20 18:23:25 -08002137
2138 // Register a callback, if we need to.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002139 if (serviceState.isHardware && serviceState.callback == null) {
2140 serviceState.callback = new ServiceCallback(mComponent, mUserId);
Jae Seo39570912014-02-20 18:23:25 -08002141 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002142 serviceState.service.registerCallback(serviceState.callback);
Jae Seo39570912014-02-20 18:23:25 -08002143 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09002144 Slog.e(TAG, "error in registerCallback", e);
Jae Seo39570912014-02-20 18:23:25 -08002145 }
2146 }
2147
2148 // And create sessions, if any.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002149 for (IBinder sessionToken : serviceState.sessionTokens) {
2150 createSessionInternalLocked(serviceState.service, sessionToken, mUserId);
Jae Seo39570912014-02-20 18:23:25 -08002151 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002152
Wonsik Kim187423c2014-06-25 14:12:48 +09002153 for (TvInputState inputState : userState.inputMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002154 if (inputState.info.getComponent().equals(component)
Jae Seo82fce642015-04-20 15:37:50 -07002155 && inputState.state != INPUT_STATE_CONNECTED) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002156 notifyInputStateChangedLocked(userState, inputState.info.getId(),
2157 inputState.state, null);
Wonsik Kim187423c2014-06-25 14:12:48 +09002158 }
2159 }
2160
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002161 if (serviceState.isHardware) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002162 List<TvInputHardwareInfo> hardwareInfoList =
2163 mTvInputHardwareManager.getHardwareList();
Wonsik Kim187423c2014-06-25 14:12:48 +09002164 for (TvInputHardwareInfo hardwareInfo : hardwareInfoList) {
2165 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002166 serviceState.service.notifyHardwareAdded(hardwareInfo);
Wonsik Kim187423c2014-06-25 14:12:48 +09002167 } catch (RemoteException e) {
2168 Slog.e(TAG, "error in notifyHardwareAdded", e);
2169 }
2170 }
2171
Jae Seo546c6352014-08-07 11:57:01 -07002172 List<HdmiDeviceInfo> deviceInfoList =
2173 mTvInputHardwareManager.getHdmiDeviceList();
2174 for (HdmiDeviceInfo deviceInfo : deviceInfoList) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002175 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002176 serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002177 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002178 Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002179 }
2180 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002181 }
Jae Seo39570912014-02-20 18:23:25 -08002182 }
2183 }
2184
2185 @Override
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002186 public void onServiceDisconnected(ComponentName component) {
Jae Seo39570912014-02-20 18:23:25 -08002187 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002188 Slog.d(TAG, "onServiceDisconnected(component=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -08002189 }
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002190 if (!mComponent.equals(component)) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002191 throw new IllegalArgumentException("Mismatched ComponentName: "
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002192 + mComponent + " (expected), " + component + " (actual).");
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002193 }
2194 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002195 UserState userState = getOrCreateUserStateLocked(mUserId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002196 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002197 if (serviceState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002198 serviceState.reconnecting = true;
2199 serviceState.bound = false;
2200 serviceState.service = null;
2201 serviceState.callback = null;
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002202
Dongwon Kang426c9a42014-08-26 17:39:21 +09002203 abortPendingCreateSessionRequestsLocked(serviceState, null, mUserId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002204 }
2205 }
Jae Seo39570912014-02-20 18:23:25 -08002206 }
2207 }
2208
2209 private final class ServiceCallback extends ITvInputServiceCallback.Stub {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002210 private final ComponentName mComponent;
Jae Seo39570912014-02-20 18:23:25 -08002211 private final int mUserId;
2212
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002213 ServiceCallback(ComponentName component, int userId) {
2214 mComponent = component;
Jae Seo39570912014-02-20 18:23:25 -08002215 mUserId = userId;
2216 }
2217
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002218 private void ensureHardwarePermission() {
2219 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
2220 != PackageManager.PERMISSION_GRANTED) {
2221 throw new SecurityException("The caller does not have hardware permission");
2222 }
2223 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002224
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002225 private void ensureValidInput(TvInputInfo inputInfo) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002226 if (inputInfo.getId() == null || !mComponent.equals(inputInfo.getComponent())) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002227 throw new IllegalArgumentException("Invalid TvInputInfo");
2228 }
2229 }
2230
Jae Seo1abbbcd2016-01-28 22:20:41 -08002231 private void addHardwareInputLocked(TvInputInfo inputInfo) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002232 ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
Jae Seo1abbbcd2016-01-28 22:20:41 -08002233 serviceState.hardwareInputList.add(inputInfo);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +09002234 buildTvInputListLocked(mUserId, null);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002235 }
2236
Jae Seo1abbbcd2016-01-28 22:20:41 -08002237 public void addHardwareInput(int deviceId, TvInputInfo inputInfo) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002238 ensureHardwarePermission();
2239 ensureValidInput(inputInfo);
2240 synchronized (mLock) {
Jae Seo1abbbcd2016-01-28 22:20:41 -08002241 mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo);
2242 addHardwareInputLocked(inputInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002243 }
2244 }
2245
Jae Seo1abbbcd2016-01-28 22:20:41 -08002246 public void addHdmiInput(int id, TvInputInfo inputInfo) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002247 ensureHardwarePermission();
2248 ensureValidInput(inputInfo);
2249 synchronized (mLock) {
Jae Seo1abbbcd2016-01-28 22:20:41 -08002250 mTvInputHardwareManager.addHdmiInput(id, inputInfo);
2251 addHardwareInputLocked(inputInfo);
Wonsik Kim187423c2014-06-25 14:12:48 +09002252 }
2253 }
2254
Jae Seo1abbbcd2016-01-28 22:20:41 -08002255 public void removeHardwareInput(String inputId) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002256 ensureHardwarePermission();
Wonsik Kim187423c2014-06-25 14:12:48 +09002257 synchronized (mLock) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002258 ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002259 boolean removed = false;
Jae Seo1abbbcd2016-01-28 22:20:41 -08002260 for (Iterator<TvInputInfo> it = serviceState.hardwareInputList.iterator();
Wonsik Kim187423c2014-06-25 14:12:48 +09002261 it.hasNext(); ) {
2262 if (it.next().getId().equals(inputId)) {
2263 it.remove();
2264 removed = true;
2265 break;
2266 }
2267 }
2268 if (removed) {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +09002269 buildTvInputListLocked(mUserId, null);
Jae Seo1abbbcd2016-01-28 22:20:41 -08002270 mTvInputHardwareManager.removeHardwareInput(inputId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002271 } else {
Jae Seofea8dd42014-08-26 13:57:41 -07002272 Slog.e(TAG, "failed to remove input " + inputId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002273 }
Jae Seo39570912014-02-20 18:23:25 -08002274 }
2275 }
2276 }
Jae Seo31dc634be2014-04-15 17:40:23 -07002277
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002278 private final class SessionCallback extends ITvInputSessionCallback.Stub {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002279 private final SessionState mSessionState;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002280 private final InputChannel[] mChannels;
2281
2282 SessionCallback(SessionState sessionState, InputChannel[] channels) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002283 mSessionState = sessionState;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002284 mChannels = channels;
2285 }
2286
2287 @Override
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002288 public void onSessionCreated(ITvInputSession session, IBinder hardwareSessionToken) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002289 if (DEBUG) {
Jae Seo2cdb05e2016-02-04 22:17:13 +09002290 Slog.d(TAG, "onSessionCreated(inputId=" + mSessionState.inputId + ")");
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002291 }
2292 synchronized (mLock) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002293 mSessionState.session = session;
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002294 mSessionState.hardwareSessionToken = hardwareSessionToken;
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002295 if (session != null && addSessionTokenToClientStateLocked(session)) {
2296 sendSessionTokenToClientLocked(mSessionState.client,
Jae Seo2cdb05e2016-02-04 22:17:13 +09002297 mSessionState.inputId, mSessionState.sessionToken, mChannels[0],
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002298 mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002299 } else {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002300 removeSessionStateLocked(mSessionState.sessionToken, mSessionState.userId);
2301 sendSessionTokenToClientLocked(mSessionState.client,
Jae Seo2cdb05e2016-02-04 22:17:13 +09002302 mSessionState.inputId, null, null, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002303 }
2304 mChannels[0].dispose();
2305 }
2306 }
2307
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002308 private boolean addSessionTokenToClientStateLocked(ITvInputSession session) {
2309 try {
2310 session.asBinder().linkToDeath(mSessionState, 0);
2311 } catch (RemoteException e) {
2312 Slog.e(TAG, "session process has already died", e);
2313 return false;
2314 }
2315
2316 IBinder clientToken = mSessionState.client.asBinder();
Jae Seo4f1a6d42015-07-20 16:15:01 -07002317 UserState userState = getOrCreateUserStateLocked(mSessionState.userId);
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002318 ClientState clientState = userState.clientStateMap.get(clientToken);
2319 if (clientState == null) {
2320 clientState = new ClientState(clientToken, mSessionState.userId);
2321 try {
2322 clientToken.linkToDeath(clientState, 0);
2323 } catch (RemoteException e) {
2324 Slog.e(TAG, "client process has already died", e);
2325 return false;
2326 }
2327 userState.clientStateMap.put(clientToken, clientState);
2328 }
2329 clientState.sessionTokens.add(mSessionState.sessionToken);
2330 return true;
2331 }
2332
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002333 @Override
2334 public void onChannelRetuned(Uri channelUri) {
2335 synchronized (mLock) {
2336 if (DEBUG) {
2337 Slog.d(TAG, "onChannelRetuned(" + channelUri + ")");
2338 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002339 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002340 return;
2341 }
2342 try {
2343 // TODO: Consider adding this channel change in the watch log. When we do
2344 // that, how we can protect the watch log from malicious tv inputs should
2345 // be addressed. e.g. add a field which represents where the channel change
2346 // originated from.
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002347 mSessionState.client.onChannelRetuned(channelUri, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002348 } catch (RemoteException e) {
2349 Slog.e(TAG, "error in onChannelRetuned", e);
2350 }
2351 }
2352 }
2353
2354 @Override
2355 public void onTracksChanged(List<TvTrackInfo> tracks) {
2356 synchronized (mLock) {
2357 if (DEBUG) {
2358 Slog.d(TAG, "onTracksChanged(" + tracks + ")");
2359 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002360 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002361 return;
2362 }
2363 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002364 mSessionState.client.onTracksChanged(tracks, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002365 } catch (RemoteException e) {
2366 Slog.e(TAG, "error in onTracksChanged", e);
2367 }
2368 }
2369 }
2370
2371 @Override
2372 public void onTrackSelected(int type, String trackId) {
2373 synchronized (mLock) {
2374 if (DEBUG) {
2375 Slog.d(TAG, "onTrackSelected(type=" + type + ", trackId=" + trackId + ")");
2376 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002377 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002378 return;
2379 }
2380 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002381 mSessionState.client.onTrackSelected(type, trackId, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002382 } catch (RemoteException e) {
2383 Slog.e(TAG, "error in onTrackSelected", e);
2384 }
2385 }
2386 }
2387
2388 @Override
2389 public void onVideoAvailable() {
2390 synchronized (mLock) {
2391 if (DEBUG) {
2392 Slog.d(TAG, "onVideoAvailable()");
2393 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002394 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002395 return;
2396 }
2397 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002398 mSessionState.client.onVideoAvailable(mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002399 } catch (RemoteException e) {
2400 Slog.e(TAG, "error in onVideoAvailable", e);
2401 }
2402 }
2403 }
2404
2405 @Override
2406 public void onVideoUnavailable(int reason) {
2407 synchronized (mLock) {
2408 if (DEBUG) {
2409 Slog.d(TAG, "onVideoUnavailable(" + reason + ")");
2410 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002411 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002412 return;
2413 }
2414 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002415 mSessionState.client.onVideoUnavailable(reason, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002416 } catch (RemoteException e) {
2417 Slog.e(TAG, "error in onVideoUnavailable", e);
2418 }
2419 }
2420 }
2421
2422 @Override
2423 public void onContentAllowed() {
2424 synchronized (mLock) {
2425 if (DEBUG) {
2426 Slog.d(TAG, "onContentAllowed()");
2427 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002428 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002429 return;
2430 }
2431 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002432 mSessionState.client.onContentAllowed(mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002433 } catch (RemoteException e) {
2434 Slog.e(TAG, "error in onContentAllowed", e);
2435 }
2436 }
2437 }
2438
2439 @Override
2440 public void onContentBlocked(String rating) {
2441 synchronized (mLock) {
2442 if (DEBUG) {
2443 Slog.d(TAG, "onContentBlocked()");
2444 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002445 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002446 return;
2447 }
2448 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002449 mSessionState.client.onContentBlocked(rating, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002450 } catch (RemoteException e) {
2451 Slog.e(TAG, "error in onContentBlocked", e);
2452 }
2453 }
2454 }
2455
2456 @Override
2457 public void onLayoutSurface(int left, int top, int right, int bottom) {
2458 synchronized (mLock) {
2459 if (DEBUG) {
2460 Slog.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top
2461 + ", right=" + right + ", bottom=" + bottom + ",)");
2462 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002463 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002464 return;
2465 }
2466 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002467 mSessionState.client.onLayoutSurface(left, top, right, bottom,
2468 mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002469 } catch (RemoteException e) {
2470 Slog.e(TAG, "error in onLayoutSurface", e);
2471 }
2472 }
2473 }
2474
2475 @Override
2476 public void onSessionEvent(String eventType, Bundle eventArgs) {
2477 synchronized (mLock) {
2478 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002479 Slog.d(TAG, "onEvent(eventType=" + eventType + ", eventArgs=" + eventArgs
2480 + ")");
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002481 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002482 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002483 return;
2484 }
2485 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002486 mSessionState.client.onSessionEvent(eventType, eventArgs, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002487 } catch (RemoteException e) {
2488 Slog.e(TAG, "error in onSessionEvent", e);
2489 }
2490 }
2491 }
Dongwon Kang6f0240c2015-03-31 17:56:36 -07002492
2493 @Override
2494 public void onTimeShiftStatusChanged(int status) {
2495 synchronized (mLock) {
2496 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002497 Slog.d(TAG, "onTimeShiftStatusChanged(status=" + status + ")");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07002498 }
2499 if (mSessionState.session == null || mSessionState.client == null) {
2500 return;
2501 }
2502 try {
2503 mSessionState.client.onTimeShiftStatusChanged(status, mSessionState.seq);
2504 } catch (RemoteException e) {
2505 Slog.e(TAG, "error in onTimeShiftStatusChanged", e);
2506 }
2507 }
2508 }
2509
2510 @Override
2511 public void onTimeShiftStartPositionChanged(long timeMs) {
2512 synchronized (mLock) {
2513 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002514 Slog.d(TAG, "onTimeShiftStartPositionChanged(timeMs=" + timeMs + ")");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07002515 }
2516 if (mSessionState.session == null || mSessionState.client == null) {
2517 return;
2518 }
2519 try {
2520 mSessionState.client.onTimeShiftStartPositionChanged(timeMs, mSessionState.seq);
2521 } catch (RemoteException e) {
2522 Slog.e(TAG, "error in onTimeShiftStartPositionChanged", e);
2523 }
2524 }
2525 }
2526
2527 @Override
2528 public void onTimeShiftCurrentPositionChanged(long timeMs) {
2529 synchronized (mLock) {
2530 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002531 Slog.d(TAG, "onTimeShiftCurrentPositionChanged(timeMs=" + timeMs + ")");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07002532 }
2533 if (mSessionState.session == null || mSessionState.client == null) {
2534 return;
2535 }
2536 try {
2537 mSessionState.client.onTimeShiftCurrentPositionChanged(timeMs,
2538 mSessionState.seq);
2539 } catch (RemoteException e) {
2540 Slog.e(TAG, "error in onTimeShiftCurrentPositionChanged", e);
2541 }
2542 }
2543 }
Jae Seoa826d012016-01-18 13:03:35 -08002544
2545 // For the recording session only
2546 @Override
2547 public void onConnected() {
2548 synchronized (mLock) {
2549 if (DEBUG) {
2550 Slog.d(TAG, "onConnected()");
2551 }
2552 if (mSessionState.session == null || mSessionState.client == null) {
2553 return;
2554 }
2555 try {
2556 mSessionState.client.onConnected(mSessionState.seq);
2557 } catch (RemoteException e) {
2558 Slog.e(TAG, "error in onConnected", e);
2559 }
2560 }
2561 }
2562
2563 // For the recording session only
2564 @Override
2565 public void onRecordingStarted() {
2566 synchronized (mLock) {
2567 if (DEBUG) {
2568 Slog.d(TAG, "onRecordingStarted()");
2569 }
2570 if (mSessionState.session == null || mSessionState.client == null) {
2571 return;
2572 }
2573 try {
2574 mSessionState.client.onRecordingStarted(mSessionState.seq);
2575 } catch (RemoteException e) {
2576 Slog.e(TAG, "error in onRecordingStarted", e);
2577 }
2578 }
2579 }
2580
2581 // For the recording session only
2582 @Override
2583 public void onRecordingStopped(Uri recordedProgramUri) {
2584 synchronized (mLock) {
2585 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002586 Slog.d(TAG, "onRecordingStopped(recordedProgramUri=" + recordedProgramUri
2587 + ")");
Jae Seoa826d012016-01-18 13:03:35 -08002588 }
2589 if (mSessionState.session == null || mSessionState.client == null) {
2590 return;
2591 }
2592 try {
2593 mSessionState.client.onRecordingStopped(recordedProgramUri, mSessionState.seq);
2594 } catch (RemoteException e) {
2595 Slog.e(TAG, "error in onRecordingStopped", e);
2596 }
2597 }
2598 }
2599
2600 // For the recording session only
2601 @Override
2602 public void onError(int error) {
2603 synchronized (mLock) {
2604 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002605 Slog.d(TAG, "onError(error=" + error + ")");
Jae Seoa826d012016-01-18 13:03:35 -08002606 }
2607 if (mSessionState.session == null || mSessionState.client == null) {
2608 return;
2609 }
2610 try {
2611 mSessionState.client.onError(error, mSessionState.seq);
2612 } catch (RemoteException e) {
2613 Slog.e(TAG, "error in onError", e);
2614 }
2615 }
2616 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002617 }
2618
2619 private static final class WatchLogHandler extends Handler {
Jae Seo7eb75df2014-08-08 22:20:48 -07002620 // There are only two kinds of watch events that can happen on the system:
2621 // 1. The current TV input session is tuned to a new channel.
2622 // 2. The session is released for some reason.
2623 // The former indicates the end of the previous log entry, if any, followed by the start of
2624 // a new entry. The latter indicates the end of the most recent entry for the given session.
2625 // Here the system supplies the database the smallest set of information only that is
2626 // sufficient to consolidate the log entries while minimizing database operations in the
2627 // system service.
Jae Seo8c375fe2015-06-23 20:33:25 -07002628 static final int MSG_LOG_WATCH_START = 1;
2629 static final int MSG_LOG_WATCH_END = 2;
2630 static final int MSG_SWITCH_CONTENT_RESOLVER = 3;
Jae Seo31dc634be2014-04-15 17:40:23 -07002631
Jae Seo8c375fe2015-06-23 20:33:25 -07002632 private ContentResolver mContentResolver;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002633
Jae Seo8c375fe2015-06-23 20:33:25 -07002634 WatchLogHandler(ContentResolver contentResolver, Looper looper) {
Jae Seo31dc634be2014-04-15 17:40:23 -07002635 super(looper);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002636 mContentResolver = contentResolver;
Jae Seo31dc634be2014-04-15 17:40:23 -07002637 }
2638
2639 @Override
2640 public void handleMessage(Message msg) {
2641 switch (msg.what) {
Jae Seo7eb75df2014-08-08 22:20:48 -07002642 case MSG_LOG_WATCH_START: {
Jae Seo31dc634be2014-04-15 17:40:23 -07002643 SomeArgs args = (SomeArgs) msg.obj;
Jae Seo7eb75df2014-08-08 22:20:48 -07002644 String packageName = (String) args.arg1;
2645 long watchStartTime = (long) args.arg2;
2646 long channelId = (long) args.arg3;
2647 Bundle tuneParams = (Bundle) args.arg4;
2648 IBinder sessionToken = (IBinder) args.arg5;
2649
2650 ContentValues values = new ContentValues();
2651 values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
2652 values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
2653 watchStartTime);
2654 values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
2655 if (tuneParams != null) {
2656 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS,
2657 encodeTuneParams(tuneParams));
2658 }
2659 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
2660 sessionToken.toString());
2661
2662 mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
Jae Seo31dc634be2014-04-15 17:40:23 -07002663 args.recycle();
Jae Seo8c375fe2015-06-23 20:33:25 -07002664 break;
Jae Seo31dc634be2014-04-15 17:40:23 -07002665 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002666 case MSG_LOG_WATCH_END: {
Jae Seo31dc634be2014-04-15 17:40:23 -07002667 SomeArgs args = (SomeArgs) msg.obj;
Jae Seo7eb75df2014-08-08 22:20:48 -07002668 IBinder sessionToken = (IBinder) args.arg1;
2669 long watchEndTime = (long) args.arg2;
2670
2671 ContentValues values = new ContentValues();
2672 values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
2673 watchEndTime);
2674 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
2675 sessionToken.toString());
2676
2677 mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
Jae Seo31dc634be2014-04-15 17:40:23 -07002678 args.recycle();
Jae Seo8c375fe2015-06-23 20:33:25 -07002679 break;
2680 }
2681 case MSG_SWITCH_CONTENT_RESOLVER: {
2682 mContentResolver = (ContentResolver) msg.obj;
2683 break;
Jae Seo31dc634be2014-04-15 17:40:23 -07002684 }
2685 default: {
Jae Seo8c375fe2015-06-23 20:33:25 -07002686 Slog.w(TAG, "unhandled message code: " + msg.what);
2687 break;
Jae Seo31dc634be2014-04-15 17:40:23 -07002688 }
2689 }
2690 }
2691
Jae Seo7eb75df2014-08-08 22:20:48 -07002692 private String encodeTuneParams(Bundle tuneParams) {
2693 StringBuilder builder = new StringBuilder();
2694 Set<String> keySet = tuneParams.keySet();
2695 Iterator<String> it = keySet.iterator();
2696 while (it.hasNext()) {
2697 String key = it.next();
2698 Object value = tuneParams.get(key);
2699 if (value == null) {
2700 continue;
Jae Seo579befe2014-08-06 19:18:37 -07002701 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002702 builder.append(replaceEscapeCharacters(key));
2703 builder.append("=");
2704 builder.append(replaceEscapeCharacters(value.toString()));
2705 if (it.hasNext()) {
2706 builder.append(", ");
Jae Seo31dc634be2014-04-15 17:40:23 -07002707 }
2708 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002709 return builder.toString();
Jae Seo31dc634be2014-04-15 17:40:23 -07002710 }
2711
Jae Seo7eb75df2014-08-08 22:20:48 -07002712 private String replaceEscapeCharacters(String src) {
2713 final char ESCAPE_CHARACTER = '%';
2714 final String ENCODING_TARGET_CHARACTERS = "%=,";
2715 StringBuilder builder = new StringBuilder();
2716 for (char ch : src.toCharArray()) {
2717 if (ENCODING_TARGET_CHARACTERS.indexOf(ch) >= 0) {
2718 builder.append(ESCAPE_CHARACTER);
Chulwoo Lee8d4ded02014-07-10 03:56:39 +09002719 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002720 builder.append(ch);
Jae Seo31dc634be2014-04-15 17:40:23 -07002721 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002722 return builder.toString();
Jae Seo579befe2014-08-06 19:18:37 -07002723 }
Jae Seo31dc634be2014-04-15 17:40:23 -07002724 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002725
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002726 private final class HardwareListener implements TvInputHardwareManager.Listener {
Wonsik Kim187423c2014-06-25 14:12:48 +09002727 @Override
2728 public void onStateChanged(String inputId, int state) {
Wonsik Kim969167d2014-06-24 16:33:17 +09002729 synchronized (mLock) {
2730 setStateLocked(inputId, state, mCurrentUserId);
2731 }
2732 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002733
2734 @Override
2735 public void onHardwareDeviceAdded(TvInputHardwareInfo info) {
2736 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002737 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002738 // Broadcast the event to all hardware inputs.
2739 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002740 if (!serviceState.isHardware || serviceState.service == null) continue;
Wonsik Kim187423c2014-06-25 14:12:48 +09002741 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002742 serviceState.service.notifyHardwareAdded(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09002743 } catch (RemoteException e) {
2744 Slog.e(TAG, "error in notifyHardwareAdded", e);
2745 }
2746 }
2747 }
2748 }
2749
2750 @Override
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002751 public void onHardwareDeviceRemoved(TvInputHardwareInfo info) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002752 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002753 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002754 // Broadcast the event to all hardware inputs.
2755 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002756 if (!serviceState.isHardware || serviceState.service == null) continue;
Wonsik Kim187423c2014-06-25 14:12:48 +09002757 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002758 serviceState.service.notifyHardwareRemoved(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09002759 } catch (RemoteException e) {
2760 Slog.e(TAG, "error in notifyHardwareRemoved", e);
2761 }
2762 }
2763 }
2764 }
2765
2766 @Override
Jae Seo546c6352014-08-07 11:57:01 -07002767 public void onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002768 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002769 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002770 // Broadcast the event to all hardware inputs.
2771 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002772 if (!serviceState.isHardware || serviceState.service == null) continue;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002773 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002774 serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002775 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002776 Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002777 }
2778 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002779 }
2780 }
2781
2782 @Override
Jae Seo546c6352014-08-07 11:57:01 -07002783 public void onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002784 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002785 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002786 // Broadcast the event to all hardware inputs.
2787 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002788 if (!serviceState.isHardware || serviceState.service == null) continue;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002789 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002790 serviceState.service.notifyHdmiDeviceRemoved(deviceInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002791 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002792 Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002793 }
2794 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002795 }
2796 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09002797
2798 @Override
Wonsik Kime92f8572014-08-12 18:30:24 +09002799 public void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo deviceInfo) {
2800 synchronized (mLock) {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002801 Integer state;
Wonsik Kime92f8572014-08-12 18:30:24 +09002802 switch (deviceInfo.getDevicePowerStatus()) {
2803 case HdmiControlManager.POWER_STATUS_ON:
2804 state = INPUT_STATE_CONNECTED;
2805 break;
2806 case HdmiControlManager.POWER_STATUS_STANDBY:
2807 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON:
2808 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY:
2809 state = INPUT_STATE_CONNECTED_STANDBY;
2810 break;
2811 case HdmiControlManager.POWER_STATUS_UNKNOWN:
2812 default:
2813 state = null;
2814 break;
2815 }
2816 if (state != null) {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002817 setStateLocked(inputId, state, mCurrentUserId);
Wonsik Kime92f8572014-08-12 18:30:24 +09002818 }
2819 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09002820 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002821 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09002822
2823 private static class SessionNotFoundException extends IllegalArgumentException {
Dongwon Kangfdce9e52014-12-04 18:08:00 +09002824 public SessionNotFoundException(String name) {
2825 super(name);
2826 }
2827 }
Jae Seo39570912014-02-20 18:23:25 -08002828}