blob: 3e99a4cfe01e2287d331951f896f7eca123e6cb8 [file] [log] [blame]
Jae Seo39570912014-02-20 18:23:25 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.tv;
18
Wonsik Kim969167d2014-06-24 16:33:17 +090019import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
Wonsik Kime92f8572014-08-12 18:30:24 +090020import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
Wonsik Kim969167d2014-06-24 16:33:17 +090021
Jae Seo39570912014-02-20 18:23:25 -080022import android.app.ActivityManager;
23import android.content.BroadcastReceiver;
24import android.content.ComponentName;
Jae Seo5c80ad22014-06-12 19:52:58 -070025import android.content.ContentProviderOperation;
26import android.content.ContentProviderResult;
Jae Seo31dc634be2014-04-15 17:40:23 -070027import android.content.ContentResolver;
28import android.content.ContentUris;
29import android.content.ContentValues;
Jae Seo39570912014-02-20 18:23:25 -080030import android.content.Context;
31import android.content.Intent;
32import android.content.IntentFilter;
Jae Seo5c80ad22014-06-12 19:52:58 -070033import android.content.OperationApplicationException;
Jae Seo39570912014-02-20 18:23:25 -080034import android.content.ServiceConnection;
Jae Seo9c165d62014-08-25 14:39:26 -070035import android.content.pm.ActivityInfo;
Jae Seo39570912014-02-20 18:23:25 -080036import android.content.pm.PackageManager;
Jae Seo8c375fe2015-06-23 20:33:25 -070037import android.content.pm.PackageManager.NameNotFoundException;
Jae Seo39570912014-02-20 18:23:25 -080038import android.content.pm.ResolveInfo;
39import android.content.pm.ServiceInfo;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090040import android.graphics.Rect;
Wonsik Kime92f8572014-08-12 18:30:24 +090041import android.hardware.hdmi.HdmiControlManager;
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090042import android.hardware.hdmi.HdmiDeviceInfo;
Jae Seo4b34cc72015-05-15 17:29:39 -070043import android.media.PlaybackParams;
Jaesung Chung58739e72015-04-24 19:39:59 +090044import android.media.tv.DvbDeviceInfo;
Jae Seod5cc4a22014-05-30 16:57:43 -070045import android.media.tv.ITvInputClient;
46import android.media.tv.ITvInputHardware;
47import android.media.tv.ITvInputHardwareCallback;
48import android.media.tv.ITvInputManager;
Wonsik Kim969167d2014-06-24 16:33:17 +090049import android.media.tv.ITvInputManagerCallback;
Jae Seod5cc4a22014-05-30 16:57:43 -070050import android.media.tv.ITvInputService;
51import android.media.tv.ITvInputServiceCallback;
52import android.media.tv.ITvInputSession;
53import android.media.tv.ITvInputSessionCallback;
Jae Seo783645e2014-07-28 17:30:50 +090054import android.media.tv.TvContentRating;
Jae Seo9c165d62014-08-25 14:39:26 -070055import android.media.tv.TvContentRatingSystemInfo;
Jae Seod5cc4a22014-05-30 16:57:43 -070056import android.media.tv.TvContract;
57import android.media.tv.TvInputHardwareInfo;
58import android.media.tv.TvInputInfo;
Jae Seo9c165d62014-08-25 14:39:26 -070059import android.media.tv.TvInputManager;
Jae Seod5cc4a22014-05-30 16:57:43 -070060import android.media.tv.TvInputService;
Terry Heoc086a3d2014-06-18 14:26:44 +090061import android.media.tv.TvStreamConfig;
Dongwon Kang1f213912014-07-02 18:35:08 +090062import android.media.tv.TvTrackInfo;
Jae Seo39570912014-02-20 18:23:25 -080063import android.net.Uri;
64import android.os.Binder;
Youngsang Cho832860f2014-05-21 20:54:03 +090065import android.os.Bundle;
Jae Seo31dc634be2014-04-15 17:40:23 -070066import android.os.Handler;
Jae Seo39570912014-02-20 18:23:25 -080067import android.os.IBinder;
Jae Seo31dc634be2014-04-15 17:40:23 -070068import android.os.Looper;
69import android.os.Message;
Jaesung Chung58739e72015-04-24 19:39:59 +090070import android.os.ParcelFileDescriptor;
Jae Seo39570912014-02-20 18:23:25 -080071import android.os.Process;
72import android.os.RemoteException;
73import android.os.UserHandle;
Jae Seoc2a89512016-01-28 10:38:11 -080074import android.text.TextUtils;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090075import android.util.Slog;
Jae Seo39570912014-02-20 18:23:25 -080076import android.util.SparseArray;
Jae Seo6a6059a2014-04-17 21:35:29 -070077import android.view.InputChannel;
Jae Seo39570912014-02-20 18:23:25 -080078import android.view.Surface;
79
80import com.android.internal.content.PackageMonitor;
Jae Seo31dc634be2014-04-15 17:40:23 -070081import com.android.internal.os.SomeArgs;
Jaewan Kime14c3f42014-06-27 13:47:48 +090082import com.android.internal.util.IndentingPrintWriter;
Jae Seo31dc634be2014-04-15 17:40:23 -070083import com.android.server.IoThread;
Jae Seo39570912014-02-20 18:23:25 -080084import com.android.server.SystemService;
85
Chulwoo Leee7bb7d62014-05-27 14:10:37 +090086import org.xmlpull.v1.XmlPullParserException;
87
Jaesung Chung58739e72015-04-24 19:39:59 +090088import java.io.File;
Jaewan Kime14c3f42014-06-27 13:47:48 +090089import java.io.FileDescriptor;
Jaesung Chung58739e72015-04-24 19:39:59 +090090import java.io.FileNotFoundException;
Chulwoo Leee7bb7d62014-05-27 14:10:37 +090091import java.io.IOException;
Jaewan Kime14c3f42014-06-27 13:47:48 +090092import java.io.PrintWriter;
Jae Seo39570912014-02-20 18:23:25 -080093import java.util.ArrayList;
Chulwoo Lee19ba61a2014-09-03 00:59:35 +090094import java.util.Arrays;
Jaesung Chung58739e72015-04-24 19:39:59 +090095import java.util.Collections;
Jae Seo39570912014-02-20 18:23:25 -080096import java.util.HashMap;
Jae Seo5c80ad22014-06-12 19:52:58 -070097import java.util.HashSet;
Wonsik Kim187423c2014-06-25 14:12:48 +090098import java.util.Iterator;
Jae Seo39570912014-02-20 18:23:25 -080099import java.util.List;
100import java.util.Map;
Jae Seo5c80ad22014-06-12 19:52:58 -0700101import java.util.Set;
Jaesung Chung58739e72015-04-24 19:39:59 +0900102import java.util.regex.Matcher;
103import java.util.regex.Pattern;
Jae Seo39570912014-02-20 18:23:25 -0800104
105/** This class provides a system service that manages television inputs. */
106public final class TvInputManagerService extends SystemService {
Jae Seoee2ec052014-09-14 10:30:05 -0700107 private static final boolean DEBUG = false;
Jae Seo39570912014-02-20 18:23:25 -0800108 private static final String TAG = "TvInputManagerService";
109
Jaesung Chung58739e72015-04-24 19:39:59 +0900110 // Pattern for selecting the DVB frontend devices from the list of files in the /dev directory.
111 private static final Pattern sFrontEndDevicePattern =
112 Pattern.compile("^dvb([0-9]+)\\.frontend([0-9]+)$");
113
Jae Seo39570912014-02-20 18:23:25 -0800114 private final Context mContext;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000115 private final TvInputHardwareManager mTvInputHardwareManager;
Jae Seo39570912014-02-20 18:23:25 -0800116
117 // A global lock.
118 private final Object mLock = new Object();
119
120 // ID of the current user.
Xiaohui Chen233d94c2015-07-30 15:08:00 -0700121 private int mCurrentUserId = UserHandle.USER_SYSTEM;
Jae Seo39570912014-02-20 18:23:25 -0800122
123 // A map from user id to UserState.
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700124 private final SparseArray<UserState> mUserStates = new SparseArray<>();
Jae Seo39570912014-02-20 18:23:25 -0800125
Jae Seo7eb75df2014-08-08 22:20:48 -0700126 private final WatchLogHandler mWatchLogHandler;
Jae Seo31dc634be2014-04-15 17:40:23 -0700127
Jae Seo39570912014-02-20 18:23:25 -0800128 public TvInputManagerService(Context context) {
129 super(context);
Jae Seo31dc634be2014-04-15 17:40:23 -0700130
Jae Seo39570912014-02-20 18:23:25 -0800131 mContext = context;
Jae Seo8c375fe2015-06-23 20:33:25 -0700132 mWatchLogHandler = new WatchLogHandler(mContext.getContentResolver(),
133 IoThread.get().getLooper());
Wonsik Kim187423c2014-06-25 14:12:48 +0900134 mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
Jae Seo31dc634be2014-04-15 17:40:23 -0700135
Jae Seo39570912014-02-20 18:23:25 -0800136 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700137 getOrCreateUserStateLocked(mCurrentUserId);
Jae Seo39570912014-02-20 18:23:25 -0800138 }
139 }
140
141 @Override
142 public void onStart() {
143 publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());
144 }
145
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900146 @Override
147 public void onBootPhase(int phase) {
148 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
149 registerBroadcastReceivers();
Wonsik Kim187423c2014-06-25 14:12:48 +0900150 } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900151 synchronized (mLock) {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900152 buildTvInputListLocked(mCurrentUserId, null);
Jae Seo9c165d62014-08-25 14:39:26 -0700153 buildTvContentRatingSystemListLocked(mCurrentUserId);
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900154 }
155 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900156 mTvInputHardwareManager.onBootPhase(phase);
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900157 }
158
Jae Seo39570912014-02-20 18:23:25 -0800159 private void registerBroadcastReceivers() {
160 PackageMonitor monitor = new PackageMonitor() {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900161 private void buildTvInputList(String[] packages) {
162 synchronized (mLock) {
163 buildTvInputListLocked(getChangingUserId(), packages);
164 buildTvContentRatingSystemListLocked(getChangingUserId());
165 }
166 }
167
168 @Override
169 public void onPackageUpdateFinished(String packageName, int uid) {
170 if (DEBUG) Slog.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")");
171 // This callback is invoked when the TV input is reinstalled.
172 // In this case, isReplacing() always returns true.
173 buildTvInputList(new String[] { packageName });
174 }
175
176 @Override
177 public void onPackagesAvailable(String[] packages) {
178 if (DEBUG) {
179 Slog.d(TAG, "onPackagesAvailable(packages=" + Arrays.toString(packages) + ")");
180 }
181 // This callback is invoked when the media on which some packages exist become
182 // available.
183 if (isReplacing()) {
184 buildTvInputList(packages);
185 }
186 }
187
188 @Override
189 public void onPackagesUnavailable(String[] packages) {
190 // This callback is invoked when the media on which some packages exist become
191 // unavailable.
192 if (DEBUG) {
193 Slog.d(TAG, "onPackagesUnavailable(packages=" + Arrays.toString(packages)
194 + ")");
195 }
196 if (isReplacing()) {
197 buildTvInputList(packages);
198 }
199 }
200
Jae Seo39570912014-02-20 18:23:25 -0800201 @Override
202 public void onSomePackagesChanged() {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900203 // TODO: Use finer-grained methods(e.g. onPackageAdded, onPackageRemoved) to manage
204 // the TV inputs.
Dongwon Kang426c9a42014-08-26 17:39:21 +0900205 if (DEBUG) Slog.d(TAG, "onSomePackagesChanged()");
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900206 if (isReplacing()) {
207 if (DEBUG) Slog.d(TAG, "Skipped building TV input list due to replacing");
208 // When the package is updated, buildTvInputListLocked is called in other
209 // methods instead.
210 return;
Jae Seo39570912014-02-20 18:23:25 -0800211 }
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900212 buildTvInputList(null);
Jae Seo39570912014-02-20 18:23:25 -0800213 }
Jae Seo5c80ad22014-06-12 19:52:58 -0700214
215 @Override
Dongwon Kang31a8f842015-04-08 18:26:23 -0700216 public boolean onPackageChanged(String packageName, int uid, String[] components) {
217 // The input list needs to be updated in any cases, regardless of whether
218 // it happened to the whole package or a specific component. Returning true so that
219 // the update can be handled in {@link #onSomePackagesChanged}.
220 return true;
221 }
222
223 @Override
Jae Seo5c80ad22014-06-12 19:52:58 -0700224 public void onPackageRemoved(String packageName, int uid) {
225 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700226 UserState userState = getOrCreateUserStateLocked(getChangingUserId());
Wonsik Kim969167d2014-06-24 16:33:17 +0900227 if (!userState.packageSet.contains(packageName)) {
Jae Seo5c80ad22014-06-12 19:52:58 -0700228 // Not a TV input package.
229 return;
230 }
231 }
232
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700233 ArrayList<ContentProviderOperation> operations = new ArrayList<>();
Jae Seo5c80ad22014-06-12 19:52:58 -0700234
235 String selection = TvContract.BaseTvColumns.COLUMN_PACKAGE_NAME + "=?";
236 String[] selectionArgs = { packageName };
237
238 operations.add(ContentProviderOperation.newDelete(TvContract.Channels.CONTENT_URI)
239 .withSelection(selection, selectionArgs).build());
240 operations.add(ContentProviderOperation.newDelete(TvContract.Programs.CONTENT_URI)
241 .withSelection(selection, selectionArgs).build());
242 operations.add(ContentProviderOperation
243 .newDelete(TvContract.WatchedPrograms.CONTENT_URI)
244 .withSelection(selection, selectionArgs).build());
245
246 ContentProviderResult[] results = null;
247 try {
Jae Seo8c375fe2015-06-23 20:33:25 -0700248 ContentResolver cr = getContentResolverForUser(getChangingUserId());
249 results = cr.applyBatch(TvContract.AUTHORITY, operations);
Jae Seo5c80ad22014-06-12 19:52:58 -0700250 } catch (RemoteException | OperationApplicationException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700251 Slog.e(TAG, "error in applyBatch", e);
Jae Seo5c80ad22014-06-12 19:52:58 -0700252 }
253
254 if (DEBUG) {
255 Slog.d(TAG, "onPackageRemoved(packageName=" + packageName + ", uid=" + uid
256 + ")");
257 Slog.d(TAG, "results=" + results);
258 }
259 }
Jae Seo39570912014-02-20 18:23:25 -0800260 };
261 monitor.register(mContext, null, UserHandle.ALL, true);
262
263 IntentFilter intentFilter = new IntentFilter();
264 intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
265 intentFilter.addAction(Intent.ACTION_USER_REMOVED);
266 mContext.registerReceiverAsUser(new BroadcastReceiver() {
267 @Override
268 public void onReceive(Context context, Intent intent) {
269 String action = intent.getAction();
270 if (Intent.ACTION_USER_SWITCHED.equals(action)) {
271 switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
272 } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
273 removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
274 }
275 }
276 }, UserHandle.ALL, intentFilter, null, null);
277 }
278
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900279 private static boolean hasHardwarePermission(PackageManager pm, ComponentName component) {
Wonsik Kim187423c2014-06-25 14:12:48 +0900280 return pm.checkPermission(android.Manifest.permission.TV_INPUT_HARDWARE,
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900281 component.getPackageName()) == PackageManager.PERMISSION_GRANTED;
Wonsik Kim187423c2014-06-25 14:12:48 +0900282 }
283
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900284 private void buildTvInputListLocked(int userId, String[] updatedPackages) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700285 UserState userState = getOrCreateUserStateLocked(userId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900286 userState.packageSet.clear();
Jae Seo39570912014-02-20 18:23:25 -0800287
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900288 if (DEBUG) Slog.d(TAG, "buildTvInputList");
Jae Seo39570912014-02-20 18:23:25 -0800289 PackageManager pm = mContext.getPackageManager();
Jae Seo76976fa2015-05-20 21:51:16 -0700290 List<ResolveInfo> services = pm.queryIntentServicesAsUser(
Chulwoo Leee7bb7d62014-05-27 14:10:37 +0900291 new Intent(TvInputService.SERVICE_INTERFACE),
Jae Seo76976fa2015-05-20 21:51:16 -0700292 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
293 userId);
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700294 List<TvInputInfo> inputList = new ArrayList<>();
Jae Seo39570912014-02-20 18:23:25 -0800295 for (ResolveInfo ri : services) {
296 ServiceInfo si = ri.serviceInfo;
297 if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900298 Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
Jae Seo39570912014-02-20 18:23:25 -0800299 + android.Manifest.permission.BIND_TV_INPUT);
300 continue;
301 }
Jae Seo9cc28e52014-08-12 16:45:58 -0700302
303 ComponentName component = new ComponentName(si.packageName, si.name);
304 if (hasHardwarePermission(pm, component)) {
305 ServiceState serviceState = userState.serviceStateMap.get(component);
306 if (serviceState == null) {
307 // We see this hardware TV input service for the first time; we need to
308 // prepare the ServiceState object so that we can connect to the service and
309 // let it add TvInputInfo objects to mInputList if there's any.
310 serviceState = new ServiceState(component, userId);
311 userState.serviceStateMap.put(component, serviceState);
Wonsik Kimf271eac2014-08-30 12:55:10 +0900312 updateServiceConnectionLocked(component, userId);
Wonsik Kim187423c2014-06-25 14:12:48 +0900313 } else {
Jae Seo1abbbcd2016-01-28 22:20:41 -0800314 inputList.addAll(serviceState.hardwareInputList);
Jae Seo9cc28e52014-08-12 16:45:58 -0700315 }
316 } else {
317 try {
Jae Seoc03671f2016-01-26 15:15:22 -0800318 TvInputInfo info = new TvInputInfo.Builder(mContext, ri).build();
319 inputList.add(info);
Jae Seo9cc28e52014-08-12 16:45:58 -0700320 } catch (XmlPullParserException | IOException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700321 Slog.e(TAG, "failed to load TV input " + si.name, e);
Jae Seo9cc28e52014-08-12 16:45:58 -0700322 continue;
Wonsik Kim969167d2014-06-24 16:33:17 +0900323 }
Chulwoo Leee7bb7d62014-05-27 14:10:37 +0900324 }
Jae Seo9cc28e52014-08-12 16:45:58 -0700325 userState.packageSet.add(si.packageName);
326 }
327
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700328 Map<String, TvInputState> inputMap = new HashMap<>();
Jae Seo9cc28e52014-08-12 16:45:58 -0700329 for (TvInputInfo info : inputList) {
Jae Seofea8dd42014-08-26 13:57:41 -0700330 if (DEBUG) {
331 Slog.d(TAG, "add " + info.getId());
332 }
Jae Seoabda4202016-01-28 19:13:04 -0800333 TvInputState inputState = userState.inputMap.get(info.getId());
334 if (inputState == null) {
335 inputState = new TvInputState();
336 inputState.info = info;
Jae Seo9cc28e52014-08-12 16:45:58 -0700337 }
Jae Seoabda4202016-01-28 19:13:04 -0800338 inputMap.put(info.getId(), inputState);
Jae Seo39570912014-02-20 18:23:25 -0800339 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900340
341 for (String inputId : inputMap.keySet()) {
342 if (!userState.inputMap.containsKey(inputId)) {
343 notifyInputAddedLocked(userState, inputId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900344 } else if (updatedPackages != null) {
345 // Notify the package updates
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +0900346 ComponentName component = inputMap.get(inputId).info.getComponent();
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900347 for (String updatedPackage : updatedPackages) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +0900348 if (component.getPackageName().equals(updatedPackage)) {
349 updateServiceConnectionLocked(component, userId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900350 notifyInputUpdatedLocked(userState, inputId);
351 break;
352 }
353 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900354 }
355 }
356
357 for (String inputId : userState.inputMap.keySet()) {
358 if (!inputMap.containsKey(inputId)) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900359 TvInputInfo info = userState.inputMap.get(inputId).info;
Dongwon Kang426c9a42014-08-26 17:39:21 +0900360 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
361 if (serviceState != null) {
362 abortPendingCreateSessionRequestsLocked(serviceState, inputId, userId);
363 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900364 notifyInputRemovedLocked(userState, inputId);
365 }
366 }
367
368 userState.inputMap.clear();
369 userState.inputMap = inputMap;
Jae Seo9c165d62014-08-25 14:39:26 -0700370 }
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900371
Jae Seo9c165d62014-08-25 14:39:26 -0700372 private void buildTvContentRatingSystemListLocked(int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700373 UserState userState = getOrCreateUserStateLocked(userId);
Jae Seo9c165d62014-08-25 14:39:26 -0700374 userState.contentRatingSystemList.clear();
375
376 final PackageManager pm = mContext.getPackageManager();
377 Intent intent = new Intent(TvInputManager.ACTION_QUERY_CONTENT_RATING_SYSTEMS);
378 for (ResolveInfo resolveInfo :
379 pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA)) {
380 ActivityInfo receiver = resolveInfo.activityInfo;
381 Bundle metaData = receiver.metaData;
382 if (metaData == null) {
383 continue;
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900384 }
Jae Seo9c165d62014-08-25 14:39:26 -0700385
386 int xmlResId = metaData.getInt(TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS);
387 if (xmlResId == 0) {
388 Slog.w(TAG, "Missing meta-data '"
389 + TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS + "' on receiver "
390 + receiver.packageName + "/" + receiver.name);
391 continue;
392 }
393 userState.contentRatingSystemList.add(
394 TvContentRatingSystemInfo.createTvContentRatingSystemInfo(xmlResId,
395 receiver.applicationInfo));
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900396 }
Jae Seo39570912014-02-20 18:23:25 -0800397 }
398
399 private void switchUser(int userId) {
400 synchronized (mLock) {
401 if (mCurrentUserId == userId) {
402 return;
403 }
Jae Seo8c375fe2015-06-23 20:33:25 -0700404 clearSessionAndServiceStatesLocked(mUserStates.get(mCurrentUserId));
Jae Seo39570912014-02-20 18:23:25 -0800405
Jae Seo8c375fe2015-06-23 20:33:25 -0700406 mCurrentUserId = userId;
Jae Seo4f1a6d42015-07-20 16:15:01 -0700407 getOrCreateUserStateLocked(userId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900408 buildTvInputListLocked(userId, null);
Jae Seo9c165d62014-08-25 14:39:26 -0700409 buildTvContentRatingSystemListLocked(userId);
Jae Seo8c375fe2015-06-23 20:33:25 -0700410 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_SWITCH_CONTENT_RESOLVER,
411 getContentResolverForUser(userId)).sendToTarget();
Jae Seo39570912014-02-20 18:23:25 -0800412 }
413 }
414
415 private void removeUser(int userId) {
416 synchronized (mLock) {
Jae Seob06cb882014-04-09 12:08:17 -0700417 UserState userState = mUserStates.get(userId);
418 if (userState == null) {
419 return;
420 }
Jae Seo8c375fe2015-06-23 20:33:25 -0700421 clearSessionAndServiceStatesLocked(userState);
Jae Seo39570912014-02-20 18:23:25 -0800422
Jae Seofea8dd42014-08-26 13:57:41 -0700423 // Clear everything else.
424 userState.inputMap.clear();
425 userState.packageSet.clear();
Jae Seo9c165d62014-08-25 14:39:26 -0700426 userState.contentRatingSystemList.clear();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900427 userState.clientStateMap.clear();
Jae Seofea8dd42014-08-26 13:57:41 -0700428 userState.callbackSet.clear();
429 userState.mainSessionToken = null;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900430
Jae Seo39570912014-02-20 18:23:25 -0800431 mUserStates.remove(userId);
432 }
433 }
434
Jae Seo8c375fe2015-06-23 20:33:25 -0700435 private void clearSessionAndServiceStatesLocked(UserState userState) {
436 // Release created sessions.
437 for (SessionState state : userState.sessionStateMap.values()) {
438 if (state.session != null) {
439 try {
Jae Seoa826d012016-01-18 13:03:35 -0800440 if (state.isRecordingSession) {
441 state.session.disconnect();
442 } else {
443 state.session.release();
444 }
Jae Seo8c375fe2015-06-23 20:33:25 -0700445 } catch (RemoteException e) {
446 Slog.e(TAG, "error in release", e);
447 }
448 }
449 }
450 userState.sessionStateMap.clear();
451
452 // Unregister all callbacks and unbind all services.
453 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kang81e3c3e2015-09-11 15:24:25 -0700454 if (serviceState.service != null) {
455 if (serviceState.callback != null) {
456 try {
457 serviceState.service.unregisterCallback(serviceState.callback);
458 } catch (RemoteException e) {
459 Slog.e(TAG, "error in unregisterCallback", e);
460 }
Jae Seo8c375fe2015-06-23 20:33:25 -0700461 }
Dongwon Kang81e3c3e2015-09-11 15:24:25 -0700462 mContext.unbindService(serviceState.connection);
Jae Seo8c375fe2015-06-23 20:33:25 -0700463 }
Jae Seo8c375fe2015-06-23 20:33:25 -0700464 }
465 userState.serviceStateMap.clear();
466 }
467
468 private ContentResolver getContentResolverForUser(int userId) {
469 UserHandle user = new UserHandle(userId);
470 Context context;
471 try {
472 context = mContext.createPackageContextAsUser("android", 0, user);
473 } catch (NameNotFoundException e) {
Jae Seo2a2b2992016-01-12 23:13:14 -0800474 Slog.e(TAG, "failed to create package context as user " + user);
Jae Seo8c375fe2015-06-23 20:33:25 -0700475 context = mContext;
476 }
477 return context.getContentResolver();
478 }
479
Jae Seo4f1a6d42015-07-20 16:15:01 -0700480 private UserState getOrCreateUserStateLocked(int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800481 UserState userState = mUserStates.get(userId);
482 if (userState == null) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700483 userState = new UserState(mContext, userId);
484 mUserStates.put(userId, userState);
Jae Seo39570912014-02-20 18:23:25 -0800485 }
486 return userState;
487 }
488
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900489 private ServiceState getServiceStateLocked(ComponentName component, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700490 UserState userState = getOrCreateUserStateLocked(userId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900491 ServiceState serviceState = userState.serviceStateMap.get(component);
Jae Seo39570912014-02-20 18:23:25 -0800492 if (serviceState == null) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900493 throw new IllegalStateException("Service state not found for " + component + " (userId="
Sungsoo Lim7de5e232014-04-12 16:51:27 +0900494 + userId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800495 }
496 return serviceState;
497 }
498
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900499 private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700500 UserState userState = getOrCreateUserStateLocked(userId);
Jae Seo39570912014-02-20 18:23:25 -0800501 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
502 if (sessionState == null) {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900503 throw new SessionNotFoundException("Session state not found for token " + sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800504 }
505 // Only the application that requested this session or the system can access it.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900506 if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) {
Jae Seo39570912014-02-20 18:23:25 -0800507 throw new SecurityException("Illegal access to the session with token " + sessionToken
508 + " from uid " + callingUid);
509 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900510 return sessionState;
511 }
512
513 private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900514 return getSessionLocked(getSessionStateLocked(sessionToken, callingUid, userId));
515 }
516
517 private ITvInputSession getSessionLocked(SessionState sessionState) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900518 ITvInputSession session = sessionState.session;
Jae Seo39570912014-02-20 18:23:25 -0800519 if (session == null) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900520 throw new IllegalStateException("Session not yet created for token "
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900521 + sessionState.sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800522 }
523 return session;
524 }
525
526 private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
527 String methodName) {
528 return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
529 false, methodName, null);
530 }
531
Wonsik Kim187423c2014-06-25 14:12:48 +0900532 private static boolean shouldMaintainConnection(ServiceState serviceState) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900533 return !serviceState.sessionTokens.isEmpty() || serviceState.isHardware;
534 // TODO: Find a way to maintain connection to hardware TV input service only when necessary.
Wonsik Kim187423c2014-06-25 14:12:48 +0900535 }
536
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900537 private void updateServiceConnectionLocked(ComponentName component, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700538 UserState userState = getOrCreateUserStateLocked(userId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900539 ServiceState serviceState = userState.serviceStateMap.get(component);
Jae Seo39570912014-02-20 18:23:25 -0800540 if (serviceState == null) {
541 return;
542 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900543 if (serviceState.reconnecting) {
544 if (!serviceState.sessionTokens.isEmpty()) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900545 // wait until all the sessions are removed.
546 return;
547 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900548 serviceState.reconnecting = false;
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900549 }
Wonsik Kim187423c2014-06-25 14:12:48 +0900550 boolean maintainConnection = shouldMaintainConnection(serviceState);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900551 if (serviceState.service == null && maintainConnection && userId == mCurrentUserId) {
Jae Seo39570912014-02-20 18:23:25 -0800552 // This means that the service is not yet connected but its state indicates that we
553 // have pending requests. Then, connect the service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900554 if (serviceState.bound) {
Jae Seo39570912014-02-20 18:23:25 -0800555 // We have already bound to the service so we don't try to bind again until after we
556 // unbind later on.
557 return;
558 }
559 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900560 Slog.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800561 }
Sungsoo Limd6672b52014-04-30 10:43:26 +0900562
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900563 Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900564 serviceState.bound = mContext.bindServiceAsUser(
Dianne Hackbornd69e4c12015-04-24 09:54:54 -0700565 i, serviceState.connection,
566 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
567 new UserHandle(userId));
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900568 } else if (serviceState.service != null && !maintainConnection) {
Jae Seo39570912014-02-20 18:23:25 -0800569 // This means that the service is already connected but its state indicates that we have
570 // nothing to do with it. Then, disconnect the service.
571 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900572 Slog.d(TAG, "unbindService(service=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -0800573 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900574 mContext.unbindService(serviceState.connection);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900575 userState.serviceStateMap.remove(component);
Jae Seo39570912014-02-20 18:23:25 -0800576 }
577 }
578
Dongwon Kang426c9a42014-08-26 17:39:21 +0900579 private void abortPendingCreateSessionRequestsLocked(ServiceState serviceState,
580 String inputId, int userId) {
581 // Let clients know the create session requests are failed.
Jae Seo4f1a6d42015-07-20 16:15:01 -0700582 UserState userState = getOrCreateUserStateLocked(userId);
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900583 List<SessionState> sessionsToAbort = new ArrayList<>();
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900584 for (IBinder sessionToken : serviceState.sessionTokens) {
Dongwon Kang426c9a42014-08-26 17:39:21 +0900585 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900586 if (sessionState.session == null && (inputId == null
Jae Seo2cdb05e2016-02-04 22:17:13 +0900587 || sessionState.inputId.equals(inputId))) {
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900588 sessionsToAbort.add(sessionState);
Dongwon Kang426c9a42014-08-26 17:39:21 +0900589 }
590 }
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900591 for (SessionState sessionState : sessionsToAbort) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900592 removeSessionStateLocked(sessionState.sessionToken, sessionState.userId);
593 sendSessionTokenToClientLocked(sessionState.client,
Jae Seo2cdb05e2016-02-04 22:17:13 +0900594 sessionState.inputId, null, null, sessionState.seq);
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900595 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900596 updateServiceConnectionLocked(serviceState.component, userId);
Dongwon Kang426c9a42014-08-26 17:39:21 +0900597 }
598
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900599 private void createSessionInternalLocked(ITvInputService service, IBinder sessionToken,
600 int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700601 UserState userState = getOrCreateUserStateLocked(userId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900602 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800603 if (DEBUG) {
Jae Seo2cdb05e2016-02-04 22:17:13 +0900604 Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.inputId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800605 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900606 InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
Jae Seo6a6059a2014-04-17 21:35:29 -0700607
Jae Seo39570912014-02-20 18:23:25 -0800608 // Set up a callback to send the session token.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900609 ITvInputSessionCallback callback = new SessionCallback(sessionState, channels);
Jae Seo39570912014-02-20 18:23:25 -0800610
611 // Create a session. When failed, send a null token immediately.
612 try {
Jae Seoa826d012016-01-18 13:03:35 -0800613 if (sessionState.isRecordingSession) {
Jae Seo2cdb05e2016-02-04 22:17:13 +0900614 service.createRecordingSession(callback, sessionState.inputId);
Jae Seoa826d012016-01-18 13:03:35 -0800615 } else {
Jae Seo2cdb05e2016-02-04 22:17:13 +0900616 service.createSession(channels[1], callback, sessionState.inputId);
Jae Seoa826d012016-01-18 13:03:35 -0800617 }
Jae Seo39570912014-02-20 18:23:25 -0800618 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900619 Slog.e(TAG, "error in createSession", e);
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900620 removeSessionStateLocked(sessionToken, userId);
Jae Seo2cdb05e2016-02-04 22:17:13 +0900621 sendSessionTokenToClientLocked(sessionState.client, sessionState.inputId, null,
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900622 null, sessionState.seq);
Jae Seo39570912014-02-20 18:23:25 -0800623 }
Jae Seo6a6059a2014-04-17 21:35:29 -0700624 channels[1].dispose();
Jae Seo39570912014-02-20 18:23:25 -0800625 }
626
Sungsoo Limd6672b52014-04-30 10:43:26 +0900627 private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId,
Jae Seo5c80ad22014-06-12 19:52:58 -0700628 IBinder sessionToken, InputChannel channel, int seq) {
Jae Seo39570912014-02-20 18:23:25 -0800629 try {
Sungsoo Limd6672b52014-04-30 10:43:26 +0900630 client.onSessionCreated(inputId, sessionToken, channel, seq);
Jae Seofea8dd42014-08-26 13:57:41 -0700631 } catch (RemoteException e) {
632 Slog.e(TAG, "error in onSessionCreated", e);
Jae Seo39570912014-02-20 18:23:25 -0800633 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900634 }
Jae Seo39570912014-02-20 18:23:25 -0800635
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900636 private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900637 SessionState sessionState = null;
638 try {
639 sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
640 if (sessionState.session != null) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700641 UserState userState = getOrCreateUserStateLocked(userId);
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900642 if (sessionToken == userState.mainSessionToken) {
643 setMainLocked(sessionToken, false, callingUid, userId);
644 }
Jae Seoa826d012016-01-18 13:03:35 -0800645 if (sessionState.isRecordingSession) {
646 sessionState.session.disconnect();
647 } else {
648 sessionState.session.release();
649 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900650 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900651 } catch (RemoteException | SessionNotFoundException e) {
652 Slog.e(TAG, "error in releaseSession", e);
653 } finally {
654 if (sessionState != null) {
655 sessionState.session = null;
656 }
Jae Seo39570912014-02-20 18:23:25 -0800657 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900658 removeSessionStateLocked(sessionToken, userId);
Jae Seo39570912014-02-20 18:23:25 -0800659 }
660
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900661 private void removeSessionStateLocked(IBinder sessionToken, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700662 UserState userState = getOrCreateUserStateLocked(userId);
Ji-Hwan Leeabca0ee2014-07-24 17:34:19 +0900663 if (sessionToken == userState.mainSessionToken) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900664 if (DEBUG) {
665 Slog.d(TAG, "mainSessionToken=null");
666 }
Ji-Hwan Leeabca0ee2014-07-24 17:34:19 +0900667 userState.mainSessionToken = null;
668 }
669
670 // Remove the session state from the global session state map of the current user.
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900671 SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
672
Chulwoo Lee8d4ded02014-07-10 03:56:39 +0900673 if (sessionState == null) {
674 return;
675 }
676
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900677 // Also remove the session token from the session token list of the current client and
678 // service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900679 ClientState clientState = userState.clientStateMap.get(sessionState.client.asBinder());
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900680 if (clientState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900681 clientState.sessionTokens.remove(sessionToken);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900682 if (clientState.isEmpty()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900683 userState.clientStateMap.remove(sessionState.client.asBinder());
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900684 }
685 }
686
Jae Seo2cdb05e2016-02-04 22:17:13 +0900687 ServiceState serviceState = userState.serviceStateMap.get(sessionState.componentName);
688 if (serviceState != null) {
689 serviceState.sessionTokens.remove(sessionToken);
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900690 }
Jae Seo2cdb05e2016-02-04 22:17:13 +0900691 updateServiceConnectionLocked(sessionState.componentName, userId);
Jae Seo7eb75df2014-08-08 22:20:48 -0700692
693 // Log the end of watch.
694 SomeArgs args = SomeArgs.obtain();
695 args.arg1 = sessionToken;
696 args.arg2 = System.currentTimeMillis();
697 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_END, args).sendToTarget();
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900698 }
699
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900700 private void setMainLocked(IBinder sessionToken, boolean isMain, int callingUid, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900701 try {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900702 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
703 if (sessionState.hardwareSessionToken != null) {
704 sessionState = getSessionStateLocked(sessionState.hardwareSessionToken,
705 Process.SYSTEM_UID, userId);
706 }
Jae Seo2cdb05e2016-02-04 22:17:13 +0900707 ServiceState serviceState = getServiceStateLocked(sessionState.componentName, userId);
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900708 if (!serviceState.isHardware) {
709 return;
710 }
711 ITvInputSession session = getSessionLocked(sessionState);
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900712 session.setMain(isMain);
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900713 } catch (RemoteException | SessionNotFoundException e) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900714 Slog.e(TAG, "error in setMain", e);
715 }
716 }
717
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900718 private void notifyInputAddedLocked(UserState userState, String inputId) {
719 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700720 Slog.d(TAG, "notifyInputAddedLocked(inputId=" + inputId + ")");
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900721 }
722 for (ITvInputManagerCallback callback : userState.callbackSet) {
723 try {
724 callback.onInputAdded(inputId);
725 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700726 Slog.e(TAG, "failed to report added input to callback", e);
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900727 }
728 }
729 }
730
731 private void notifyInputRemovedLocked(UserState userState, String inputId) {
732 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700733 Slog.d(TAG, "notifyInputRemovedLocked(inputId=" + inputId + ")");
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900734 }
735 for (ITvInputManagerCallback callback : userState.callbackSet) {
736 try {
737 callback.onInputRemoved(inputId);
738 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700739 Slog.e(TAG, "failed to report removed input to callback", e);
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900740 }
741 }
742 }
743
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900744 private void notifyInputUpdatedLocked(UserState userState, String inputId) {
745 if (DEBUG) {
746 Slog.d(TAG, "notifyInputUpdatedLocked(inputId=" + inputId + ")");
747 }
748 for (ITvInputManagerCallback callback : userState.callbackSet) {
749 try {
750 callback.onInputUpdated(inputId);
751 } catch (RemoteException e) {
752 Slog.e(TAG, "failed to report updated input to callback", e);
753 }
754 }
755 }
756
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900757 private void notifyInputStateChangedLocked(UserState userState, String inputId,
Wonsik Kim969167d2014-06-24 16:33:17 +0900758 int state, ITvInputManagerCallback targetCallback) {
759 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700760 Slog.d(TAG, "notifyInputStateChangedLocked(inputId=" + inputId
761 + ", state=" + state + ")");
Wonsik Kim969167d2014-06-24 16:33:17 +0900762 }
763 if (targetCallback == null) {
764 for (ITvInputManagerCallback callback : userState.callbackSet) {
765 try {
766 callback.onInputStateChanged(inputId, state);
767 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700768 Slog.e(TAG, "failed to report state change to callback", e);
Wonsik Kim969167d2014-06-24 16:33:17 +0900769 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900770 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900771 } else {
772 try {
773 targetCallback.onInputStateChanged(inputId, state);
774 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700775 Slog.e(TAG, "failed to report state change to callback", e);
Wonsik Kim969167d2014-06-24 16:33:17 +0900776 }
777 }
778 }
779
Jae Seoc2a89512016-01-28 10:38:11 -0800780 private void setTvInputInfoLocked(UserState userState, TvInputInfo inputInfo) {
Jae Seoa826d012016-01-18 13:03:35 -0800781 if (DEBUG) {
Jae Seoc2a89512016-01-28 10:38:11 -0800782 Slog.d(TAG, "setTvInputInfoLocked(inputInfo=" + inputInfo + ")");
Jae Seoa826d012016-01-18 13:03:35 -0800783 }
Jae Seoabda4202016-01-28 19:13:04 -0800784 String inputId = inputInfo.getId();
785 TvInputState inputState = userState.inputMap.get(inputId);
786 if (inputState == null) {
787 Slog.e(TAG, "failed to set input info - unknown input id " + inputId);
788 return;
789 }
790 if (inputState.info.equals(inputInfo)) {
791 return;
792 }
793 inputState.info = inputInfo;
794
Jae Seoa826d012016-01-18 13:03:35 -0800795 for (ITvInputManagerCallback callback : userState.callbackSet) {
796 try {
Jae Seoc2a89512016-01-28 10:38:11 -0800797 callback.onTvInputInfoChanged(inputInfo);
Jae Seoa826d012016-01-18 13:03:35 -0800798 } catch (RemoteException e) {
799 Slog.e(TAG, "failed to report changed input info to callback", e);
800 }
801 }
802 }
803
Wonsik Kim969167d2014-06-24 16:33:17 +0900804 private void setStateLocked(String inputId, int state, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700805 UserState userState = getOrCreateUserStateLocked(userId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900806 TvInputState inputState = userState.inputMap.get(inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900807 ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent());
808 int oldState = inputState.state;
809 inputState.state = state;
810 if (serviceState != null && serviceState.service == null
Wonsik Kim187423c2014-06-25 14:12:48 +0900811 && shouldMaintainConnection(serviceState)) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900812 // We don't notify state change while reconnecting. It should remain disconnected.
813 return;
814 }
815 if (oldState != state) {
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900816 notifyInputStateChangedLocked(userState, inputId, state, null);
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900817 }
818 }
819
Jae Seo39570912014-02-20 18:23:25 -0800820 private final class BinderService extends ITvInputManager.Stub {
821 @Override
822 public List<TvInputInfo> getTvInputList(int userId) {
823 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
824 Binder.getCallingUid(), userId, "getTvInputList");
825 final long identity = Binder.clearCallingIdentity();
826 try {
827 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700828 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700829 List<TvInputInfo> inputList = new ArrayList<>();
Wonsik Kim969167d2014-06-24 16:33:17 +0900830 for (TvInputState state : userState.inputMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900831 inputList.add(state.info);
Jae Seo39570912014-02-20 18:23:25 -0800832 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900833 return inputList;
Jae Seo39570912014-02-20 18:23:25 -0800834 }
835 } finally {
836 Binder.restoreCallingIdentity(identity);
837 }
Jae Seo39570912014-02-20 18:23:25 -0800838 }
839
840 @Override
Jae Seob3758052014-07-12 19:25:24 -0700841 public TvInputInfo getTvInputInfo(String inputId, int userId) {
842 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
843 Binder.getCallingUid(), userId, "getTvInputInfo");
844 final long identity = Binder.clearCallingIdentity();
845 try {
846 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700847 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seob3758052014-07-12 19:25:24 -0700848 TvInputState state = userState.inputMap.get(inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900849 return state == null ? null : state.info;
Jae Seob3758052014-07-12 19:25:24 -0700850 }
851 } finally {
852 Binder.restoreCallingIdentity(identity);
853 }
854 }
855
Jae Seoc2a89512016-01-28 10:38:11 -0800856 public void setTvInputInfo(TvInputInfo inputInfo, int userId) {
857 String inputInfoPackageName = inputInfo.getServiceInfo().packageName;
858 String callingPackageName = getCallingPackageName();
859 if (!TextUtils.equals(inputInfoPackageName, callingPackageName)) {
860 throw new IllegalArgumentException("calling package " + callingPackageName
861 + " is not allowed to change TvInputInfo for " + inputInfoPackageName);
862 }
863
864 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
Jae Seoabda4202016-01-28 19:13:04 -0800865 Binder.getCallingUid(), userId, "setTvInputInfo");
Jae Seoc2a89512016-01-28 10:38:11 -0800866 final long identity = Binder.clearCallingIdentity();
867 try {
868 synchronized (mLock) {
869 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
870 setTvInputInfoLocked(userState, inputInfo);
871 }
872 } finally {
873 Binder.restoreCallingIdentity(identity);
874 }
875 }
876
877 private String getCallingPackageName() {
878 final String[] packages = mContext.getPackageManager().getPackagesForUid(
879 Binder.getCallingUid());
880 if (packages != null && packages.length > 0) {
881 return packages[0];
882 }
883 return "unknown";
884 }
885
Jae Seob3758052014-07-12 19:25:24 -0700886 @Override
Dongwon Kang993f81e2014-11-27 19:34:18 +0900887 public int getTvInputState(String inputId, int userId) {
888 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
889 Binder.getCallingUid(), userId, "getTvInputState");
890 final long identity = Binder.clearCallingIdentity();
891 try {
892 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700893 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Dongwon Kang993f81e2014-11-27 19:34:18 +0900894 TvInputState state = userState.inputMap.get(inputId);
Jae Seo82fce642015-04-20 15:37:50 -0700895 return state == null ? INPUT_STATE_CONNECTED : state.state;
Dongwon Kang993f81e2014-11-27 19:34:18 +0900896 }
897 } finally {
898 Binder.restoreCallingIdentity(identity);
899 }
900 }
901
902 @Override
Jae Seo9c165d62014-08-25 14:39:26 -0700903 public List<TvContentRatingSystemInfo> getTvContentRatingSystemList(int userId) {
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900904 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
Jae Seo9c165d62014-08-25 14:39:26 -0700905 Binder.getCallingUid(), userId, "getTvContentRatingSystemList");
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900906 final long identity = Binder.clearCallingIdentity();
907 try {
908 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700909 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo9c165d62014-08-25 14:39:26 -0700910 return userState.contentRatingSystemList;
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900911 }
912 } finally {
913 Binder.restoreCallingIdentity(identity);
914 }
915 }
916
917 @Override
Wonsik Kim969167d2014-06-24 16:33:17 +0900918 public void registerCallback(final ITvInputManagerCallback callback, int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800919 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
920 Binder.getCallingUid(), userId, "registerCallback");
921 final long identity = Binder.clearCallingIdentity();
922 try {
923 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700924 final UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900925 userState.callbackSet.add(callback);
Jae Seofea8dd42014-08-26 13:57:41 -0700926 try {
927 callback.asBinder().linkToDeath(new IBinder.DeathRecipient() {
928 @Override
929 public void binderDied() {
930 synchronized (mLock) {
931 if (userState.callbackSet != null) {
932 userState.callbackSet.remove(callback);
933 }
934 }
935 }
936 }, 0);
937 } catch (RemoteException e) {
938 Slog.e(TAG, "client process has already died", e);
939 }
Jae Seo39570912014-02-20 18:23:25 -0800940 }
941 } finally {
942 Binder.restoreCallingIdentity(identity);
943 }
944 }
945
946 @Override
Wonsik Kim969167d2014-06-24 16:33:17 +0900947 public void unregisterCallback(ITvInputManagerCallback callback, int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800948 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
949 Binder.getCallingUid(), userId, "unregisterCallback");
950 final long identity = Binder.clearCallingIdentity();
951 try {
952 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700953 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900954 userState.callbackSet.remove(callback);
Jae Seo39570912014-02-20 18:23:25 -0800955 }
956 } finally {
957 Binder.restoreCallingIdentity(identity);
958 }
959 }
960
961 @Override
Jae Seo783645e2014-07-28 17:30:50 +0900962 public boolean isParentalControlsEnabled(int userId) {
963 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
964 Binder.getCallingUid(), userId, "isParentalControlsEnabled");
965 final long identity = Binder.clearCallingIdentity();
966 try {
967 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700968 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +0900969 return userState.persistentDataStore.isParentalControlsEnabled();
970 }
971 } finally {
972 Binder.restoreCallingIdentity(identity);
973 }
974 }
975
976 @Override
977 public void setParentalControlsEnabled(boolean enabled, int userId) {
978 ensureParentalControlsPermission();
979 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
980 Binder.getCallingUid(), userId, "setParentalControlsEnabled");
981 final long identity = Binder.clearCallingIdentity();
982 try {
983 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700984 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +0900985 userState.persistentDataStore.setParentalControlsEnabled(enabled);
986 }
987 } finally {
988 Binder.restoreCallingIdentity(identity);
989 }
990 }
991
992 @Override
993 public boolean isRatingBlocked(String rating, int userId) {
994 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
995 Binder.getCallingUid(), userId, "isRatingBlocked");
996 final long identity = Binder.clearCallingIdentity();
997 try {
998 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700999 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +09001000 return userState.persistentDataStore.isRatingBlocked(
1001 TvContentRating.unflattenFromString(rating));
1002 }
1003 } finally {
1004 Binder.restoreCallingIdentity(identity);
1005 }
1006 }
1007
1008 @Override
1009 public List<String> getBlockedRatings(int userId) {
1010 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1011 Binder.getCallingUid(), userId, "getBlockedRatings");
1012 final long identity = Binder.clearCallingIdentity();
1013 try {
1014 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001015 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001016 List<String> ratings = new ArrayList<>();
Jae Seo783645e2014-07-28 17:30:50 +09001017 for (TvContentRating rating
1018 : userState.persistentDataStore.getBlockedRatings()) {
1019 ratings.add(rating.flattenToString());
1020 }
1021 return ratings;
1022 }
1023 } finally {
1024 Binder.restoreCallingIdentity(identity);
1025 }
1026 }
1027
1028 @Override
1029 public void addBlockedRating(String rating, int userId) {
1030 ensureParentalControlsPermission();
1031 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1032 Binder.getCallingUid(), userId, "addBlockedRating");
1033 final long identity = Binder.clearCallingIdentity();
1034 try {
1035 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001036 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +09001037 userState.persistentDataStore.addBlockedRating(
1038 TvContentRating.unflattenFromString(rating));
1039 }
1040 } finally {
1041 Binder.restoreCallingIdentity(identity);
1042 }
1043 }
1044
1045 @Override
1046 public void removeBlockedRating(String rating, int userId) {
1047 ensureParentalControlsPermission();
1048 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1049 Binder.getCallingUid(), userId, "removeBlockedRating");
1050 final long identity = Binder.clearCallingIdentity();
1051 try {
1052 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001053 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +09001054 userState.persistentDataStore.removeBlockedRating(
1055 TvContentRating.unflattenFromString(rating));
1056 }
1057 } finally {
1058 Binder.restoreCallingIdentity(identity);
1059 }
1060 }
1061
1062 private void ensureParentalControlsPermission() {
Jae Seofc836f62014-08-27 00:47:56 +00001063 if (mContext.checkCallingPermission(
1064 android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
1065 != PackageManager.PERMISSION_GRANTED) {
1066 throw new SecurityException(
1067 "The caller does not have parental controls permission");
1068 }
Jae Seo783645e2014-07-28 17:30:50 +09001069 }
1070
1071 @Override
Sungsoo Limd6672b52014-04-30 10:43:26 +09001072 public void createSession(final ITvInputClient client, final String inputId,
Jae Seoa826d012016-01-18 13:03:35 -08001073 boolean isRecordingSession, int seq, int userId) {
Jae Seo39570912014-02-20 18:23:25 -08001074 final int callingUid = Binder.getCallingUid();
1075 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1076 userId, "createSession");
1077 final long identity = Binder.clearCallingIdentity();
1078 try {
1079 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001080 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Dongwon Kang426c9a42014-08-26 17:39:21 +09001081 TvInputState inputState = userState.inputMap.get(inputId);
1082 if (inputState == null) {
1083 Slog.w(TAG, "Failed to find input state for inputId=" + inputId);
1084 sendSessionTokenToClientLocked(client, inputId, null, null, seq);
1085 return;
1086 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001087 TvInputInfo info = inputState.info;
Wonsik Kim187423c2014-06-25 14:12:48 +09001088 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
Jae Seo39570912014-02-20 18:23:25 -08001089 if (serviceState == null) {
Wonsik Kim187423c2014-06-25 14:12:48 +09001090 serviceState = new ServiceState(info.getComponent(), resolvedUserId);
1091 userState.serviceStateMap.put(info.getComponent(), serviceState);
Jae Seo39570912014-02-20 18:23:25 -08001092 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001093 // Send a null token immediately while reconnecting.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001094 if (serviceState.reconnecting) {
Jae Seo5c80ad22014-06-12 19:52:58 -07001095 sendSessionTokenToClientLocked(client, inputId, null, null, seq);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001096 return;
1097 }
1098
1099 // Create a new session token and a session state.
1100 IBinder sessionToken = new Binder();
Jae Seo2cdb05e2016-02-04 22:17:13 +09001101 SessionState sessionState = new SessionState(sessionToken, info.getId(),
1102 info.getComponent(), isRecordingSession, client, seq, callingUid,
1103 resolvedUserId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001104
1105 // Add them to the global session state map of the current user.
1106 userState.sessionStateMap.put(sessionToken, sessionState);
1107
1108 // Also, add them to the session state map of the current service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001109 serviceState.sessionTokens.add(sessionToken);
Jae Seo39570912014-02-20 18:23:25 -08001110
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001111 if (serviceState.service != null) {
1112 createSessionInternalLocked(serviceState.service, sessionToken,
Sungsoo Lim7de5e232014-04-12 16:51:27 +09001113 resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001114 } else {
Wonsik Kim187423c2014-06-25 14:12:48 +09001115 updateServiceConnectionLocked(info.getComponent(), resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001116 }
1117 }
1118 } finally {
1119 Binder.restoreCallingIdentity(identity);
1120 }
1121 }
1122
1123 @Override
1124 public void releaseSession(IBinder sessionToken, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001125 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -07001126 Slog.d(TAG, "releaseSession(sessionToken=" + sessionToken + ")");
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001127 }
Jae Seo39570912014-02-20 18:23:25 -08001128 final int callingUid = Binder.getCallingUid();
1129 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1130 userId, "releaseSession");
1131 final long identity = Binder.clearCallingIdentity();
1132 try {
1133 synchronized (mLock) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001134 releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001135 }
1136 } finally {
1137 Binder.restoreCallingIdentity(identity);
1138 }
1139 }
1140
1141 @Override
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001142 public void setMainSession(IBinder sessionToken, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001143 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -07001144 Slog.d(TAG, "setMainSession(sessionToken=" + sessionToken + ")");
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001145 }
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001146 final int callingUid = Binder.getCallingUid();
1147 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1148 userId, "setMainSession");
1149 final long identity = Binder.clearCallingIdentity();
1150 try {
1151 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001152 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001153 if (userState.mainSessionToken == sessionToken) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001154 return;
1155 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001156 if (DEBUG) {
1157 Slog.d(TAG, "mainSessionToken=" + sessionToken);
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001158 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001159 IBinder oldMainSessionToken = userState.mainSessionToken;
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001160 userState.mainSessionToken = sessionToken;
1161
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001162 // Inform the new main session first.
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001163 // See {@link TvInputService.Session#onSetMain}.
1164 if (sessionToken != null) {
1165 setMainLocked(sessionToken, true, callingUid, userId);
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001166 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001167 if (oldMainSessionToken != null) {
1168 setMainLocked(oldMainSessionToken, false, Process.SYSTEM_UID, userId);
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001169 }
1170 }
1171 } finally {
1172 Binder.restoreCallingIdentity(identity);
1173 }
1174 }
1175
1176 @Override
Jae Seo39570912014-02-20 18:23:25 -08001177 public void setSurface(IBinder sessionToken, Surface surface, int userId) {
1178 final int callingUid = Binder.getCallingUid();
1179 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1180 userId, "setSurface");
1181 final long identity = Binder.clearCallingIdentity();
1182 try {
1183 synchronized (mLock) {
1184 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001185 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1186 resolvedUserId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001187 if (sessionState.hardwareSessionToken == null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001188 getSessionLocked(sessionState).setSurface(surface);
1189 } else {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001190 getSessionLocked(sessionState.hardwareSessionToken,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001191 Process.SYSTEM_UID, resolvedUserId).setSurface(surface);
1192 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001193 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001194 Slog.e(TAG, "error in setSurface", e);
Jae Seo39570912014-02-20 18:23:25 -08001195 }
1196 }
1197 } finally {
Youngsang Chof8362062014-04-30 17:24:20 +09001198 if (surface != null) {
1199 // surface is not used in TvInputManagerService.
1200 surface.release();
1201 }
Jae Seo39570912014-02-20 18:23:25 -08001202 Binder.restoreCallingIdentity(identity);
1203 }
1204 }
1205
1206 @Override
Youngsang Choe821d712014-07-16 14:22:19 -07001207 public void dispatchSurfaceChanged(IBinder sessionToken, int format, int width,
1208 int height, int userId) {
1209 final int callingUid = Binder.getCallingUid();
1210 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1211 userId, "dispatchSurfaceChanged");
1212 final long identity = Binder.clearCallingIdentity();
1213 try {
1214 synchronized (mLock) {
1215 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001216 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1217 resolvedUserId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001218 getSessionLocked(sessionState).dispatchSurfaceChanged(format, width,
1219 height);
1220 if (sessionState.hardwareSessionToken != null) {
1221 getSessionLocked(sessionState.hardwareSessionToken, Process.SYSTEM_UID,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001222 resolvedUserId).dispatchSurfaceChanged(format, width, height);
1223 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001224 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Choe821d712014-07-16 14:22:19 -07001225 Slog.e(TAG, "error in dispatchSurfaceChanged", e);
1226 }
1227 }
1228 } finally {
1229 Binder.restoreCallingIdentity(identity);
1230 }
1231 }
1232
1233 @Override
Jae Seo39570912014-02-20 18:23:25 -08001234 public void setVolume(IBinder sessionToken, float volume, int userId) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001235 final float REMOTE_VOLUME_ON = 1.0f;
1236 final float REMOTE_VOLUME_OFF = 0f;
Jae Seo39570912014-02-20 18:23:25 -08001237 final int callingUid = Binder.getCallingUid();
1238 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1239 userId, "setVolume");
1240 final long identity = Binder.clearCallingIdentity();
1241 try {
1242 synchronized (mLock) {
1243 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001244 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1245 resolvedUserId);
1246 getSessionLocked(sessionState).setVolume(volume);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001247 if (sessionState.hardwareSessionToken != null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001248 // Here, we let the hardware session know only whether volume is on or
1249 // off to prevent that the volume is controlled in the both side.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001250 getSessionLocked(sessionState.hardwareSessionToken,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001251 Process.SYSTEM_UID, resolvedUserId).setVolume((volume > 0.0f)
1252 ? REMOTE_VOLUME_ON : REMOTE_VOLUME_OFF);
1253 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001254 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001255 Slog.e(TAG, "error in setVolume", e);
Jae Seo39570912014-02-20 18:23:25 -08001256 }
1257 }
1258 } finally {
1259 Binder.restoreCallingIdentity(identity);
1260 }
1261 }
1262
1263 @Override
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +09001264 public void tune(IBinder sessionToken, final Uri channelUri, Bundle params, int userId) {
Jae Seo39570912014-02-20 18:23:25 -08001265 final int callingUid = Binder.getCallingUid();
1266 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1267 userId, "tune");
1268 final long identity = Binder.clearCallingIdentity();
1269 try {
1270 synchronized (mLock) {
Jae Seo39570912014-02-20 18:23:25 -08001271 try {
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +09001272 getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(
1273 channelUri, params);
Jae Seoc22d0c02014-08-15 13:03:21 -07001274 if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
Youngsang Cho008f6d42014-07-22 21:29:47 -07001275 // Do not log the watch history for passthrough inputs.
1276 return;
1277 }
Jae Seo31dc634be2014-04-15 17:40:23 -07001278
Jae Seo4f1a6d42015-07-20 16:15:01 -07001279 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo31dc634be2014-04-15 17:40:23 -07001280 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Jae Seo31dc634be2014-04-15 17:40:23 -07001281
Jae Seo7eb75df2014-08-08 22:20:48 -07001282 // Log the start of watch.
Jae Seo31dc634be2014-04-15 17:40:23 -07001283 SomeArgs args = SomeArgs.obtain();
Jae Seo2cdb05e2016-02-04 22:17:13 +09001284 args.arg1 = sessionState.componentName.getPackageName();
Jae Seo7eb75df2014-08-08 22:20:48 -07001285 args.arg2 = System.currentTimeMillis();
1286 args.arg3 = ContentUris.parseId(channelUri);
1287 args.arg4 = params;
1288 args.arg5 = sessionToken;
1289 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_START, args)
1290 .sendToTarget();
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001291 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001292 Slog.e(TAG, "error in tune", e);
Jae Seo39570912014-02-20 18:23:25 -08001293 }
1294 }
1295 } finally {
1296 Binder.restoreCallingIdentity(identity);
1297 }
1298 }
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001299
1300 @Override
Jae Seoa9033832015-03-11 19:29:46 -07001301 public void unblockContent(
Sungsoo Lim9bf671f2014-07-19 12:59:51 +09001302 IBinder sessionToken, String unblockedRating, int userId) {
Jaewan Kim903d6b72014-07-16 11:28:56 +09001303 final int callingUid = Binder.getCallingUid();
1304 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1305 userId, "unblockContent");
1306 final long identity = Binder.clearCallingIdentity();
1307 try {
1308 synchronized (mLock) {
1309 try {
1310 getSessionLocked(sessionToken, callingUid, resolvedUserId)
Jae Seoa9033832015-03-11 19:29:46 -07001311 .unblockContent(unblockedRating);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001312 } catch (RemoteException | SessionNotFoundException e) {
Jae Seoa9033832015-03-11 19:29:46 -07001313 Slog.e(TAG, "error in unblockContent", e);
Jaewan Kim903d6b72014-07-16 11:28:56 +09001314 }
1315 }
1316 } finally {
1317 Binder.restoreCallingIdentity(identity);
1318 }
1319 }
1320
1321 @Override
Jae Seo2c1c31c2014-07-10 14:57:01 -07001322 public void setCaptionEnabled(IBinder sessionToken, boolean enabled, int userId) {
1323 final int callingUid = Binder.getCallingUid();
1324 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1325 userId, "setCaptionEnabled");
1326 final long identity = Binder.clearCallingIdentity();
1327 try {
1328 synchronized (mLock) {
1329 try {
1330 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1331 .setCaptionEnabled(enabled);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001332 } catch (RemoteException | SessionNotFoundException e) {
Jae Seo2c1c31c2014-07-10 14:57:01 -07001333 Slog.e(TAG, "error in setCaptionEnabled", e);
1334 }
1335 }
1336 } finally {
1337 Binder.restoreCallingIdentity(identity);
1338 }
1339 }
1340
1341 @Override
Jae Seo10d285a2014-07-31 22:46:47 +09001342 public void selectTrack(IBinder sessionToken, int type, String trackId, int userId) {
Dongwon Kang1f213912014-07-02 18:35:08 +09001343 final int callingUid = Binder.getCallingUid();
1344 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1345 userId, "selectTrack");
1346 final long identity = Binder.clearCallingIdentity();
1347 try {
1348 synchronized (mLock) {
1349 try {
1350 getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack(
Jae Seo10d285a2014-07-31 22:46:47 +09001351 type, trackId);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001352 } catch (RemoteException | SessionNotFoundException e) {
Dongwon Kang1f213912014-07-02 18:35:08 +09001353 Slog.e(TAG, "error in selectTrack", e);
1354 }
1355 }
1356 } finally {
1357 Binder.restoreCallingIdentity(identity);
1358 }
1359 }
1360
1361 @Override
Jae Seoa759b112014-07-18 22:16:08 -07001362 public void sendAppPrivateCommand(IBinder sessionToken, String command, Bundle data,
1363 int userId) {
1364 final int callingUid = Binder.getCallingUid();
1365 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1366 userId, "sendAppPrivateCommand");
1367 final long identity = Binder.clearCallingIdentity();
1368 try {
1369 synchronized (mLock) {
1370 try {
1371 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1372 .appPrivateCommand(command, data);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001373 } catch (RemoteException | SessionNotFoundException e) {
Jae Seofea8dd42014-08-26 13:57:41 -07001374 Slog.e(TAG, "error in appPrivateCommand", e);
Jae Seoa759b112014-07-18 22:16:08 -07001375 }
1376 }
1377 } finally {
1378 Binder.restoreCallingIdentity(identity);
1379 }
1380 }
1381
1382 @Override
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001383 public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
1384 int userId) {
1385 final int callingUid = Binder.getCallingUid();
1386 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1387 userId, "createOverlayView");
1388 final long identity = Binder.clearCallingIdentity();
1389 try {
1390 synchronized (mLock) {
1391 try {
1392 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1393 .createOverlayView(windowToken, frame);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001394 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001395 Slog.e(TAG, "error in createOverlayView", e);
1396 }
1397 }
1398 } finally {
1399 Binder.restoreCallingIdentity(identity);
1400 }
1401 }
1402
1403 @Override
1404 public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
1405 final int callingUid = Binder.getCallingUid();
1406 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1407 userId, "relayoutOverlayView");
1408 final long identity = Binder.clearCallingIdentity();
1409 try {
1410 synchronized (mLock) {
1411 try {
1412 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1413 .relayoutOverlayView(frame);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001414 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001415 Slog.e(TAG, "error in relayoutOverlayView", e);
1416 }
1417 }
1418 } finally {
1419 Binder.restoreCallingIdentity(identity);
1420 }
1421 }
1422
1423 @Override
1424 public void removeOverlayView(IBinder sessionToken, int userId) {
1425 final int callingUid = Binder.getCallingUid();
1426 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1427 userId, "removeOverlayView");
1428 final long identity = Binder.clearCallingIdentity();
1429 try {
1430 synchronized (mLock) {
1431 try {
1432 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1433 .removeOverlayView();
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001434 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001435 Slog.e(TAG, "error in removeOverlayView", e);
1436 }
1437 }
1438 } finally {
1439 Binder.restoreCallingIdentity(identity);
1440 }
1441 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001442
1443 @Override
Jae Seoa826d012016-01-18 13:03:35 -08001444 public void timeShiftPlay(IBinder sessionToken, final Uri recordedProgramUri, int userId) {
1445 final int callingUid = Binder.getCallingUid();
1446 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1447 userId, "timeShiftPlay");
1448 final long identity = Binder.clearCallingIdentity();
1449 try {
1450 synchronized (mLock) {
1451 try {
1452 getSessionLocked(sessionToken, callingUid, resolvedUserId).timeShiftPlay(
1453 recordedProgramUri);
1454 } catch (RemoteException | SessionNotFoundException e) {
1455 Slog.e(TAG, "error in timeShiftPlay", e);
1456 }
1457 }
1458 } finally {
1459 Binder.restoreCallingIdentity(identity);
1460 }
1461 }
1462
1463 @Override
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001464 public void timeShiftPause(IBinder sessionToken, int userId) {
1465 final int callingUid = Binder.getCallingUid();
1466 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1467 userId, "timeShiftPause");
1468 final long identity = Binder.clearCallingIdentity();
1469 try {
1470 synchronized (mLock) {
1471 try {
Jae Seoa826d012016-01-18 13:03:35 -08001472 getSessionLocked(sessionToken, callingUid, resolvedUserId).timeShiftPause();
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001473 } catch (RemoteException | SessionNotFoundException e) {
1474 Slog.e(TAG, "error in timeShiftPause", e);
1475 }
1476 }
1477 } finally {
1478 Binder.restoreCallingIdentity(identity);
1479 }
1480 }
1481
1482 @Override
1483 public void timeShiftResume(IBinder sessionToken, int userId) {
1484 final int callingUid = Binder.getCallingUid();
1485 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1486 userId, "timeShiftResume");
1487 final long identity = Binder.clearCallingIdentity();
1488 try {
1489 synchronized (mLock) {
1490 try {
1491 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1492 .timeShiftResume();
1493 } catch (RemoteException | SessionNotFoundException e) {
1494 Slog.e(TAG, "error in timeShiftResume", e);
1495 }
1496 }
1497 } finally {
1498 Binder.restoreCallingIdentity(identity);
1499 }
1500 }
1501
1502 @Override
1503 public void timeShiftSeekTo(IBinder sessionToken, long timeMs, int userId) {
1504 final int callingUid = Binder.getCallingUid();
1505 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1506 userId, "timeShiftSeekTo");
1507 final long identity = Binder.clearCallingIdentity();
1508 try {
1509 synchronized (mLock) {
1510 try {
1511 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1512 .timeShiftSeekTo(timeMs);
1513 } catch (RemoteException | SessionNotFoundException e) {
1514 Slog.e(TAG, "error in timeShiftSeekTo", e);
1515 }
1516 }
1517 } finally {
1518 Binder.restoreCallingIdentity(identity);
1519 }
1520 }
1521
1522 @Override
Jae Seo4b34cc72015-05-15 17:29:39 -07001523 public void timeShiftSetPlaybackParams(IBinder sessionToken, PlaybackParams params,
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001524 int userId) {
1525 final int callingUid = Binder.getCallingUid();
1526 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
Jae Seo4b34cc72015-05-15 17:29:39 -07001527 userId, "timeShiftSetPlaybackParams");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001528 final long identity = Binder.clearCallingIdentity();
1529 try {
1530 synchronized (mLock) {
1531 try {
1532 getSessionLocked(sessionToken, callingUid, resolvedUserId)
Jae Seo4b34cc72015-05-15 17:29:39 -07001533 .timeShiftSetPlaybackParams(params);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001534 } catch (RemoteException | SessionNotFoundException e) {
Jae Seo4b34cc72015-05-15 17:29:39 -07001535 Slog.e(TAG, "error in timeShiftSetPlaybackParams", e);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001536 }
1537 }
1538 } finally {
1539 Binder.restoreCallingIdentity(identity);
1540 }
1541 }
1542
1543 @Override
Jae Seo465f0d62015-04-06 18:40:46 -07001544 public void timeShiftEnablePositionTracking(IBinder sessionToken, boolean enable,
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001545 int userId) {
1546 final int callingUid = Binder.getCallingUid();
1547 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
Jae Seo465f0d62015-04-06 18:40:46 -07001548 userId, "timeShiftEnablePositionTracking");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001549 final long identity = Binder.clearCallingIdentity();
1550 try {
1551 synchronized (mLock) {
1552 try {
1553 getSessionLocked(sessionToken, callingUid, resolvedUserId)
Jae Seo465f0d62015-04-06 18:40:46 -07001554 .timeShiftEnablePositionTracking(enable);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001555 } catch (RemoteException | SessionNotFoundException e) {
Jae Seo465f0d62015-04-06 18:40:46 -07001556 Slog.e(TAG, "error in timeShiftEnablePositionTracking", e);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001557 }
1558 }
1559 } finally {
1560 Binder.restoreCallingIdentity(identity);
1561 }
1562 }
1563
1564 @Override
Jae Seoa826d012016-01-18 13:03:35 -08001565 public void connect(IBinder sessionToken, final Uri channelUri, Bundle params, int userId) {
1566 final int callingUid = Binder.getCallingUid();
1567 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1568 userId, "connect");
1569 final long identity = Binder.clearCallingIdentity();
1570 try {
1571 synchronized (mLock) {
1572 try {
1573 getSessionLocked(sessionToken, callingUid, resolvedUserId).connect(
1574 channelUri, params);
1575 } catch (RemoteException | SessionNotFoundException e) {
1576 Slog.e(TAG, "error in connect", e);
1577 }
1578 }
1579 } finally {
1580 Binder.restoreCallingIdentity(identity);
1581 }
1582 }
1583
1584 @Override
1585 public void startRecording(IBinder sessionToken, int userId) {
1586 final int callingUid = Binder.getCallingUid();
1587 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1588 userId, "startRecording");
1589 final long identity = Binder.clearCallingIdentity();
1590 try {
1591 synchronized (mLock) {
1592 try {
1593 getSessionLocked(sessionToken, callingUid, resolvedUserId).startRecording();
1594 } catch (RemoteException | SessionNotFoundException e) {
1595 Slog.e(TAG, "error in startRecording", e);
1596 }
1597 }
1598 } finally {
1599 Binder.restoreCallingIdentity(identity);
1600 }
1601 }
1602
1603 @Override
1604 public void stopRecording(IBinder sessionToken, int userId) {
1605 final int callingUid = Binder.getCallingUid();
1606 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1607 userId, "stopRecording");
1608 final long identity = Binder.clearCallingIdentity();
1609 try {
1610 synchronized (mLock) {
1611 try {
1612 getSessionLocked(sessionToken, callingUid, resolvedUserId).stopRecording();
1613 } catch (RemoteException | SessionNotFoundException e) {
1614 Slog.e(TAG, "error in stopRecording", e);
1615 }
1616 }
1617 } finally {
1618 Binder.restoreCallingIdentity(identity);
1619 }
1620 }
1621
1622 @Override
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001623 public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
Wonsik Kim969167d2014-06-24 16:33:17 +09001624 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001625 != PackageManager.PERMISSION_GRANTED) {
1626 return null;
1627 }
1628
1629 final long identity = Binder.clearCallingIdentity();
1630 try {
1631 return mTvInputHardwareManager.getHardwareList();
1632 } finally {
1633 Binder.restoreCallingIdentity(identity);
1634 }
1635 }
1636
1637 @Override
1638 public ITvInputHardware acquireTvInputHardware(int deviceId,
Wonsik Kim969167d2014-06-24 16:33:17 +09001639 ITvInputHardwareCallback callback, TvInputInfo info, int userId)
1640 throws RemoteException {
1641 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001642 != PackageManager.PERMISSION_GRANTED) {
1643 return null;
1644 }
1645
1646 final long identity = Binder.clearCallingIdentity();
1647 final int callingUid = Binder.getCallingUid();
1648 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1649 userId, "acquireTvInputHardware");
1650 try {
1651 return mTvInputHardwareManager.acquireHardware(
Wonsik Kim969167d2014-06-24 16:33:17 +09001652 deviceId, callback, info, callingUid, resolvedUserId);
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001653 } finally {
1654 Binder.restoreCallingIdentity(identity);
1655 }
1656 }
1657
1658 @Override
1659 public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
1660 throws RemoteException {
Wonsik Kim969167d2014-06-24 16:33:17 +09001661 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001662 != PackageManager.PERMISSION_GRANTED) {
1663 return;
1664 }
1665
1666 final long identity = Binder.clearCallingIdentity();
1667 final int callingUid = Binder.getCallingUid();
1668 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1669 userId, "releaseTvInputHardware");
1670 try {
1671 mTvInputHardwareManager.releaseHardware(
1672 deviceId, hardware, callingUid, resolvedUserId);
1673 } finally {
1674 Binder.restoreCallingIdentity(identity);
1675 }
1676 }
Jaewan Kime14c3f42014-06-27 13:47:48 +09001677
1678 @Override
Jaesung Chung58739e72015-04-24 19:39:59 +09001679 public List<DvbDeviceInfo> getDvbDeviceList() throws RemoteException {
1680 if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)
1681 != PackageManager.PERMISSION_GRANTED) {
1682 throw new SecurityException("Requires DVB_DEVICE permission");
1683 }
1684
1685 final long identity = Binder.clearCallingIdentity();
1686 try {
1687 ArrayList<DvbDeviceInfo> deviceInfos = new ArrayList<>();
1688 File devDirectory = new File("/dev");
1689 for (String fileName : devDirectory.list()) {
1690 Matcher matcher = sFrontEndDevicePattern.matcher(fileName);
1691 if (matcher.find()) {
1692 int adapterId = Integer.parseInt(matcher.group(1));
1693 int deviceId = Integer.parseInt(matcher.group(2));
1694 deviceInfos.add(new DvbDeviceInfo(adapterId, deviceId));
1695 }
1696 }
1697 return Collections.unmodifiableList(deviceInfos);
1698 } finally {
1699 Binder.restoreCallingIdentity(identity);
1700 }
1701 }
1702
1703 @Override
1704 public ParcelFileDescriptor openDvbDevice(DvbDeviceInfo info, int device)
1705 throws RemoteException {
1706 if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)
1707 != PackageManager.PERMISSION_GRANTED) {
1708 throw new SecurityException("Requires DVB_DEVICE permission");
1709 }
1710
1711 final long identity = Binder.clearCallingIdentity();
1712 try {
1713 String deviceFileName;
1714 switch (device) {
1715 case TvInputManager.DVB_DEVICE_DEMUX:
1716 deviceFileName = String.format("/dev/dvb%d.demux%d", info.getAdapterId(),
1717 info.getDeviceId());
1718 break;
1719 case TvInputManager.DVB_DEVICE_DVR:
1720 deviceFileName = String.format("/dev/dvb%d.dvr%d", info.getAdapterId(),
1721 info.getDeviceId());
1722 break;
1723 case TvInputManager.DVB_DEVICE_FRONTEND:
1724 deviceFileName = String.format("/dev/dvb%d.frontend%d", info.getAdapterId(),
1725 info.getDeviceId());
1726 break;
1727 default:
1728 throw new IllegalArgumentException("Invalid DVB device: " + device);
1729 }
1730 try {
1731 // The DVB frontend device only needs to be opened in read/write mode, which
1732 // allows performing tuning operations. The DVB demux and DVR device are enough
1733 // to be opened in read only mode.
1734 return ParcelFileDescriptor.open(new File(deviceFileName),
1735 TvInputManager.DVB_DEVICE_FRONTEND == device
1736 ? ParcelFileDescriptor.MODE_READ_WRITE
1737 : ParcelFileDescriptor.MODE_READ_ONLY);
1738 } catch (FileNotFoundException e) {
1739 return null;
1740 }
1741 } finally {
1742 Binder.restoreCallingIdentity(identity);
1743 }
1744 }
1745
1746 @Override
Terry Heoc086a3d2014-06-18 14:26:44 +09001747 public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int userId)
1748 throws RemoteException {
1749 if (mContext.checkCallingPermission(
1750 android.Manifest.permission.CAPTURE_TV_INPUT)
1751 != PackageManager.PERMISSION_GRANTED) {
1752 throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1753 }
1754
1755 final long identity = Binder.clearCallingIdentity();
1756 final int callingUid = Binder.getCallingUid();
1757 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1758 userId, "getAvailableTvStreamConfigList");
1759 try {
1760 return mTvInputHardwareManager.getAvailableTvStreamConfigList(
1761 inputId, callingUid, resolvedUserId);
1762 } finally {
1763 Binder.restoreCallingIdentity(identity);
1764 }
1765 }
1766
1767 @Override
1768 public boolean captureFrame(String inputId, Surface surface, TvStreamConfig config,
1769 int userId)
1770 throws RemoteException {
1771 if (mContext.checkCallingPermission(
1772 android.Manifest.permission.CAPTURE_TV_INPUT)
1773 != PackageManager.PERMISSION_GRANTED) {
1774 throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1775 }
1776
1777 final long identity = Binder.clearCallingIdentity();
1778 final int callingUid = Binder.getCallingUid();
1779 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1780 userId, "captureFrame");
1781 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001782 String hardwareInputId = null;
Terry Heo79124a72014-07-21 15:17:17 +09001783 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001784 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001785 if (userState.inputMap.get(inputId) == null) {
Jae Seofea8dd42014-08-26 13:57:41 -07001786 Slog.e(TAG, "input not found for " + inputId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001787 return false;
1788 }
1789 for (SessionState sessionState : userState.sessionStateMap.values()) {
Jae Seo2cdb05e2016-02-04 22:17:13 +09001790 if (sessionState.inputId.equals(inputId)
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001791 && sessionState.hardwareSessionToken != null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001792 hardwareInputId = userState.sessionStateMap.get(
Jae Seo2cdb05e2016-02-04 22:17:13 +09001793 sessionState.hardwareSessionToken).inputId;
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001794 break;
1795 }
1796 }
Terry Heo79124a72014-07-21 15:17:17 +09001797 }
Terry Heoc086a3d2014-06-18 14:26:44 +09001798 return mTvInputHardwareManager.captureFrame(
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001799 (hardwareInputId != null) ? hardwareInputId : inputId,
Terry Heo79124a72014-07-21 15:17:17 +09001800 surface, config, callingUid, resolvedUserId);
Terry Heoc086a3d2014-06-18 14:26:44 +09001801 } finally {
1802 Binder.restoreCallingIdentity(identity);
1803 }
1804 }
1805
1806 @Override
Terry Heodf9f0a32014-08-06 13:53:33 +09001807 public boolean isSingleSessionActive(int userId) throws RemoteException {
1808 final long identity = Binder.clearCallingIdentity();
1809 final int callingUid = Binder.getCallingUid();
1810 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1811 userId, "isSingleSessionActive");
1812 try {
1813 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001814 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Terry Heodf9f0a32014-08-06 13:53:33 +09001815 if (userState.sessionStateMap.size() == 1) {
1816 return true;
Jae Seo93ff14b2015-06-21 14:08:54 -07001817 } else if (userState.sessionStateMap.size() == 2) {
Terry Heodf9f0a32014-08-06 13:53:33 +09001818 SessionState[] sessionStates = userState.sessionStateMap.values().toArray(
Jae Seo93ff14b2015-06-21 14:08:54 -07001819 new SessionState[2]);
Terry Heodf9f0a32014-08-06 13:53:33 +09001820 // Check if there is a wrapper input.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001821 if (sessionStates[0].hardwareSessionToken != null
1822 || sessionStates[1].hardwareSessionToken != null) {
Terry Heodf9f0a32014-08-06 13:53:33 +09001823 return true;
1824 }
1825 }
1826 return false;
1827 }
1828 } finally {
1829 Binder.restoreCallingIdentity(identity);
1830 }
1831 }
1832
1833 @Override
Jae Seo0f8fc342014-07-02 10:47:08 -07001834 @SuppressWarnings("resource")
Jaewan Kime14c3f42014-06-27 13:47:48 +09001835 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1836 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
Jae Seo0f8fc342014-07-02 10:47:08 -07001837 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
Jaewan Kime14c3f42014-06-27 13:47:48 +09001838 != PackageManager.PERMISSION_GRANTED) {
Jae Seo0f8fc342014-07-02 10:47:08 -07001839 pw.println("Permission Denial: can't dump TvInputManager from pid="
1840 + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
Jaewan Kime14c3f42014-06-27 13:47:48 +09001841 return;
1842 }
1843
1844 synchronized (mLock) {
1845 pw.println("User Ids (Current user: " + mCurrentUserId + "):");
1846 pw.increaseIndent();
1847 for (int i = 0; i < mUserStates.size(); i++) {
1848 int userId = mUserStates.keyAt(i);
1849 pw.println(Integer.valueOf(userId));
1850 }
1851 pw.decreaseIndent();
1852
1853 for (int i = 0; i < mUserStates.size(); i++) {
1854 int userId = mUserStates.keyAt(i);
Jae Seo4f1a6d42015-07-20 16:15:01 -07001855 UserState userState = getOrCreateUserStateLocked(userId);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001856 pw.println("UserState (" + userId + "):");
1857 pw.increaseIndent();
1858
Wonsik Kim969167d2014-06-24 16:33:17 +09001859 pw.println("inputMap: inputId -> TvInputState");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001860 pw.increaseIndent();
Jaewan Kim8e6b51b2014-07-15 13:01:57 +09001861 for (Map.Entry<String, TvInputState> entry: userState.inputMap.entrySet()) {
1862 pw.println(entry.getKey() + ": " + entry.getValue());
Jaewan Kime14c3f42014-06-27 13:47:48 +09001863 }
1864 pw.decreaseIndent();
1865
Wonsik Kim969167d2014-06-24 16:33:17 +09001866 pw.println("packageSet:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001867 pw.increaseIndent();
Wonsik Kim969167d2014-06-24 16:33:17 +09001868 for (String packageName : userState.packageSet) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09001869 pw.println(packageName);
1870 }
1871 pw.decreaseIndent();
1872
1873 pw.println("clientStateMap: ITvInputClient -> ClientState");
1874 pw.increaseIndent();
1875 for (Map.Entry<IBinder, ClientState> entry :
1876 userState.clientStateMap.entrySet()) {
1877 ClientState client = entry.getValue();
1878 pw.println(entry.getKey() + ": " + client);
1879
1880 pw.increaseIndent();
1881
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001882 pw.println("sessionTokens:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001883 pw.increaseIndent();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001884 for (IBinder token : client.sessionTokens) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09001885 pw.println("" + token);
1886 }
1887 pw.decreaseIndent();
1888
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001889 pw.println("clientTokens: " + client.clientToken);
1890 pw.println("userId: " + client.userId);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001891
1892 pw.decreaseIndent();
1893 }
1894 pw.decreaseIndent();
1895
Wonsik Kim187423c2014-06-25 14:12:48 +09001896 pw.println("serviceStateMap: ComponentName -> ServiceState");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001897 pw.increaseIndent();
Wonsik Kim187423c2014-06-25 14:12:48 +09001898 for (Map.Entry<ComponentName, ServiceState> entry :
Jaewan Kime14c3f42014-06-27 13:47:48 +09001899 userState.serviceStateMap.entrySet()) {
1900 ServiceState service = entry.getValue();
1901 pw.println(entry.getKey() + ": " + service);
1902
1903 pw.increaseIndent();
1904
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001905 pw.println("sessionTokens:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001906 pw.increaseIndent();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001907 for (IBinder token : service.sessionTokens) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09001908 pw.println("" + token);
1909 }
1910 pw.decreaseIndent();
1911
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001912 pw.println("service: " + service.service);
1913 pw.println("callback: " + service.callback);
1914 pw.println("bound: " + service.bound);
1915 pw.println("reconnecting: " + service.reconnecting);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001916
1917 pw.decreaseIndent();
1918 }
1919 pw.decreaseIndent();
1920
1921 pw.println("sessionStateMap: ITvInputSession -> SessionState");
1922 pw.increaseIndent();
1923 for (Map.Entry<IBinder, SessionState> entry :
1924 userState.sessionStateMap.entrySet()) {
1925 SessionState session = entry.getValue();
1926 pw.println(entry.getKey() + ": " + session);
1927
1928 pw.increaseIndent();
Jae Seo2cdb05e2016-02-04 22:17:13 +09001929 pw.println("inputId: " + session.inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001930 pw.println("client: " + session.client);
1931 pw.println("seq: " + session.seq);
1932 pw.println("callingUid: " + session.callingUid);
1933 pw.println("userId: " + session.userId);
1934 pw.println("sessionToken: " + session.sessionToken);
1935 pw.println("session: " + session.session);
1936 pw.println("logUri: " + session.logUri);
1937 pw.println("hardwareSessionToken: " + session.hardwareSessionToken);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001938 pw.decreaseIndent();
1939 }
1940 pw.decreaseIndent();
1941
Wonsik Kim969167d2014-06-24 16:33:17 +09001942 pw.println("callbackSet:");
1943 pw.increaseIndent();
1944 for (ITvInputManagerCallback callback : userState.callbackSet) {
1945 pw.println(callback.toString());
1946 }
1947 pw.decreaseIndent();
1948
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001949 pw.println("mainSessionToken: " + userState.mainSessionToken);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001950 pw.decreaseIndent();
1951 }
1952 }
1953 }
Jae Seo39570912014-02-20 18:23:25 -08001954 }
1955
Wonsik Kim969167d2014-06-24 16:33:17 +09001956 private static final class UserState {
1957 // A mapping from the TV input id to its TvInputState.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001958 private Map<String, TvInputState> inputMap = new HashMap<>();
Wonsik Kim969167d2014-06-24 16:33:17 +09001959
1960 // A set of all TV input packages.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001961 private final Set<String> packageSet = new HashSet<>();
Jae Seo5c80ad22014-06-12 19:52:58 -07001962
Jae Seo9c165d62014-08-25 14:39:26 -07001963 // A list of all TV content rating systems defined.
1964 private final List<TvContentRatingSystemInfo>
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001965 contentRatingSystemList = new ArrayList<>();
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +09001966
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001967 // A mapping from the token of a client to its state.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001968 private final Map<IBinder, ClientState> clientStateMap = new HashMap<>();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001969
Jae Seo39570912014-02-20 18:23:25 -08001970 // A mapping from the name of a TV input service to its state.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001971 private final Map<ComponentName, ServiceState> serviceStateMap = new HashMap<>();
Jae Seo39570912014-02-20 18:23:25 -08001972
1973 // A mapping from the token of a TV input session to its state.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001974 private final Map<IBinder, SessionState> sessionStateMap = new HashMap<>();
Wonsik Kim969167d2014-06-24 16:33:17 +09001975
1976 // A set of callbacks.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001977 private final Set<ITvInputManagerCallback> callbackSet = new HashSet<>();
Terry Heo79124a72014-07-21 15:17:17 +09001978
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001979 // The token of a "main" TV input session.
1980 private IBinder mainSessionToken = null;
Jae Seo783645e2014-07-28 17:30:50 +09001981
1982 // Persistent data store for all internal settings maintained by the TV input manager
1983 // service.
1984 private final PersistentDataStore persistentDataStore;
1985
1986 private UserState(Context context, int userId) {
1987 persistentDataStore = new PersistentDataStore(context, userId);
1988 }
Jae Seo39570912014-02-20 18:23:25 -08001989 }
1990
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001991 private final class ClientState implements IBinder.DeathRecipient {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001992 private final List<IBinder> sessionTokens = new ArrayList<>();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001993
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001994 private IBinder clientToken;
1995 private final int userId;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001996
1997 ClientState(IBinder clientToken, int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001998 this.clientToken = clientToken;
1999 this.userId = userId;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002000 }
2001
2002 public boolean isEmpty() {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002003 return sessionTokens.isEmpty();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002004 }
2005
2006 @Override
2007 public void binderDied() {
2008 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002009 UserState userState = getOrCreateUserStateLocked(userId);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002010 // DO NOT remove the client state of clientStateMap in this method. It will be
Ji-Hwan Leea65118e2014-07-24 16:30:02 +09002011 // removed in releaseSessionLocked().
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002012 ClientState clientState = userState.clientStateMap.get(clientToken);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002013 if (clientState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002014 while (clientState.sessionTokens.size() > 0) {
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002015 releaseSessionLocked(
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002016 clientState.sessionTokens.get(0), Process.SYSTEM_UID, userId);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002017 }
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002018 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002019 clientToken = null;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002020 }
2021 }
2022 }
2023
Jae Seo39570912014-02-20 18:23:25 -08002024 private final class ServiceState {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002025 private final List<IBinder> sessionTokens = new ArrayList<>();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002026 private final ServiceConnection connection;
2027 private final ComponentName component;
2028 private final boolean isHardware;
Jae Seo1abbbcd2016-01-28 22:20:41 -08002029 private final List<TvInputInfo> hardwareInputList = new ArrayList<>();
Jae Seo39570912014-02-20 18:23:25 -08002030
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002031 private ITvInputService service;
2032 private ServiceCallback callback;
2033 private boolean bound;
2034 private boolean reconnecting;
Jae Seo39570912014-02-20 18:23:25 -08002035
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002036 private ServiceState(ComponentName component, int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002037 this.component = component;
2038 this.connection = new InputServiceConnection(component, userId);
2039 this.isHardware = hasHardwarePermission(mContext.getPackageManager(), component);
2040 }
2041 }
2042
2043 private static final class TvInputState {
2044 // A TvInputInfo object which represents the TV input.
2045 private TvInputInfo info;
2046
2047 // The state of TV input. Connected by default.
2048 private int state = INPUT_STATE_CONNECTED;
2049
2050 @Override
2051 public String toString() {
2052 return "info: " + info + "; state: " + state;
Jae Seo39570912014-02-20 18:23:25 -08002053 }
2054 }
2055
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002056 private final class SessionState implements IBinder.DeathRecipient {
Jae Seo2cdb05e2016-02-04 22:17:13 +09002057 private final String inputId;
2058 private final ComponentName componentName;
Jae Seoa826d012016-01-18 13:03:35 -08002059 private final boolean isRecordingSession;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002060 private final ITvInputClient client;
2061 private final int seq;
2062 private final int callingUid;
2063 private final int userId;
2064 private final IBinder sessionToken;
2065 private ITvInputSession session;
2066 private Uri logUri;
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09002067 // Not null if this session represents an external device connected to a hardware TV input.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002068 private IBinder hardwareSessionToken;
Jae Seo39570912014-02-20 18:23:25 -08002069
Jae Seo2cdb05e2016-02-04 22:17:13 +09002070 private SessionState(IBinder sessionToken, String inputId, ComponentName componentName,
2071 boolean isRecordingSession, ITvInputClient client, int seq, int callingUid,
2072 int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002073 this.sessionToken = sessionToken;
Jae Seo2cdb05e2016-02-04 22:17:13 +09002074 this.inputId = inputId;
2075 this.componentName = componentName;
Jae Seoa826d012016-01-18 13:03:35 -08002076 this.isRecordingSession = isRecordingSession;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002077 this.client = client;
2078 this.seq = seq;
2079 this.callingUid = callingUid;
2080 this.userId = userId;
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002081 }
2082
2083 @Override
2084 public void binderDied() {
2085 synchronized (mLock) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002086 session = null;
2087 if (client != null) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002088 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002089 client.onSessionReleased(seq);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002090 } catch(RemoteException e) {
2091 Slog.e(TAG, "error in onSessionReleased", e);
2092 }
2093 }
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09002094 // If there are any other sessions based on this session, they should be released.
Jae Seo4f1a6d42015-07-20 16:15:01 -07002095 UserState userState = getOrCreateUserStateLocked(userId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09002096 for (SessionState sessionState : userState.sessionStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002097 if (sessionToken == sessionState.hardwareSessionToken) {
2098 releaseSessionLocked(sessionState.sessionToken, Process.SYSTEM_UID,
2099 userId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09002100 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002101 sessionState.client.onSessionReleased(sessionState.seq);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09002102 } catch (RemoteException e) {
2103 Slog.e(TAG, "error in onSessionReleased", e);
2104 }
2105 }
2106 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002107 removeSessionStateLocked(sessionToken, userId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002108 }
Jae Seo39570912014-02-20 18:23:25 -08002109 }
2110 }
2111
2112 private final class InputServiceConnection implements ServiceConnection {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002113 private final ComponentName mComponent;
Jae Seo39570912014-02-20 18:23:25 -08002114 private final int mUserId;
2115
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002116 private InputServiceConnection(ComponentName component, int userId) {
2117 mComponent = component;
Jae Seo39570912014-02-20 18:23:25 -08002118 mUserId = userId;
2119 }
2120
2121 @Override
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002122 public void onServiceConnected(ComponentName component, IBinder service) {
Jae Seo39570912014-02-20 18:23:25 -08002123 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002124 Slog.d(TAG, "onServiceConnected(component=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -08002125 }
2126 synchronized (mLock) {
Dongwon Kang81e3c3e2015-09-11 15:24:25 -07002127 UserState userState = mUserStates.get(mUserId);
2128 if (userState == null) {
2129 // The user was removed while connecting.
2130 mContext.unbindService(this);
2131 return;
2132 }
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002133 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002134 serviceState.service = ITvInputService.Stub.asInterface(service);
Jae Seo39570912014-02-20 18:23:25 -08002135
2136 // Register a callback, if we need to.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002137 if (serviceState.isHardware && serviceState.callback == null) {
2138 serviceState.callback = new ServiceCallback(mComponent, mUserId);
Jae Seo39570912014-02-20 18:23:25 -08002139 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002140 serviceState.service.registerCallback(serviceState.callback);
Jae Seo39570912014-02-20 18:23:25 -08002141 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09002142 Slog.e(TAG, "error in registerCallback", e);
Jae Seo39570912014-02-20 18:23:25 -08002143 }
2144 }
2145
2146 // And create sessions, if any.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002147 for (IBinder sessionToken : serviceState.sessionTokens) {
2148 createSessionInternalLocked(serviceState.service, sessionToken, mUserId);
Jae Seo39570912014-02-20 18:23:25 -08002149 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002150
Wonsik Kim187423c2014-06-25 14:12:48 +09002151 for (TvInputState inputState : userState.inputMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002152 if (inputState.info.getComponent().equals(component)
Jae Seo82fce642015-04-20 15:37:50 -07002153 && inputState.state != INPUT_STATE_CONNECTED) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002154 notifyInputStateChangedLocked(userState, inputState.info.getId(),
2155 inputState.state, null);
Wonsik Kim187423c2014-06-25 14:12:48 +09002156 }
2157 }
2158
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002159 if (serviceState.isHardware) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002160 List<TvInputHardwareInfo> hardwareInfoList =
2161 mTvInputHardwareManager.getHardwareList();
Wonsik Kim187423c2014-06-25 14:12:48 +09002162 for (TvInputHardwareInfo hardwareInfo : hardwareInfoList) {
2163 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002164 serviceState.service.notifyHardwareAdded(hardwareInfo);
Wonsik Kim187423c2014-06-25 14:12:48 +09002165 } catch (RemoteException e) {
2166 Slog.e(TAG, "error in notifyHardwareAdded", e);
2167 }
2168 }
2169
Jae Seo546c6352014-08-07 11:57:01 -07002170 List<HdmiDeviceInfo> deviceInfoList =
2171 mTvInputHardwareManager.getHdmiDeviceList();
2172 for (HdmiDeviceInfo deviceInfo : deviceInfoList) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002173 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002174 serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002175 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002176 Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002177 }
2178 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002179 }
Jae Seo39570912014-02-20 18:23:25 -08002180 }
2181 }
2182
2183 @Override
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002184 public void onServiceDisconnected(ComponentName component) {
Jae Seo39570912014-02-20 18:23:25 -08002185 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002186 Slog.d(TAG, "onServiceDisconnected(component=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -08002187 }
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002188 if (!mComponent.equals(component)) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002189 throw new IllegalArgumentException("Mismatched ComponentName: "
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002190 + mComponent + " (expected), " + component + " (actual).");
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002191 }
2192 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002193 UserState userState = getOrCreateUserStateLocked(mUserId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002194 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002195 if (serviceState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002196 serviceState.reconnecting = true;
2197 serviceState.bound = false;
2198 serviceState.service = null;
2199 serviceState.callback = null;
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002200
Dongwon Kang426c9a42014-08-26 17:39:21 +09002201 abortPendingCreateSessionRequestsLocked(serviceState, null, mUserId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002202 }
2203 }
Jae Seo39570912014-02-20 18:23:25 -08002204 }
2205 }
2206
2207 private final class ServiceCallback extends ITvInputServiceCallback.Stub {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002208 private final ComponentName mComponent;
Jae Seo39570912014-02-20 18:23:25 -08002209 private final int mUserId;
2210
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002211 ServiceCallback(ComponentName component, int userId) {
2212 mComponent = component;
Jae Seo39570912014-02-20 18:23:25 -08002213 mUserId = userId;
2214 }
2215
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002216 private void ensureHardwarePermission() {
2217 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
2218 != PackageManager.PERMISSION_GRANTED) {
2219 throw new SecurityException("The caller does not have hardware permission");
2220 }
2221 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002222
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002223 private void ensureValidInput(TvInputInfo inputInfo) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002224 if (inputInfo.getId() == null || !mComponent.equals(inputInfo.getComponent())) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002225 throw new IllegalArgumentException("Invalid TvInputInfo");
2226 }
2227 }
2228
Jae Seo1abbbcd2016-01-28 22:20:41 -08002229 private void addHardwareInputLocked(TvInputInfo inputInfo) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002230 ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
Jae Seo1abbbcd2016-01-28 22:20:41 -08002231 serviceState.hardwareInputList.add(inputInfo);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +09002232 buildTvInputListLocked(mUserId, null);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002233 }
2234
Jae Seo1abbbcd2016-01-28 22:20:41 -08002235 public void addHardwareInput(int deviceId, TvInputInfo inputInfo) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002236 ensureHardwarePermission();
2237 ensureValidInput(inputInfo);
2238 synchronized (mLock) {
Jae Seo1abbbcd2016-01-28 22:20:41 -08002239 mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo);
2240 addHardwareInputLocked(inputInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002241 }
2242 }
2243
Jae Seo1abbbcd2016-01-28 22:20:41 -08002244 public void addHdmiInput(int id, TvInputInfo inputInfo) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002245 ensureHardwarePermission();
2246 ensureValidInput(inputInfo);
2247 synchronized (mLock) {
Jae Seo1abbbcd2016-01-28 22:20:41 -08002248 mTvInputHardwareManager.addHdmiInput(id, inputInfo);
2249 addHardwareInputLocked(inputInfo);
Wonsik Kim187423c2014-06-25 14:12:48 +09002250 }
2251 }
2252
Jae Seo1abbbcd2016-01-28 22:20:41 -08002253 public void removeHardwareInput(String inputId) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002254 ensureHardwarePermission();
Wonsik Kim187423c2014-06-25 14:12:48 +09002255 synchronized (mLock) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002256 ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002257 boolean removed = false;
Jae Seo1abbbcd2016-01-28 22:20:41 -08002258 for (Iterator<TvInputInfo> it = serviceState.hardwareInputList.iterator();
Wonsik Kim187423c2014-06-25 14:12:48 +09002259 it.hasNext(); ) {
2260 if (it.next().getId().equals(inputId)) {
2261 it.remove();
2262 removed = true;
2263 break;
2264 }
2265 }
2266 if (removed) {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +09002267 buildTvInputListLocked(mUserId, null);
Jae Seo1abbbcd2016-01-28 22:20:41 -08002268 mTvInputHardwareManager.removeHardwareInput(inputId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002269 } else {
Jae Seofea8dd42014-08-26 13:57:41 -07002270 Slog.e(TAG, "failed to remove input " + inputId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002271 }
Jae Seo39570912014-02-20 18:23:25 -08002272 }
2273 }
2274 }
Jae Seo31dc634be2014-04-15 17:40:23 -07002275
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002276 private final class SessionCallback extends ITvInputSessionCallback.Stub {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002277 private final SessionState mSessionState;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002278 private final InputChannel[] mChannels;
2279
2280 SessionCallback(SessionState sessionState, InputChannel[] channels) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002281 mSessionState = sessionState;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002282 mChannels = channels;
2283 }
2284
2285 @Override
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002286 public void onSessionCreated(ITvInputSession session, IBinder hardwareSessionToken) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002287 if (DEBUG) {
Jae Seo2cdb05e2016-02-04 22:17:13 +09002288 Slog.d(TAG, "onSessionCreated(inputId=" + mSessionState.inputId + ")");
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002289 }
2290 synchronized (mLock) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002291 mSessionState.session = session;
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002292 mSessionState.hardwareSessionToken = hardwareSessionToken;
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002293 if (session != null && addSessionTokenToClientStateLocked(session)) {
2294 sendSessionTokenToClientLocked(mSessionState.client,
Jae Seo2cdb05e2016-02-04 22:17:13 +09002295 mSessionState.inputId, mSessionState.sessionToken, mChannels[0],
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002296 mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002297 } else {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002298 removeSessionStateLocked(mSessionState.sessionToken, mSessionState.userId);
2299 sendSessionTokenToClientLocked(mSessionState.client,
Jae Seo2cdb05e2016-02-04 22:17:13 +09002300 mSessionState.inputId, null, null, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002301 }
2302 mChannels[0].dispose();
2303 }
2304 }
2305
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002306 private boolean addSessionTokenToClientStateLocked(ITvInputSession session) {
2307 try {
2308 session.asBinder().linkToDeath(mSessionState, 0);
2309 } catch (RemoteException e) {
2310 Slog.e(TAG, "session process has already died", e);
2311 return false;
2312 }
2313
2314 IBinder clientToken = mSessionState.client.asBinder();
Jae Seo4f1a6d42015-07-20 16:15:01 -07002315 UserState userState = getOrCreateUserStateLocked(mSessionState.userId);
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002316 ClientState clientState = userState.clientStateMap.get(clientToken);
2317 if (clientState == null) {
2318 clientState = new ClientState(clientToken, mSessionState.userId);
2319 try {
2320 clientToken.linkToDeath(clientState, 0);
2321 } catch (RemoteException e) {
2322 Slog.e(TAG, "client process has already died", e);
2323 return false;
2324 }
2325 userState.clientStateMap.put(clientToken, clientState);
2326 }
2327 clientState.sessionTokens.add(mSessionState.sessionToken);
2328 return true;
2329 }
2330
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002331 @Override
2332 public void onChannelRetuned(Uri channelUri) {
2333 synchronized (mLock) {
2334 if (DEBUG) {
2335 Slog.d(TAG, "onChannelRetuned(" + channelUri + ")");
2336 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002337 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002338 return;
2339 }
2340 try {
2341 // TODO: Consider adding this channel change in the watch log. When we do
2342 // that, how we can protect the watch log from malicious tv inputs should
2343 // be addressed. e.g. add a field which represents where the channel change
2344 // originated from.
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002345 mSessionState.client.onChannelRetuned(channelUri, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002346 } catch (RemoteException e) {
2347 Slog.e(TAG, "error in onChannelRetuned", e);
2348 }
2349 }
2350 }
2351
2352 @Override
2353 public void onTracksChanged(List<TvTrackInfo> tracks) {
2354 synchronized (mLock) {
2355 if (DEBUG) {
2356 Slog.d(TAG, "onTracksChanged(" + tracks + ")");
2357 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002358 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002359 return;
2360 }
2361 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002362 mSessionState.client.onTracksChanged(tracks, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002363 } catch (RemoteException e) {
2364 Slog.e(TAG, "error in onTracksChanged", e);
2365 }
2366 }
2367 }
2368
2369 @Override
2370 public void onTrackSelected(int type, String trackId) {
2371 synchronized (mLock) {
2372 if (DEBUG) {
2373 Slog.d(TAG, "onTrackSelected(type=" + type + ", trackId=" + trackId + ")");
2374 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002375 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002376 return;
2377 }
2378 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002379 mSessionState.client.onTrackSelected(type, trackId, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002380 } catch (RemoteException e) {
2381 Slog.e(TAG, "error in onTrackSelected", e);
2382 }
2383 }
2384 }
2385
2386 @Override
2387 public void onVideoAvailable() {
2388 synchronized (mLock) {
2389 if (DEBUG) {
2390 Slog.d(TAG, "onVideoAvailable()");
2391 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002392 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002393 return;
2394 }
2395 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002396 mSessionState.client.onVideoAvailable(mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002397 } catch (RemoteException e) {
2398 Slog.e(TAG, "error in onVideoAvailable", e);
2399 }
2400 }
2401 }
2402
2403 @Override
2404 public void onVideoUnavailable(int reason) {
2405 synchronized (mLock) {
2406 if (DEBUG) {
2407 Slog.d(TAG, "onVideoUnavailable(" + reason + ")");
2408 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002409 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002410 return;
2411 }
2412 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002413 mSessionState.client.onVideoUnavailable(reason, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002414 } catch (RemoteException e) {
2415 Slog.e(TAG, "error in onVideoUnavailable", e);
2416 }
2417 }
2418 }
2419
2420 @Override
2421 public void onContentAllowed() {
2422 synchronized (mLock) {
2423 if (DEBUG) {
2424 Slog.d(TAG, "onContentAllowed()");
2425 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002426 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002427 return;
2428 }
2429 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002430 mSessionState.client.onContentAllowed(mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002431 } catch (RemoteException e) {
2432 Slog.e(TAG, "error in onContentAllowed", e);
2433 }
2434 }
2435 }
2436
2437 @Override
2438 public void onContentBlocked(String rating) {
2439 synchronized (mLock) {
2440 if (DEBUG) {
2441 Slog.d(TAG, "onContentBlocked()");
2442 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002443 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002444 return;
2445 }
2446 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002447 mSessionState.client.onContentBlocked(rating, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002448 } catch (RemoteException e) {
2449 Slog.e(TAG, "error in onContentBlocked", e);
2450 }
2451 }
2452 }
2453
2454 @Override
2455 public void onLayoutSurface(int left, int top, int right, int bottom) {
2456 synchronized (mLock) {
2457 if (DEBUG) {
2458 Slog.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top
2459 + ", right=" + right + ", bottom=" + bottom + ",)");
2460 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002461 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002462 return;
2463 }
2464 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002465 mSessionState.client.onLayoutSurface(left, top, right, bottom,
2466 mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002467 } catch (RemoteException e) {
2468 Slog.e(TAG, "error in onLayoutSurface", e);
2469 }
2470 }
2471 }
2472
2473 @Override
2474 public void onSessionEvent(String eventType, Bundle eventArgs) {
2475 synchronized (mLock) {
2476 if (DEBUG) {
2477 Slog.d(TAG, "onEvent(what=" + eventType + ", data=" + eventArgs + ")");
2478 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002479 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002480 return;
2481 }
2482 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002483 mSessionState.client.onSessionEvent(eventType, eventArgs, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002484 } catch (RemoteException e) {
2485 Slog.e(TAG, "error in onSessionEvent", e);
2486 }
2487 }
2488 }
Dongwon Kang6f0240c2015-03-31 17:56:36 -07002489
2490 @Override
2491 public void onTimeShiftStatusChanged(int status) {
2492 synchronized (mLock) {
2493 if (DEBUG) {
2494 Slog.d(TAG, "onTimeShiftStatusChanged()");
2495 }
2496 if (mSessionState.session == null || mSessionState.client == null) {
2497 return;
2498 }
2499 try {
2500 mSessionState.client.onTimeShiftStatusChanged(status, mSessionState.seq);
2501 } catch (RemoteException e) {
2502 Slog.e(TAG, "error in onTimeShiftStatusChanged", e);
2503 }
2504 }
2505 }
2506
2507 @Override
2508 public void onTimeShiftStartPositionChanged(long timeMs) {
2509 synchronized (mLock) {
2510 if (DEBUG) {
2511 Slog.d(TAG, "onTimeShiftStartPositionChanged()");
2512 }
2513 if (mSessionState.session == null || mSessionState.client == null) {
2514 return;
2515 }
2516 try {
2517 mSessionState.client.onTimeShiftStartPositionChanged(timeMs, mSessionState.seq);
2518 } catch (RemoteException e) {
2519 Slog.e(TAG, "error in onTimeShiftStartPositionChanged", e);
2520 }
2521 }
2522 }
2523
2524 @Override
2525 public void onTimeShiftCurrentPositionChanged(long timeMs) {
2526 synchronized (mLock) {
2527 if (DEBUG) {
2528 Slog.d(TAG, "onTimeShiftCurrentPositionChanged()");
2529 }
2530 if (mSessionState.session == null || mSessionState.client == null) {
2531 return;
2532 }
2533 try {
2534 mSessionState.client.onTimeShiftCurrentPositionChanged(timeMs,
2535 mSessionState.seq);
2536 } catch (RemoteException e) {
2537 Slog.e(TAG, "error in onTimeShiftCurrentPositionChanged", e);
2538 }
2539 }
2540 }
Jae Seoa826d012016-01-18 13:03:35 -08002541
2542 // For the recording session only
2543 @Override
2544 public void onConnected() {
2545 synchronized (mLock) {
2546 if (DEBUG) {
2547 Slog.d(TAG, "onConnected()");
2548 }
2549 if (mSessionState.session == null || mSessionState.client == null) {
2550 return;
2551 }
2552 try {
2553 mSessionState.client.onConnected(mSessionState.seq);
2554 } catch (RemoteException e) {
2555 Slog.e(TAG, "error in onConnected", e);
2556 }
2557 }
2558 }
2559
2560 // For the recording session only
2561 @Override
2562 public void onRecordingStarted() {
2563 synchronized (mLock) {
2564 if (DEBUG) {
2565 Slog.d(TAG, "onRecordingStarted()");
2566 }
2567 if (mSessionState.session == null || mSessionState.client == null) {
2568 return;
2569 }
2570 try {
2571 mSessionState.client.onRecordingStarted(mSessionState.seq);
2572 } catch (RemoteException e) {
2573 Slog.e(TAG, "error in onRecordingStarted", e);
2574 }
2575 }
2576 }
2577
2578 // For the recording session only
2579 @Override
2580 public void onRecordingStopped(Uri recordedProgramUri) {
2581 synchronized (mLock) {
2582 if (DEBUG) {
2583 Slog.d(TAG, "onRecordingStopped()");
2584 }
2585 if (mSessionState.session == null || mSessionState.client == null) {
2586 return;
2587 }
2588 try {
2589 mSessionState.client.onRecordingStopped(recordedProgramUri, mSessionState.seq);
2590 } catch (RemoteException e) {
2591 Slog.e(TAG, "error in onRecordingStopped", e);
2592 }
2593 }
2594 }
2595
2596 // For the recording session only
2597 @Override
2598 public void onError(int error) {
2599 synchronized (mLock) {
2600 if (DEBUG) {
2601 Slog.d(TAG, "onError()");
2602 }
2603 if (mSessionState.session == null || mSessionState.client == null) {
2604 return;
2605 }
2606 try {
2607 mSessionState.client.onError(error, mSessionState.seq);
2608 } catch (RemoteException e) {
2609 Slog.e(TAG, "error in onError", e);
2610 }
2611 }
2612 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002613 }
2614
2615 private static final class WatchLogHandler extends Handler {
Jae Seo7eb75df2014-08-08 22:20:48 -07002616 // There are only two kinds of watch events that can happen on the system:
2617 // 1. The current TV input session is tuned to a new channel.
2618 // 2. The session is released for some reason.
2619 // The former indicates the end of the previous log entry, if any, followed by the start of
2620 // a new entry. The latter indicates the end of the most recent entry for the given session.
2621 // Here the system supplies the database the smallest set of information only that is
2622 // sufficient to consolidate the log entries while minimizing database operations in the
2623 // system service.
Jae Seo8c375fe2015-06-23 20:33:25 -07002624 static final int MSG_LOG_WATCH_START = 1;
2625 static final int MSG_LOG_WATCH_END = 2;
2626 static final int MSG_SWITCH_CONTENT_RESOLVER = 3;
Jae Seo31dc634be2014-04-15 17:40:23 -07002627
Jae Seo8c375fe2015-06-23 20:33:25 -07002628 private ContentResolver mContentResolver;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002629
Jae Seo8c375fe2015-06-23 20:33:25 -07002630 WatchLogHandler(ContentResolver contentResolver, Looper looper) {
Jae Seo31dc634be2014-04-15 17:40:23 -07002631 super(looper);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002632 mContentResolver = contentResolver;
Jae Seo31dc634be2014-04-15 17:40:23 -07002633 }
2634
2635 @Override
2636 public void handleMessage(Message msg) {
2637 switch (msg.what) {
Jae Seo7eb75df2014-08-08 22:20:48 -07002638 case MSG_LOG_WATCH_START: {
Jae Seo31dc634be2014-04-15 17:40:23 -07002639 SomeArgs args = (SomeArgs) msg.obj;
Jae Seo7eb75df2014-08-08 22:20:48 -07002640 String packageName = (String) args.arg1;
2641 long watchStartTime = (long) args.arg2;
2642 long channelId = (long) args.arg3;
2643 Bundle tuneParams = (Bundle) args.arg4;
2644 IBinder sessionToken = (IBinder) args.arg5;
2645
2646 ContentValues values = new ContentValues();
2647 values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
2648 values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
2649 watchStartTime);
2650 values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
2651 if (tuneParams != null) {
2652 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS,
2653 encodeTuneParams(tuneParams));
2654 }
2655 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
2656 sessionToken.toString());
2657
2658 mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
Jae Seo31dc634be2014-04-15 17:40:23 -07002659 args.recycle();
Jae Seo8c375fe2015-06-23 20:33:25 -07002660 break;
Jae Seo31dc634be2014-04-15 17:40:23 -07002661 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002662 case MSG_LOG_WATCH_END: {
Jae Seo31dc634be2014-04-15 17:40:23 -07002663 SomeArgs args = (SomeArgs) msg.obj;
Jae Seo7eb75df2014-08-08 22:20:48 -07002664 IBinder sessionToken = (IBinder) args.arg1;
2665 long watchEndTime = (long) args.arg2;
2666
2667 ContentValues values = new ContentValues();
2668 values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
2669 watchEndTime);
2670 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
2671 sessionToken.toString());
2672
2673 mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
Jae Seo31dc634be2014-04-15 17:40:23 -07002674 args.recycle();
Jae Seo8c375fe2015-06-23 20:33:25 -07002675 break;
2676 }
2677 case MSG_SWITCH_CONTENT_RESOLVER: {
2678 mContentResolver = (ContentResolver) msg.obj;
2679 break;
Jae Seo31dc634be2014-04-15 17:40:23 -07002680 }
2681 default: {
Jae Seo8c375fe2015-06-23 20:33:25 -07002682 Slog.w(TAG, "unhandled message code: " + msg.what);
2683 break;
Jae Seo31dc634be2014-04-15 17:40:23 -07002684 }
2685 }
2686 }
2687
Jae Seo7eb75df2014-08-08 22:20:48 -07002688 private String encodeTuneParams(Bundle tuneParams) {
2689 StringBuilder builder = new StringBuilder();
2690 Set<String> keySet = tuneParams.keySet();
2691 Iterator<String> it = keySet.iterator();
2692 while (it.hasNext()) {
2693 String key = it.next();
2694 Object value = tuneParams.get(key);
2695 if (value == null) {
2696 continue;
Jae Seo579befe2014-08-06 19:18:37 -07002697 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002698 builder.append(replaceEscapeCharacters(key));
2699 builder.append("=");
2700 builder.append(replaceEscapeCharacters(value.toString()));
2701 if (it.hasNext()) {
2702 builder.append(", ");
Jae Seo31dc634be2014-04-15 17:40:23 -07002703 }
2704 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002705 return builder.toString();
Jae Seo31dc634be2014-04-15 17:40:23 -07002706 }
2707
Jae Seo7eb75df2014-08-08 22:20:48 -07002708 private String replaceEscapeCharacters(String src) {
2709 final char ESCAPE_CHARACTER = '%';
2710 final String ENCODING_TARGET_CHARACTERS = "%=,";
2711 StringBuilder builder = new StringBuilder();
2712 for (char ch : src.toCharArray()) {
2713 if (ENCODING_TARGET_CHARACTERS.indexOf(ch) >= 0) {
2714 builder.append(ESCAPE_CHARACTER);
Chulwoo Lee8d4ded02014-07-10 03:56:39 +09002715 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002716 builder.append(ch);
Jae Seo31dc634be2014-04-15 17:40:23 -07002717 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002718 return builder.toString();
Jae Seo579befe2014-08-06 19:18:37 -07002719 }
Jae Seo31dc634be2014-04-15 17:40:23 -07002720 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002721
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002722 private final class HardwareListener implements TvInputHardwareManager.Listener {
Wonsik Kim187423c2014-06-25 14:12:48 +09002723 @Override
2724 public void onStateChanged(String inputId, int state) {
Wonsik Kim969167d2014-06-24 16:33:17 +09002725 synchronized (mLock) {
2726 setStateLocked(inputId, state, mCurrentUserId);
2727 }
2728 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002729
2730 @Override
2731 public void onHardwareDeviceAdded(TvInputHardwareInfo info) {
2732 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002733 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002734 // Broadcast the event to all hardware inputs.
2735 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002736 if (!serviceState.isHardware || serviceState.service == null) continue;
Wonsik Kim187423c2014-06-25 14:12:48 +09002737 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002738 serviceState.service.notifyHardwareAdded(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09002739 } catch (RemoteException e) {
2740 Slog.e(TAG, "error in notifyHardwareAdded", e);
2741 }
2742 }
2743 }
2744 }
2745
2746 @Override
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002747 public void onHardwareDeviceRemoved(TvInputHardwareInfo info) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002748 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002749 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002750 // Broadcast the event to all hardware inputs.
2751 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002752 if (!serviceState.isHardware || serviceState.service == null) continue;
Wonsik Kim187423c2014-06-25 14:12:48 +09002753 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002754 serviceState.service.notifyHardwareRemoved(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09002755 } catch (RemoteException e) {
2756 Slog.e(TAG, "error in notifyHardwareRemoved", e);
2757 }
2758 }
2759 }
2760 }
2761
2762 @Override
Jae Seo546c6352014-08-07 11:57:01 -07002763 public void onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002764 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002765 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002766 // Broadcast the event to all hardware inputs.
2767 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002768 if (!serviceState.isHardware || serviceState.service == null) continue;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002769 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002770 serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002771 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002772 Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002773 }
2774 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002775 }
2776 }
2777
2778 @Override
Jae Seo546c6352014-08-07 11:57:01 -07002779 public void onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002780 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002781 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002782 // Broadcast the event to all hardware inputs.
2783 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002784 if (!serviceState.isHardware || serviceState.service == null) continue;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002785 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002786 serviceState.service.notifyHdmiDeviceRemoved(deviceInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002787 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002788 Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002789 }
2790 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002791 }
2792 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09002793
2794 @Override
Wonsik Kime92f8572014-08-12 18:30:24 +09002795 public void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo deviceInfo) {
2796 synchronized (mLock) {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002797 Integer state;
Wonsik Kime92f8572014-08-12 18:30:24 +09002798 switch (deviceInfo.getDevicePowerStatus()) {
2799 case HdmiControlManager.POWER_STATUS_ON:
2800 state = INPUT_STATE_CONNECTED;
2801 break;
2802 case HdmiControlManager.POWER_STATUS_STANDBY:
2803 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON:
2804 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY:
2805 state = INPUT_STATE_CONNECTED_STANDBY;
2806 break;
2807 case HdmiControlManager.POWER_STATUS_UNKNOWN:
2808 default:
2809 state = null;
2810 break;
2811 }
2812 if (state != null) {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002813 setStateLocked(inputId, state, mCurrentUserId);
Wonsik Kime92f8572014-08-12 18:30:24 +09002814 }
2815 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09002816 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002817 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09002818
2819 private static class SessionNotFoundException extends IllegalArgumentException {
Dongwon Kangfdce9e52014-12-04 18:08:00 +09002820 public SessionNotFoundException(String name) {
2821 super(name);
2822 }
2823 }
Jae Seo39570912014-02-20 18:23:25 -08002824}