blob: d5e59c8dfd6af3b9252320f0aefd4207613133ba [file] [log] [blame]
Jae Seo39570912014-02-20 18:23:25 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.tv;
18
Wonsik Kim969167d2014-06-24 16:33:17 +090019import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
Wonsik Kime92f8572014-08-12 18:30:24 +090020import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
Wonsik Kim969167d2014-06-24 16:33:17 +090021
Jae Seo4eee6a72016-02-06 11:11:35 +090022import android.annotation.Nullable;
Jae Seo39570912014-02-20 18:23:25 -080023import android.app.ActivityManager;
24import android.content.BroadcastReceiver;
25import android.content.ComponentName;
Jae Seo31dc634be2014-04-15 17:40:23 -070026import android.content.ContentResolver;
27import android.content.ContentUris;
28import android.content.ContentValues;
Jae Seo39570912014-02-20 18:23:25 -080029import android.content.Context;
30import android.content.Intent;
31import android.content.IntentFilter;
32import android.content.ServiceConnection;
Jae Seo9c165d62014-08-25 14:39:26 -070033import android.content.pm.ActivityInfo;
Jae Seo39570912014-02-20 18:23:25 -080034import android.content.pm.PackageManager;
Jae Seo8c375fe2015-06-23 20:33:25 -070035import android.content.pm.PackageManager.NameNotFoundException;
Jae Seo39570912014-02-20 18:23:25 -080036import android.content.pm.ResolveInfo;
37import android.content.pm.ServiceInfo;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090038import android.graphics.Rect;
Wonsik Kime92f8572014-08-12 18:30:24 +090039import android.hardware.hdmi.HdmiControlManager;
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090040import android.hardware.hdmi.HdmiDeviceInfo;
Jae Seo4b34cc72015-05-15 17:29:39 -070041import android.media.PlaybackParams;
Jaesung Chung58739e72015-04-24 19:39:59 +090042import android.media.tv.DvbDeviceInfo;
Jae Seod5cc4a22014-05-30 16:57:43 -070043import android.media.tv.ITvInputClient;
44import android.media.tv.ITvInputHardware;
45import android.media.tv.ITvInputHardwareCallback;
46import android.media.tv.ITvInputManager;
Wonsik Kim969167d2014-06-24 16:33:17 +090047import android.media.tv.ITvInputManagerCallback;
Jae Seod5cc4a22014-05-30 16:57:43 -070048import android.media.tv.ITvInputService;
49import android.media.tv.ITvInputServiceCallback;
50import android.media.tv.ITvInputSession;
51import android.media.tv.ITvInputSessionCallback;
Jae Seo783645e2014-07-28 17:30:50 +090052import android.media.tv.TvContentRating;
Jae Seo9c165d62014-08-25 14:39:26 -070053import android.media.tv.TvContentRatingSystemInfo;
Jae Seod5cc4a22014-05-30 16:57:43 -070054import android.media.tv.TvContract;
55import android.media.tv.TvInputHardwareInfo;
56import android.media.tv.TvInputInfo;
Jae Seo9c165d62014-08-25 14:39:26 -070057import android.media.tv.TvInputManager;
Jae Seod5cc4a22014-05-30 16:57:43 -070058import android.media.tv.TvInputService;
Terry Heoc086a3d2014-06-18 14:26:44 +090059import android.media.tv.TvStreamConfig;
Dongwon Kang1f213912014-07-02 18:35:08 +090060import android.media.tv.TvTrackInfo;
Jae Seo39570912014-02-20 18:23:25 -080061import android.net.Uri;
62import android.os.Binder;
Youngsang Cho832860f2014-05-21 20:54:03 +090063import android.os.Bundle;
Jae Seo31dc634be2014-04-15 17:40:23 -070064import android.os.Handler;
Jae Seo39570912014-02-20 18:23:25 -080065import android.os.IBinder;
Jae Seo31dc634be2014-04-15 17:40:23 -070066import android.os.Looper;
67import android.os.Message;
Jaesung Chung58739e72015-04-24 19:39:59 +090068import android.os.ParcelFileDescriptor;
Jae Seo39570912014-02-20 18:23:25 -080069import android.os.Process;
70import android.os.RemoteException;
71import android.os.UserHandle;
Jae Seoc2a89512016-01-28 10:38:11 -080072import android.text.TextUtils;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090073import android.util.Slog;
Jae Seo39570912014-02-20 18:23:25 -080074import android.util.SparseArray;
Jae Seo6a6059a2014-04-17 21:35:29 -070075import android.view.InputChannel;
Jae Seo39570912014-02-20 18:23:25 -080076import android.view.Surface;
77
78import com.android.internal.content.PackageMonitor;
Jae Seo31dc634be2014-04-15 17:40:23 -070079import com.android.internal.os.SomeArgs;
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060080import com.android.internal.util.DumpUtils;
Jaewan Kime14c3f42014-06-27 13:47:48 +090081import com.android.internal.util.IndentingPrintWriter;
Jae Seo31dc634be2014-04-15 17:40:23 -070082import com.android.server.IoThread;
Jae Seo39570912014-02-20 18:23:25 -080083import com.android.server.SystemService;
84
Jaesung Chung58739e72015-04-24 19:39:59 +090085import java.io.File;
Jaewan Kime14c3f42014-06-27 13:47:48 +090086import java.io.FileDescriptor;
Jaesung Chung58739e72015-04-24 19:39:59 +090087import java.io.FileNotFoundException;
Jaewan Kime14c3f42014-06-27 13:47:48 +090088import java.io.PrintWriter;
Jae Seo39570912014-02-20 18:23:25 -080089import java.util.ArrayList;
Chulwoo Lee19ba61a2014-09-03 00:59:35 +090090import java.util.Arrays;
Jaesung Chung58739e72015-04-24 19:39:59 +090091import java.util.Collections;
Jae Seo39570912014-02-20 18:23:25 -080092import java.util.HashMap;
Jae Seo5c80ad22014-06-12 19:52:58 -070093import java.util.HashSet;
Wonsik Kim187423c2014-06-25 14:12:48 +090094import java.util.Iterator;
Jae Seo39570912014-02-20 18:23:25 -080095import java.util.List;
96import java.util.Map;
Jae Seo5c80ad22014-06-12 19:52:58 -070097import java.util.Set;
Jaesung Chung58739e72015-04-24 19:39:59 +090098import java.util.regex.Matcher;
99import java.util.regex.Pattern;
Jae Seo39570912014-02-20 18:23:25 -0800100
101/** This class provides a system service that manages television inputs. */
102public final class TvInputManagerService extends SystemService {
Jae Seoee2ec052014-09-14 10:30:05 -0700103 private static final boolean DEBUG = false;
Jae Seo39570912014-02-20 18:23:25 -0800104 private static final String TAG = "TvInputManagerService";
Jiabinef1659f2016-11-18 10:50:15 +0900105 private static final String DVB_DIRECTORY = "/dev/dvb";
Jae Seo39570912014-02-20 18:23:25 -0800106
Jiabinef1659f2016-11-18 10:50:15 +0900107 // There are two different formats of DVB frontend devices. One is /dev/dvb%d.frontend%d,
108 // another one is /dev/dvb/adapter%d/frontend%d. Followings are the patterns for selecting the
109 // DVB frontend devices from the list of files in the /dev and /dev/dvb/adapter%d directory.
Jaesung Chung58739e72015-04-24 19:39:59 +0900110 private static final Pattern sFrontEndDevicePattern =
111 Pattern.compile("^dvb([0-9]+)\\.frontend([0-9]+)$");
Jiabinef1659f2016-11-18 10:50:15 +0900112 private static final Pattern sAdapterDirPattern =
113 Pattern.compile("^adapter([0-9]+)$");
114 private static final Pattern sFrontEndInAdapterDirPattern =
115 Pattern.compile("^frontend([0-9]+)$");
Jaesung Chung58739e72015-04-24 19:39:59 +0900116
Jae Seo39570912014-02-20 18:23:25 -0800117 private final Context mContext;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000118 private final TvInputHardwareManager mTvInputHardwareManager;
Jae Seo39570912014-02-20 18:23:25 -0800119
120 // A global lock.
121 private final Object mLock = new Object();
122
123 // ID of the current user.
Xiaohui Chen233d94c2015-07-30 15:08:00 -0700124 private int mCurrentUserId = UserHandle.USER_SYSTEM;
Jae Seo39570912014-02-20 18:23:25 -0800125
126 // A map from user id to UserState.
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700127 private final SparseArray<UserState> mUserStates = new SparseArray<>();
Jae Seo39570912014-02-20 18:23:25 -0800128
Jae Seo7eb75df2014-08-08 22:20:48 -0700129 private final WatchLogHandler mWatchLogHandler;
Jae Seo31dc634be2014-04-15 17:40:23 -0700130
Jae Seo39570912014-02-20 18:23:25 -0800131 public TvInputManagerService(Context context) {
132 super(context);
Jae Seo31dc634be2014-04-15 17:40:23 -0700133
Jae Seo39570912014-02-20 18:23:25 -0800134 mContext = context;
Jae Seo8c375fe2015-06-23 20:33:25 -0700135 mWatchLogHandler = new WatchLogHandler(mContext.getContentResolver(),
136 IoThread.get().getLooper());
Wonsik Kim187423c2014-06-25 14:12:48 +0900137 mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
Jae Seo31dc634be2014-04-15 17:40:23 -0700138
Jae Seo39570912014-02-20 18:23:25 -0800139 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700140 getOrCreateUserStateLocked(mCurrentUserId);
Jae Seo39570912014-02-20 18:23:25 -0800141 }
142 }
143
144 @Override
145 public void onStart() {
146 publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());
147 }
148
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900149 @Override
150 public void onBootPhase(int phase) {
151 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
152 registerBroadcastReceivers();
Wonsik Kim187423c2014-06-25 14:12:48 +0900153 } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900154 synchronized (mLock) {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900155 buildTvInputListLocked(mCurrentUserId, null);
Jae Seo9c165d62014-08-25 14:39:26 -0700156 buildTvContentRatingSystemListLocked(mCurrentUserId);
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900157 }
158 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900159 mTvInputHardwareManager.onBootPhase(phase);
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900160 }
161
Youngsang Cho19c47d72016-05-05 09:21:24 -0700162 @Override
163 public void onUnlockUser(int userHandle) {
164 if (DEBUG) Slog.d(TAG, "onUnlockUser(userHandle=" + userHandle + ")");
165 synchronized (mLock) {
166 if (mCurrentUserId != userHandle) {
167 return;
168 }
169 buildTvInputListLocked(mCurrentUserId, null);
170 buildTvContentRatingSystemListLocked(mCurrentUserId);
171 }
172 }
173
Jae Seo39570912014-02-20 18:23:25 -0800174 private void registerBroadcastReceivers() {
175 PackageMonitor monitor = new PackageMonitor() {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900176 private void buildTvInputList(String[] packages) {
177 synchronized (mLock) {
Dongwon Kanga897e5f2016-04-19 13:39:47 -0700178 if (mCurrentUserId == getChangingUserId()) {
179 buildTvInputListLocked(mCurrentUserId, packages);
180 buildTvContentRatingSystemListLocked(mCurrentUserId);
181 }
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900182 }
183 }
184
185 @Override
186 public void onPackageUpdateFinished(String packageName, int uid) {
187 if (DEBUG) Slog.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")");
188 // This callback is invoked when the TV input is reinstalled.
189 // In this case, isReplacing() always returns true.
190 buildTvInputList(new String[] { packageName });
191 }
192
193 @Override
194 public void onPackagesAvailable(String[] packages) {
195 if (DEBUG) {
196 Slog.d(TAG, "onPackagesAvailable(packages=" + Arrays.toString(packages) + ")");
197 }
198 // This callback is invoked when the media on which some packages exist become
199 // available.
200 if (isReplacing()) {
201 buildTvInputList(packages);
202 }
203 }
204
205 @Override
206 public void onPackagesUnavailable(String[] packages) {
207 // This callback is invoked when the media on which some packages exist become
208 // unavailable.
209 if (DEBUG) {
210 Slog.d(TAG, "onPackagesUnavailable(packages=" + Arrays.toString(packages)
211 + ")");
212 }
213 if (isReplacing()) {
214 buildTvInputList(packages);
215 }
216 }
217
Jae Seo39570912014-02-20 18:23:25 -0800218 @Override
219 public void onSomePackagesChanged() {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900220 // TODO: Use finer-grained methods(e.g. onPackageAdded, onPackageRemoved) to manage
221 // the TV inputs.
Dongwon Kang426c9a42014-08-26 17:39:21 +0900222 if (DEBUG) Slog.d(TAG, "onSomePackagesChanged()");
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900223 if (isReplacing()) {
224 if (DEBUG) Slog.d(TAG, "Skipped building TV input list due to replacing");
225 // When the package is updated, buildTvInputListLocked is called in other
226 // methods instead.
227 return;
Jae Seo39570912014-02-20 18:23:25 -0800228 }
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900229 buildTvInputList(null);
Jae Seo39570912014-02-20 18:23:25 -0800230 }
Jae Seo5c80ad22014-06-12 19:52:58 -0700231
232 @Override
Dongwon Kang31a8f842015-04-08 18:26:23 -0700233 public boolean onPackageChanged(String packageName, int uid, String[] components) {
234 // The input list needs to be updated in any cases, regardless of whether
235 // it happened to the whole package or a specific component. Returning true so that
236 // the update can be handled in {@link #onSomePackagesChanged}.
237 return true;
238 }
Jae Seo39570912014-02-20 18:23:25 -0800239 };
240 monitor.register(mContext, null, UserHandle.ALL, true);
241
242 IntentFilter intentFilter = new IntentFilter();
243 intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
244 intentFilter.addAction(Intent.ACTION_USER_REMOVED);
245 mContext.registerReceiverAsUser(new BroadcastReceiver() {
246 @Override
247 public void onReceive(Context context, Intent intent) {
248 String action = intent.getAction();
249 if (Intent.ACTION_USER_SWITCHED.equals(action)) {
250 switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
251 } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
252 removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
253 }
254 }
255 }, UserHandle.ALL, intentFilter, null, null);
256 }
257
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900258 private static boolean hasHardwarePermission(PackageManager pm, ComponentName component) {
Wonsik Kim187423c2014-06-25 14:12:48 +0900259 return pm.checkPermission(android.Manifest.permission.TV_INPUT_HARDWARE,
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900260 component.getPackageName()) == PackageManager.PERMISSION_GRANTED;
Wonsik Kim187423c2014-06-25 14:12:48 +0900261 }
262
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900263 private void buildTvInputListLocked(int userId, String[] updatedPackages) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700264 UserState userState = getOrCreateUserStateLocked(userId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900265 userState.packageSet.clear();
Jae Seo39570912014-02-20 18:23:25 -0800266
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900267 if (DEBUG) Slog.d(TAG, "buildTvInputList");
Jae Seo39570912014-02-20 18:23:25 -0800268 PackageManager pm = mContext.getPackageManager();
Jae Seo76976fa2015-05-20 21:51:16 -0700269 List<ResolveInfo> services = pm.queryIntentServicesAsUser(
Chulwoo Leee7bb7d62014-05-27 14:10:37 +0900270 new Intent(TvInputService.SERVICE_INTERFACE),
Jae Seo76976fa2015-05-20 21:51:16 -0700271 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
272 userId);
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700273 List<TvInputInfo> inputList = new ArrayList<>();
Jae Seo39570912014-02-20 18:23:25 -0800274 for (ResolveInfo ri : services) {
275 ServiceInfo si = ri.serviceInfo;
276 if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900277 Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
Jae Seo39570912014-02-20 18:23:25 -0800278 + android.Manifest.permission.BIND_TV_INPUT);
279 continue;
280 }
Jae Seo9cc28e52014-08-12 16:45:58 -0700281
282 ComponentName component = new ComponentName(si.packageName, si.name);
283 if (hasHardwarePermission(pm, component)) {
284 ServiceState serviceState = userState.serviceStateMap.get(component);
285 if (serviceState == null) {
Jae Seoc980f43d2016-02-09 23:46:58 -0800286 // New hardware input found. Create a new ServiceState and connect to the
287 // service to populate the hardware list.
Jae Seo9cc28e52014-08-12 16:45:58 -0700288 serviceState = new ServiceState(component, userId);
289 userState.serviceStateMap.put(component, serviceState);
Wonsik Kimf271eac2014-08-30 12:55:10 +0900290 updateServiceConnectionLocked(component, userId);
Wonsik Kim187423c2014-06-25 14:12:48 +0900291 } else {
Shubang71d5c762017-02-23 15:46:40 -0800292 inputList.addAll(serviceState.hardwareInputMap.values());
Jae Seo9cc28e52014-08-12 16:45:58 -0700293 }
294 } else {
295 try {
Jae Seoc03671f2016-01-26 15:15:22 -0800296 TvInputInfo info = new TvInputInfo.Builder(mContext, ri).build();
297 inputList.add(info);
Jae Seo18c0cfb2016-05-16 18:19:11 -0700298 } catch (Exception e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700299 Slog.e(TAG, "failed to load TV input " + si.name, e);
Jae Seo9cc28e52014-08-12 16:45:58 -0700300 continue;
Wonsik Kim969167d2014-06-24 16:33:17 +0900301 }
Chulwoo Leee7bb7d62014-05-27 14:10:37 +0900302 }
Jae Seo9cc28e52014-08-12 16:45:58 -0700303 userState.packageSet.add(si.packageName);
304 }
305
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700306 Map<String, TvInputState> inputMap = new HashMap<>();
Jae Seo9cc28e52014-08-12 16:45:58 -0700307 for (TvInputInfo info : inputList) {
Jae Seofea8dd42014-08-26 13:57:41 -0700308 if (DEBUG) {
309 Slog.d(TAG, "add " + info.getId());
310 }
Jae Seoabda4202016-01-28 19:13:04 -0800311 TvInputState inputState = userState.inputMap.get(info.getId());
312 if (inputState == null) {
313 inputState = new TvInputState();
Jae Seo9cc28e52014-08-12 16:45:58 -0700314 }
Shubang2263fb02016-05-13 16:51:53 -0700315 inputState.info = info;
Jae Seoabda4202016-01-28 19:13:04 -0800316 inputMap.put(info.getId(), inputState);
Jae Seo39570912014-02-20 18:23:25 -0800317 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900318
319 for (String inputId : inputMap.keySet()) {
320 if (!userState.inputMap.containsKey(inputId)) {
321 notifyInputAddedLocked(userState, inputId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900322 } else if (updatedPackages != null) {
323 // Notify the package updates
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +0900324 ComponentName component = inputMap.get(inputId).info.getComponent();
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900325 for (String updatedPackage : updatedPackages) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +0900326 if (component.getPackageName().equals(updatedPackage)) {
327 updateServiceConnectionLocked(component, userId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900328 notifyInputUpdatedLocked(userState, inputId);
329 break;
330 }
331 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900332 }
333 }
334
335 for (String inputId : userState.inputMap.keySet()) {
336 if (!inputMap.containsKey(inputId)) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900337 TvInputInfo info = userState.inputMap.get(inputId).info;
Dongwon Kang426c9a42014-08-26 17:39:21 +0900338 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
339 if (serviceState != null) {
340 abortPendingCreateSessionRequestsLocked(serviceState, inputId, userId);
341 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900342 notifyInputRemovedLocked(userState, inputId);
343 }
344 }
345
346 userState.inputMap.clear();
347 userState.inputMap = inputMap;
Jae Seo9c165d62014-08-25 14:39:26 -0700348 }
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900349
Jae Seo9c165d62014-08-25 14:39:26 -0700350 private void buildTvContentRatingSystemListLocked(int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700351 UserState userState = getOrCreateUserStateLocked(userId);
Jae Seo9c165d62014-08-25 14:39:26 -0700352 userState.contentRatingSystemList.clear();
353
354 final PackageManager pm = mContext.getPackageManager();
355 Intent intent = new Intent(TvInputManager.ACTION_QUERY_CONTENT_RATING_SYSTEMS);
356 for (ResolveInfo resolveInfo :
357 pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA)) {
358 ActivityInfo receiver = resolveInfo.activityInfo;
359 Bundle metaData = receiver.metaData;
360 if (metaData == null) {
361 continue;
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900362 }
Jae Seo9c165d62014-08-25 14:39:26 -0700363
364 int xmlResId = metaData.getInt(TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS);
365 if (xmlResId == 0) {
366 Slog.w(TAG, "Missing meta-data '"
367 + TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS + "' on receiver "
368 + receiver.packageName + "/" + receiver.name);
369 continue;
370 }
371 userState.contentRatingSystemList.add(
372 TvContentRatingSystemInfo.createTvContentRatingSystemInfo(xmlResId,
373 receiver.applicationInfo));
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900374 }
Jae Seo39570912014-02-20 18:23:25 -0800375 }
376
377 private void switchUser(int userId) {
378 synchronized (mLock) {
379 if (mCurrentUserId == userId) {
380 return;
381 }
shubang8049f202016-04-25 11:21:42 -0700382 UserState userState = mUserStates.get(mCurrentUserId);
383 List<SessionState> sessionStatesToRelease = new ArrayList<>();
384 for (SessionState sessionState : userState.sessionStateMap.values()) {
385 if (sessionState.session != null && !sessionState.isRecordingSession) {
386 sessionStatesToRelease.add(sessionState);
387 }
388 }
389 for (SessionState sessionState : sessionStatesToRelease) {
390 try {
391 sessionState.session.release();
392 } catch (RemoteException e) {
393 Slog.e(TAG, "error in release", e);
394 }
395 clearSessionAndNotifyClientLocked(sessionState);
396 }
397
398 for (Iterator<ComponentName> it = userState.serviceStateMap.keySet().iterator();
399 it.hasNext(); ) {
400 ComponentName component = it.next();
401 ServiceState serviceState = userState.serviceStateMap.get(component);
402 if (serviceState != null && serviceState.sessionTokens.isEmpty()) {
403 if (serviceState.callback != null) {
404 try {
405 serviceState.service.unregisterCallback(serviceState.callback);
406 } catch (RemoteException e) {
407 Slog.e(TAG, "error in unregisterCallback", e);
408 }
409 }
410 mContext.unbindService(serviceState.connection);
411 it.remove();
412 }
413 }
Jae Seo39570912014-02-20 18:23:25 -0800414
Jae Seo8c375fe2015-06-23 20:33:25 -0700415 mCurrentUserId = userId;
Jae Seo4f1a6d42015-07-20 16:15:01 -0700416 getOrCreateUserStateLocked(userId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900417 buildTvInputListLocked(userId, null);
Jae Seo9c165d62014-08-25 14:39:26 -0700418 buildTvContentRatingSystemListLocked(userId);
Jae Seo8c375fe2015-06-23 20:33:25 -0700419 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_SWITCH_CONTENT_RESOLVER,
420 getContentResolverForUser(userId)).sendToTarget();
Jae Seo39570912014-02-20 18:23:25 -0800421 }
422 }
423
shubang8049f202016-04-25 11:21:42 -0700424 private void clearSessionAndNotifyClientLocked(SessionState state) {
425 if (state.client != null) {
426 try {
427 state.client.onSessionReleased(state.seq);
428 } catch(RemoteException e) {
429 Slog.e(TAG, "error in onSessionReleased", e);
430 }
431 }
432 // If there are any other sessions based on this session, they should be released.
433 UserState userState = getOrCreateUserStateLocked(state.userId);
434 for (SessionState sessionState : userState.sessionStateMap.values()) {
435 if (state.sessionToken == sessionState.hardwareSessionToken) {
436 releaseSessionLocked(sessionState.sessionToken, Process.SYSTEM_UID, state.userId);
437 try {
438 sessionState.client.onSessionReleased(sessionState.seq);
439 } catch (RemoteException e) {
440 Slog.e(TAG, "error in onSessionReleased", e);
441 }
442 }
443 }
444 removeSessionStateLocked(state.sessionToken, state.userId);
445 }
446
Jae Seo39570912014-02-20 18:23:25 -0800447 private void removeUser(int userId) {
448 synchronized (mLock) {
Jae Seob06cb882014-04-09 12:08:17 -0700449 UserState userState = mUserStates.get(userId);
450 if (userState == null) {
451 return;
452 }
shubang8049f202016-04-25 11:21:42 -0700453 // Release all created sessions.
454 for (SessionState state : userState.sessionStateMap.values()) {
455 if (state.session != null) {
456 try {
457 state.session.release();
458 } catch (RemoteException e) {
459 Slog.e(TAG, "error in release", e);
460 }
461 }
462 }
463 userState.sessionStateMap.clear();
464
465 // Unregister all callbacks and unbind all services.
466 for (ServiceState serviceState : userState.serviceStateMap.values()) {
467 if (serviceState.service != null) {
468 if (serviceState.callback != null) {
469 try {
470 serviceState.service.unregisterCallback(serviceState.callback);
471 } catch (RemoteException e) {
472 Slog.e(TAG, "error in unregisterCallback", e);
473 }
474 }
475 mContext.unbindService(serviceState.connection);
476 }
477 }
478 userState.serviceStateMap.clear();
Jae Seo39570912014-02-20 18:23:25 -0800479
Jae Seofea8dd42014-08-26 13:57:41 -0700480 // Clear everything else.
481 userState.inputMap.clear();
482 userState.packageSet.clear();
Jae Seo9c165d62014-08-25 14:39:26 -0700483 userState.contentRatingSystemList.clear();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900484 userState.clientStateMap.clear();
Jae Seofea8dd42014-08-26 13:57:41 -0700485 userState.callbackSet.clear();
486 userState.mainSessionToken = null;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900487
Jae Seo39570912014-02-20 18:23:25 -0800488 mUserStates.remove(userId);
489 }
490 }
491
Jae Seo8c375fe2015-06-23 20:33:25 -0700492 private ContentResolver getContentResolverForUser(int userId) {
493 UserHandle user = new UserHandle(userId);
494 Context context;
495 try {
496 context = mContext.createPackageContextAsUser("android", 0, user);
497 } catch (NameNotFoundException e) {
Jae Seo2a2b2992016-01-12 23:13:14 -0800498 Slog.e(TAG, "failed to create package context as user " + user);
Jae Seo8c375fe2015-06-23 20:33:25 -0700499 context = mContext;
500 }
501 return context.getContentResolver();
502 }
503
Jae Seo4f1a6d42015-07-20 16:15:01 -0700504 private UserState getOrCreateUserStateLocked(int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800505 UserState userState = mUserStates.get(userId);
506 if (userState == null) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700507 userState = new UserState(mContext, userId);
508 mUserStates.put(userId, userState);
Jae Seo39570912014-02-20 18:23:25 -0800509 }
510 return userState;
511 }
512
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900513 private ServiceState getServiceStateLocked(ComponentName component, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700514 UserState userState = getOrCreateUserStateLocked(userId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900515 ServiceState serviceState = userState.serviceStateMap.get(component);
Jae Seo39570912014-02-20 18:23:25 -0800516 if (serviceState == null) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900517 throw new IllegalStateException("Service state not found for " + component + " (userId="
Sungsoo Lim7de5e232014-04-12 16:51:27 +0900518 + userId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800519 }
520 return serviceState;
521 }
522
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900523 private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700524 UserState userState = getOrCreateUserStateLocked(userId);
Jae Seo39570912014-02-20 18:23:25 -0800525 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
526 if (sessionState == null) {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900527 throw new SessionNotFoundException("Session state not found for token " + sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800528 }
529 // Only the application that requested this session or the system can access it.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900530 if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) {
Jae Seo39570912014-02-20 18:23:25 -0800531 throw new SecurityException("Illegal access to the session with token " + sessionToken
532 + " from uid " + callingUid);
533 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900534 return sessionState;
535 }
536
537 private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900538 return getSessionLocked(getSessionStateLocked(sessionToken, callingUid, userId));
539 }
540
541 private ITvInputSession getSessionLocked(SessionState sessionState) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900542 ITvInputSession session = sessionState.session;
Jae Seo39570912014-02-20 18:23:25 -0800543 if (session == null) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900544 throw new IllegalStateException("Session not yet created for token "
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900545 + sessionState.sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800546 }
547 return session;
548 }
549
550 private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
551 String methodName) {
552 return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
553 false, methodName, null);
554 }
555
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900556 private void updateServiceConnectionLocked(ComponentName component, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700557 UserState userState = getOrCreateUserStateLocked(userId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900558 ServiceState serviceState = userState.serviceStateMap.get(component);
Jae Seo39570912014-02-20 18:23:25 -0800559 if (serviceState == null) {
560 return;
561 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900562 if (serviceState.reconnecting) {
563 if (!serviceState.sessionTokens.isEmpty()) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900564 // wait until all the sessions are removed.
565 return;
566 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900567 serviceState.reconnecting = false;
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900568 }
shubang8049f202016-04-25 11:21:42 -0700569
570 boolean shouldBind;
571 if (userId == mCurrentUserId) {
572 shouldBind = !serviceState.sessionTokens.isEmpty() || serviceState.isHardware;
573 } else {
574 // For a non-current user,
575 // if sessionTokens is not empty, it contains recording sessions only
576 // because other sessions must have been removed while switching user
577 // and non-recording sessions are not created by createSession().
578 shouldBind = !serviceState.sessionTokens.isEmpty();
579 }
580
581 if (serviceState.service == null && shouldBind) {
Jae Seo39570912014-02-20 18:23:25 -0800582 // This means that the service is not yet connected but its state indicates that we
583 // have pending requests. Then, connect the service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900584 if (serviceState.bound) {
Jae Seo39570912014-02-20 18:23:25 -0800585 // We have already bound to the service so we don't try to bind again until after we
586 // unbind later on.
587 return;
588 }
589 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900590 Slog.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800591 }
Sungsoo Limd6672b52014-04-30 10:43:26 +0900592
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900593 Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900594 serviceState.bound = mContext.bindServiceAsUser(
Dianne Hackbornd69e4c12015-04-24 09:54:54 -0700595 i, serviceState.connection,
596 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
597 new UserHandle(userId));
shubang8049f202016-04-25 11:21:42 -0700598 } else if (serviceState.service != null && !shouldBind) {
Jae Seo39570912014-02-20 18:23:25 -0800599 // This means that the service is already connected but its state indicates that we have
600 // nothing to do with it. Then, disconnect the service.
601 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900602 Slog.d(TAG, "unbindService(service=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -0800603 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900604 mContext.unbindService(serviceState.connection);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900605 userState.serviceStateMap.remove(component);
Jae Seo39570912014-02-20 18:23:25 -0800606 }
607 }
608
Dongwon Kang426c9a42014-08-26 17:39:21 +0900609 private void abortPendingCreateSessionRequestsLocked(ServiceState serviceState,
610 String inputId, int userId) {
611 // Let clients know the create session requests are failed.
Jae Seo4f1a6d42015-07-20 16:15:01 -0700612 UserState userState = getOrCreateUserStateLocked(userId);
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900613 List<SessionState> sessionsToAbort = new ArrayList<>();
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900614 for (IBinder sessionToken : serviceState.sessionTokens) {
Dongwon Kang426c9a42014-08-26 17:39:21 +0900615 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900616 if (sessionState.session == null && (inputId == null
Jae Seo2cdb05e2016-02-04 22:17:13 +0900617 || sessionState.inputId.equals(inputId))) {
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900618 sessionsToAbort.add(sessionState);
Dongwon Kang426c9a42014-08-26 17:39:21 +0900619 }
620 }
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900621 for (SessionState sessionState : sessionsToAbort) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900622 removeSessionStateLocked(sessionState.sessionToken, sessionState.userId);
623 sendSessionTokenToClientLocked(sessionState.client,
Jae Seo2cdb05e2016-02-04 22:17:13 +0900624 sessionState.inputId, null, null, sessionState.seq);
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900625 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900626 updateServiceConnectionLocked(serviceState.component, userId);
Dongwon Kang426c9a42014-08-26 17:39:21 +0900627 }
628
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900629 private void createSessionInternalLocked(ITvInputService service, IBinder sessionToken,
630 int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700631 UserState userState = getOrCreateUserStateLocked(userId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900632 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800633 if (DEBUG) {
Jae Seo2cdb05e2016-02-04 22:17:13 +0900634 Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.inputId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800635 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900636 InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
Jae Seo6a6059a2014-04-17 21:35:29 -0700637
Jae Seo39570912014-02-20 18:23:25 -0800638 // Set up a callback to send the session token.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900639 ITvInputSessionCallback callback = new SessionCallback(sessionState, channels);
Jae Seo39570912014-02-20 18:23:25 -0800640
641 // Create a session. When failed, send a null token immediately.
642 try {
Jae Seoa826d012016-01-18 13:03:35 -0800643 if (sessionState.isRecordingSession) {
Jae Seo2cdb05e2016-02-04 22:17:13 +0900644 service.createRecordingSession(callback, sessionState.inputId);
Jae Seoa826d012016-01-18 13:03:35 -0800645 } else {
Jae Seo2cdb05e2016-02-04 22:17:13 +0900646 service.createSession(channels[1], callback, sessionState.inputId);
Jae Seoa826d012016-01-18 13:03:35 -0800647 }
Jae Seo39570912014-02-20 18:23:25 -0800648 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900649 Slog.e(TAG, "error in createSession", e);
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900650 removeSessionStateLocked(sessionToken, userId);
Jae Seo2cdb05e2016-02-04 22:17:13 +0900651 sendSessionTokenToClientLocked(sessionState.client, sessionState.inputId, null,
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900652 null, sessionState.seq);
Jae Seo39570912014-02-20 18:23:25 -0800653 }
Jae Seo6a6059a2014-04-17 21:35:29 -0700654 channels[1].dispose();
Jae Seo39570912014-02-20 18:23:25 -0800655 }
656
Sungsoo Limd6672b52014-04-30 10:43:26 +0900657 private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId,
Jae Seo5c80ad22014-06-12 19:52:58 -0700658 IBinder sessionToken, InputChannel channel, int seq) {
Jae Seo39570912014-02-20 18:23:25 -0800659 try {
Sungsoo Limd6672b52014-04-30 10:43:26 +0900660 client.onSessionCreated(inputId, sessionToken, channel, seq);
Jae Seofea8dd42014-08-26 13:57:41 -0700661 } catch (RemoteException e) {
662 Slog.e(TAG, "error in onSessionCreated", e);
Jae Seo39570912014-02-20 18:23:25 -0800663 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900664 }
Jae Seo39570912014-02-20 18:23:25 -0800665
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900666 private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900667 SessionState sessionState = null;
668 try {
669 sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
670 if (sessionState.session != null) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700671 UserState userState = getOrCreateUserStateLocked(userId);
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900672 if (sessionToken == userState.mainSessionToken) {
673 setMainLocked(sessionToken, false, callingUid, userId);
674 }
Jae Seoe3c11e82016-02-08 23:18:49 -0800675 sessionState.session.release();
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900676 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900677 } catch (RemoteException | SessionNotFoundException e) {
678 Slog.e(TAG, "error in releaseSession", e);
679 } finally {
680 if (sessionState != null) {
681 sessionState.session = null;
682 }
Jae Seo39570912014-02-20 18:23:25 -0800683 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900684 removeSessionStateLocked(sessionToken, userId);
Jae Seo39570912014-02-20 18:23:25 -0800685 }
686
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900687 private void removeSessionStateLocked(IBinder sessionToken, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700688 UserState userState = getOrCreateUserStateLocked(userId);
Ji-Hwan Leeabca0ee2014-07-24 17:34:19 +0900689 if (sessionToken == userState.mainSessionToken) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900690 if (DEBUG) {
691 Slog.d(TAG, "mainSessionToken=null");
692 }
Ji-Hwan Leeabca0ee2014-07-24 17:34:19 +0900693 userState.mainSessionToken = null;
694 }
695
696 // Remove the session state from the global session state map of the current user.
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900697 SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
698
Chulwoo Lee8d4ded02014-07-10 03:56:39 +0900699 if (sessionState == null) {
700 return;
701 }
702
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900703 // Also remove the session token from the session token list of the current client and
704 // service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900705 ClientState clientState = userState.clientStateMap.get(sessionState.client.asBinder());
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900706 if (clientState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900707 clientState.sessionTokens.remove(sessionToken);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900708 if (clientState.isEmpty()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900709 userState.clientStateMap.remove(sessionState.client.asBinder());
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900710 }
711 }
712
Jae Seo2cdb05e2016-02-04 22:17:13 +0900713 ServiceState serviceState = userState.serviceStateMap.get(sessionState.componentName);
714 if (serviceState != null) {
715 serviceState.sessionTokens.remove(sessionToken);
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900716 }
Jae Seo2cdb05e2016-02-04 22:17:13 +0900717 updateServiceConnectionLocked(sessionState.componentName, userId);
Jae Seo7eb75df2014-08-08 22:20:48 -0700718
719 // Log the end of watch.
720 SomeArgs args = SomeArgs.obtain();
721 args.arg1 = sessionToken;
722 args.arg2 = System.currentTimeMillis();
723 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_END, args).sendToTarget();
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900724 }
725
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900726 private void setMainLocked(IBinder sessionToken, boolean isMain, int callingUid, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900727 try {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900728 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
729 if (sessionState.hardwareSessionToken != null) {
730 sessionState = getSessionStateLocked(sessionState.hardwareSessionToken,
731 Process.SYSTEM_UID, userId);
732 }
Jae Seo2cdb05e2016-02-04 22:17:13 +0900733 ServiceState serviceState = getServiceStateLocked(sessionState.componentName, userId);
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900734 if (!serviceState.isHardware) {
735 return;
736 }
737 ITvInputSession session = getSessionLocked(sessionState);
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900738 session.setMain(isMain);
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900739 } catch (RemoteException | SessionNotFoundException e) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900740 Slog.e(TAG, "error in setMain", e);
741 }
742 }
743
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900744 private void notifyInputAddedLocked(UserState userState, String inputId) {
745 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700746 Slog.d(TAG, "notifyInputAddedLocked(inputId=" + inputId + ")");
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900747 }
748 for (ITvInputManagerCallback callback : userState.callbackSet) {
749 try {
750 callback.onInputAdded(inputId);
751 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700752 Slog.e(TAG, "failed to report added input to callback", e);
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900753 }
754 }
755 }
756
757 private void notifyInputRemovedLocked(UserState userState, String inputId) {
758 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700759 Slog.d(TAG, "notifyInputRemovedLocked(inputId=" + inputId + ")");
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900760 }
761 for (ITvInputManagerCallback callback : userState.callbackSet) {
762 try {
763 callback.onInputRemoved(inputId);
764 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700765 Slog.e(TAG, "failed to report removed input to callback", e);
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900766 }
767 }
768 }
769
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900770 private void notifyInputUpdatedLocked(UserState userState, String inputId) {
771 if (DEBUG) {
772 Slog.d(TAG, "notifyInputUpdatedLocked(inputId=" + inputId + ")");
773 }
774 for (ITvInputManagerCallback callback : userState.callbackSet) {
775 try {
776 callback.onInputUpdated(inputId);
777 } catch (RemoteException e) {
778 Slog.e(TAG, "failed to report updated input to callback", e);
779 }
780 }
781 }
782
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900783 private void notifyInputStateChangedLocked(UserState userState, String inputId,
Wonsik Kim969167d2014-06-24 16:33:17 +0900784 int state, ITvInputManagerCallback targetCallback) {
785 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700786 Slog.d(TAG, "notifyInputStateChangedLocked(inputId=" + inputId
787 + ", state=" + state + ")");
Wonsik Kim969167d2014-06-24 16:33:17 +0900788 }
789 if (targetCallback == null) {
790 for (ITvInputManagerCallback callback : userState.callbackSet) {
791 try {
792 callback.onInputStateChanged(inputId, state);
793 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700794 Slog.e(TAG, "failed to report state change to callback", e);
Wonsik Kim969167d2014-06-24 16:33:17 +0900795 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900796 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900797 } else {
798 try {
799 targetCallback.onInputStateChanged(inputId, state);
800 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700801 Slog.e(TAG, "failed to report state change to callback", e);
Wonsik Kim969167d2014-06-24 16:33:17 +0900802 }
803 }
804 }
805
Jae Seoaa5605f2016-02-13 01:38:08 -0800806 private void updateTvInputInfoLocked(UserState userState, TvInputInfo inputInfo) {
Jae Seoa826d012016-01-18 13:03:35 -0800807 if (DEBUG) {
Jae Seoaa5605f2016-02-13 01:38:08 -0800808 Slog.d(TAG, "updateTvInputInfoLocked(inputInfo=" + inputInfo + ")");
Jae Seoa826d012016-01-18 13:03:35 -0800809 }
Jae Seoabda4202016-01-28 19:13:04 -0800810 String inputId = inputInfo.getId();
811 TvInputState inputState = userState.inputMap.get(inputId);
812 if (inputState == null) {
813 Slog.e(TAG, "failed to set input info - unknown input id " + inputId);
814 return;
815 }
Jae Seoabda4202016-01-28 19:13:04 -0800816 inputState.info = inputInfo;
817
Jae Seoa826d012016-01-18 13:03:35 -0800818 for (ITvInputManagerCallback callback : userState.callbackSet) {
819 try {
Jae Seoaa5605f2016-02-13 01:38:08 -0800820 callback.onTvInputInfoUpdated(inputInfo);
Jae Seoa826d012016-01-18 13:03:35 -0800821 } catch (RemoteException e) {
Jae Seoaa5605f2016-02-13 01:38:08 -0800822 Slog.e(TAG, "failed to report updated input info to callback", e);
Jae Seoa826d012016-01-18 13:03:35 -0800823 }
824 }
825 }
826
Wonsik Kim969167d2014-06-24 16:33:17 +0900827 private void setStateLocked(String inputId, int state, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700828 UserState userState = getOrCreateUserStateLocked(userId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900829 TvInputState inputState = userState.inputMap.get(inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900830 ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent());
831 int oldState = inputState.state;
832 inputState.state = state;
833 if (serviceState != null && serviceState.service == null
shubang8049f202016-04-25 11:21:42 -0700834 && (!serviceState.sessionTokens.isEmpty() || serviceState.isHardware)) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900835 // We don't notify state change while reconnecting. It should remain disconnected.
836 return;
837 }
838 if (oldState != state) {
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900839 notifyInputStateChangedLocked(userState, inputId, state, null);
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900840 }
841 }
842
Jae Seo39570912014-02-20 18:23:25 -0800843 private final class BinderService extends ITvInputManager.Stub {
844 @Override
845 public List<TvInputInfo> getTvInputList(int userId) {
846 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
847 Binder.getCallingUid(), userId, "getTvInputList");
848 final long identity = Binder.clearCallingIdentity();
849 try {
850 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700851 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700852 List<TvInputInfo> inputList = new ArrayList<>();
Wonsik Kim969167d2014-06-24 16:33:17 +0900853 for (TvInputState state : userState.inputMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900854 inputList.add(state.info);
Jae Seo39570912014-02-20 18:23:25 -0800855 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900856 return inputList;
Jae Seo39570912014-02-20 18:23:25 -0800857 }
858 } finally {
859 Binder.restoreCallingIdentity(identity);
860 }
Jae Seo39570912014-02-20 18:23:25 -0800861 }
862
863 @Override
Jae Seob3758052014-07-12 19:25:24 -0700864 public TvInputInfo getTvInputInfo(String inputId, int userId) {
865 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
866 Binder.getCallingUid(), userId, "getTvInputInfo");
867 final long identity = Binder.clearCallingIdentity();
868 try {
869 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700870 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seob3758052014-07-12 19:25:24 -0700871 TvInputState state = userState.inputMap.get(inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900872 return state == null ? null : state.info;
Jae Seob3758052014-07-12 19:25:24 -0700873 }
874 } finally {
875 Binder.restoreCallingIdentity(identity);
876 }
877 }
878
Jae Seoaa5605f2016-02-13 01:38:08 -0800879 public void updateTvInputInfo(TvInputInfo inputInfo, int userId) {
Jae Seoc2a89512016-01-28 10:38:11 -0800880 String inputInfoPackageName = inputInfo.getServiceInfo().packageName;
881 String callingPackageName = getCallingPackageName();
Dongwon Kang9d0e0f12016-10-11 15:14:01 -0700882 if (!TextUtils.equals(inputInfoPackageName, callingPackageName)
883 && mContext.checkCallingPermission(
884 android.Manifest.permission.WRITE_SECURE_SETTINGS)
885 != PackageManager.PERMISSION_GRANTED) {
886 // Only the app owning the input and system settings are allowed to update info.
Jae Seoc2a89512016-01-28 10:38:11 -0800887 throw new IllegalArgumentException("calling package " + callingPackageName
888 + " is not allowed to change TvInputInfo for " + inputInfoPackageName);
889 }
890
891 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
Jae Seoaa5605f2016-02-13 01:38:08 -0800892 Binder.getCallingUid(), userId, "updateTvInputInfo");
Jae Seoc2a89512016-01-28 10:38:11 -0800893 final long identity = Binder.clearCallingIdentity();
894 try {
895 synchronized (mLock) {
896 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seoaa5605f2016-02-13 01:38:08 -0800897 updateTvInputInfoLocked(userState, inputInfo);
Jae Seoc2a89512016-01-28 10:38:11 -0800898 }
899 } finally {
900 Binder.restoreCallingIdentity(identity);
901 }
902 }
903
904 private String getCallingPackageName() {
905 final String[] packages = mContext.getPackageManager().getPackagesForUid(
906 Binder.getCallingUid());
907 if (packages != null && packages.length > 0) {
908 return packages[0];
909 }
910 return "unknown";
911 }
912
Jae Seob3758052014-07-12 19:25:24 -0700913 @Override
Dongwon Kang993f81e2014-11-27 19:34:18 +0900914 public int getTvInputState(String inputId, int userId) {
915 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
916 Binder.getCallingUid(), userId, "getTvInputState");
917 final long identity = Binder.clearCallingIdentity();
918 try {
919 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700920 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Dongwon Kang993f81e2014-11-27 19:34:18 +0900921 TvInputState state = userState.inputMap.get(inputId);
Jae Seo82fce642015-04-20 15:37:50 -0700922 return state == null ? INPUT_STATE_CONNECTED : state.state;
Dongwon Kang993f81e2014-11-27 19:34:18 +0900923 }
924 } finally {
925 Binder.restoreCallingIdentity(identity);
926 }
927 }
928
929 @Override
Jae Seo9c165d62014-08-25 14:39:26 -0700930 public List<TvContentRatingSystemInfo> getTvContentRatingSystemList(int userId) {
Shubangdd3ec0b2017-06-23 13:54:10 -0700931 if (mContext.checkCallingPermission(
932 android.Manifest.permission.READ_CONTENT_RATING_SYSTEMS)
933 != PackageManager.PERMISSION_GRANTED) {
934 throw new SecurityException(
935 "The caller does not have permission to read content rating systems");
936 }
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900937 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
Jae Seo9c165d62014-08-25 14:39:26 -0700938 Binder.getCallingUid(), userId, "getTvContentRatingSystemList");
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900939 final long identity = Binder.clearCallingIdentity();
940 try {
941 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700942 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo9c165d62014-08-25 14:39:26 -0700943 return userState.contentRatingSystemList;
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900944 }
945 } finally {
946 Binder.restoreCallingIdentity(identity);
947 }
948 }
949
950 @Override
Conrad Chen558acf92017-04-05 17:31:01 -0700951 public void sendTvInputNotifyIntent(Intent intent, int userId) {
952 if (mContext.checkCallingPermission(android.Manifest.permission.NOTIFY_TV_INPUTS)
953 != PackageManager.PERMISSION_GRANTED) {
954 throw new SecurityException("The caller: " + getCallingPackageName()
955 + " doesn't have permission: "
956 + android.Manifest.permission.NOTIFY_TV_INPUTS);
957 }
958 if (TextUtils.isEmpty(intent.getPackage())) {
959 throw new IllegalArgumentException("Must specify package name to notify.");
960 }
961 switch (intent.getAction()) {
962 case TvContract.ACTION_PREVIEW_PROGRAM_BROWSABLE_DISABLED:
963 if (intent.getLongExtra(TvContract.EXTRA_PREVIEW_PROGRAM_ID, -1) < 0) {
964 throw new IllegalArgumentException("Invalid preview program ID.");
965 }
966 break;
967 case TvContract.ACTION_WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED:
968 if (intent.getLongExtra(TvContract.EXTRA_WATCH_NEXT_PROGRAM_ID, -1) < 0) {
969 throw new IllegalArgumentException("Invalid watch next program ID.");
970 }
971 break;
972 case TvContract.ACTION_PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT:
973 if (intent.getLongExtra(TvContract.EXTRA_PREVIEW_PROGRAM_ID, -1) < 0) {
974 throw new IllegalArgumentException("Invalid preview program ID.");
975 }
976 if (intent.getLongExtra(TvContract.EXTRA_WATCH_NEXT_PROGRAM_ID, -1) < 0) {
977 throw new IllegalArgumentException("Invalid watch next program ID.");
978 }
979 break;
980 default:
981 throw new IllegalArgumentException("Invalid TV input notifying action: "
982 + intent.getAction());
983 }
984 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
985 Binder.getCallingUid(), userId, "sendTvInputNotifyIntent");
986 final long identity = Binder.clearCallingIdentity();
987 try {
988 getContext().sendBroadcastAsUser(intent, new UserHandle(resolvedUserId));
989 } finally {
990 Binder.restoreCallingIdentity(identity);
991 }
992 }
993
994 @Override
Wonsik Kim969167d2014-06-24 16:33:17 +0900995 public void registerCallback(final ITvInputManagerCallback callback, int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800996 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
997 Binder.getCallingUid(), userId, "registerCallback");
998 final long identity = Binder.clearCallingIdentity();
999 try {
1000 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001001 final UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Wonsik Kim969167d2014-06-24 16:33:17 +09001002 userState.callbackSet.add(callback);
Jae Seofea8dd42014-08-26 13:57:41 -07001003 try {
1004 callback.asBinder().linkToDeath(new IBinder.DeathRecipient() {
1005 @Override
1006 public void binderDied() {
1007 synchronized (mLock) {
1008 if (userState.callbackSet != null) {
1009 userState.callbackSet.remove(callback);
1010 }
1011 }
1012 }
1013 }, 0);
1014 } catch (RemoteException e) {
1015 Slog.e(TAG, "client process has already died", e);
1016 }
Jae Seo39570912014-02-20 18:23:25 -08001017 }
1018 } finally {
1019 Binder.restoreCallingIdentity(identity);
1020 }
1021 }
1022
1023 @Override
Wonsik Kim969167d2014-06-24 16:33:17 +09001024 public void unregisterCallback(ITvInputManagerCallback callback, int userId) {
Jae Seo39570912014-02-20 18:23:25 -08001025 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1026 Binder.getCallingUid(), userId, "unregisterCallback");
1027 final long identity = Binder.clearCallingIdentity();
1028 try {
1029 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001030 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Wonsik Kim969167d2014-06-24 16:33:17 +09001031 userState.callbackSet.remove(callback);
Jae Seo39570912014-02-20 18:23:25 -08001032 }
1033 } finally {
1034 Binder.restoreCallingIdentity(identity);
1035 }
1036 }
1037
1038 @Override
Jae Seo783645e2014-07-28 17:30:50 +09001039 public boolean isParentalControlsEnabled(int userId) {
1040 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1041 Binder.getCallingUid(), userId, "isParentalControlsEnabled");
1042 final long identity = Binder.clearCallingIdentity();
1043 try {
1044 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001045 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +09001046 return userState.persistentDataStore.isParentalControlsEnabled();
1047 }
1048 } finally {
1049 Binder.restoreCallingIdentity(identity);
1050 }
1051 }
1052
1053 @Override
1054 public void setParentalControlsEnabled(boolean enabled, int userId) {
1055 ensureParentalControlsPermission();
1056 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1057 Binder.getCallingUid(), userId, "setParentalControlsEnabled");
1058 final long identity = Binder.clearCallingIdentity();
1059 try {
1060 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001061 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +09001062 userState.persistentDataStore.setParentalControlsEnabled(enabled);
1063 }
1064 } finally {
1065 Binder.restoreCallingIdentity(identity);
1066 }
1067 }
1068
1069 @Override
1070 public boolean isRatingBlocked(String rating, int userId) {
1071 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1072 Binder.getCallingUid(), userId, "isRatingBlocked");
1073 final long identity = Binder.clearCallingIdentity();
1074 try {
1075 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001076 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +09001077 return userState.persistentDataStore.isRatingBlocked(
1078 TvContentRating.unflattenFromString(rating));
1079 }
1080 } finally {
1081 Binder.restoreCallingIdentity(identity);
1082 }
1083 }
1084
1085 @Override
1086 public List<String> getBlockedRatings(int userId) {
1087 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1088 Binder.getCallingUid(), userId, "getBlockedRatings");
1089 final long identity = Binder.clearCallingIdentity();
1090 try {
1091 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001092 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001093 List<String> ratings = new ArrayList<>();
Jae Seo783645e2014-07-28 17:30:50 +09001094 for (TvContentRating rating
1095 : userState.persistentDataStore.getBlockedRatings()) {
1096 ratings.add(rating.flattenToString());
1097 }
1098 return ratings;
1099 }
1100 } finally {
1101 Binder.restoreCallingIdentity(identity);
1102 }
1103 }
1104
1105 @Override
1106 public void addBlockedRating(String rating, int userId) {
1107 ensureParentalControlsPermission();
1108 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1109 Binder.getCallingUid(), userId, "addBlockedRating");
1110 final long identity = Binder.clearCallingIdentity();
1111 try {
1112 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001113 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +09001114 userState.persistentDataStore.addBlockedRating(
1115 TvContentRating.unflattenFromString(rating));
1116 }
1117 } finally {
1118 Binder.restoreCallingIdentity(identity);
1119 }
1120 }
1121
1122 @Override
1123 public void removeBlockedRating(String rating, int userId) {
1124 ensureParentalControlsPermission();
1125 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1126 Binder.getCallingUid(), userId, "removeBlockedRating");
1127 final long identity = Binder.clearCallingIdentity();
1128 try {
1129 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001130 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +09001131 userState.persistentDataStore.removeBlockedRating(
1132 TvContentRating.unflattenFromString(rating));
1133 }
1134 } finally {
1135 Binder.restoreCallingIdentity(identity);
1136 }
1137 }
1138
1139 private void ensureParentalControlsPermission() {
Jae Seofc836f62014-08-27 00:47:56 +00001140 if (mContext.checkCallingPermission(
1141 android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
1142 != PackageManager.PERMISSION_GRANTED) {
1143 throw new SecurityException(
1144 "The caller does not have parental controls permission");
1145 }
Jae Seo783645e2014-07-28 17:30:50 +09001146 }
1147
1148 @Override
Sungsoo Limd6672b52014-04-30 10:43:26 +09001149 public void createSession(final ITvInputClient client, final String inputId,
Jae Seoa826d012016-01-18 13:03:35 -08001150 boolean isRecordingSession, int seq, int userId) {
Jae Seo39570912014-02-20 18:23:25 -08001151 final int callingUid = Binder.getCallingUid();
1152 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1153 userId, "createSession");
1154 final long identity = Binder.clearCallingIdentity();
1155 try {
1156 synchronized (mLock) {
shubang8049f202016-04-25 11:21:42 -07001157 if (userId != mCurrentUserId && !isRecordingSession) {
1158 // A non-recording session of a backgroud (non-current) user
1159 // should not be created.
1160 // Let the client get onConnectionFailed callback for this case.
1161 sendSessionTokenToClientLocked(client, inputId, null, null, seq);
1162 return;
1163 }
Jae Seo4f1a6d42015-07-20 16:15:01 -07001164 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Dongwon Kang426c9a42014-08-26 17:39:21 +09001165 TvInputState inputState = userState.inputMap.get(inputId);
1166 if (inputState == null) {
1167 Slog.w(TAG, "Failed to find input state for inputId=" + inputId);
1168 sendSessionTokenToClientLocked(client, inputId, null, null, seq);
1169 return;
1170 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001171 TvInputInfo info = inputState.info;
Wonsik Kim187423c2014-06-25 14:12:48 +09001172 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
Jae Seo39570912014-02-20 18:23:25 -08001173 if (serviceState == null) {
Wonsik Kim187423c2014-06-25 14:12:48 +09001174 serviceState = new ServiceState(info.getComponent(), resolvedUserId);
1175 userState.serviceStateMap.put(info.getComponent(), serviceState);
Jae Seo39570912014-02-20 18:23:25 -08001176 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001177 // Send a null token immediately while reconnecting.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001178 if (serviceState.reconnecting) {
Jae Seo5c80ad22014-06-12 19:52:58 -07001179 sendSessionTokenToClientLocked(client, inputId, null, null, seq);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001180 return;
1181 }
1182
1183 // Create a new session token and a session state.
1184 IBinder sessionToken = new Binder();
Jae Seo2cdb05e2016-02-04 22:17:13 +09001185 SessionState sessionState = new SessionState(sessionToken, info.getId(),
1186 info.getComponent(), isRecordingSession, client, seq, callingUid,
1187 resolvedUserId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001188
1189 // Add them to the global session state map of the current user.
1190 userState.sessionStateMap.put(sessionToken, sessionState);
1191
1192 // Also, add them to the session state map of the current service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001193 serviceState.sessionTokens.add(sessionToken);
Jae Seo39570912014-02-20 18:23:25 -08001194
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001195 if (serviceState.service != null) {
1196 createSessionInternalLocked(serviceState.service, sessionToken,
Sungsoo Lim7de5e232014-04-12 16:51:27 +09001197 resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001198 } else {
Wonsik Kim187423c2014-06-25 14:12:48 +09001199 updateServiceConnectionLocked(info.getComponent(), resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001200 }
1201 }
1202 } finally {
1203 Binder.restoreCallingIdentity(identity);
1204 }
1205 }
1206
1207 @Override
1208 public void releaseSession(IBinder sessionToken, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001209 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -07001210 Slog.d(TAG, "releaseSession(sessionToken=" + sessionToken + ")");
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001211 }
Jae Seo39570912014-02-20 18:23:25 -08001212 final int callingUid = Binder.getCallingUid();
1213 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1214 userId, "releaseSession");
1215 final long identity = Binder.clearCallingIdentity();
1216 try {
1217 synchronized (mLock) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001218 releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001219 }
1220 } finally {
1221 Binder.restoreCallingIdentity(identity);
1222 }
1223 }
1224
1225 @Override
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001226 public void setMainSession(IBinder sessionToken, int userId) {
Shubange41b76f2017-06-07 13:23:12 -07001227 if (mContext.checkCallingPermission(
1228 android.Manifest.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE)
1229 != PackageManager.PERMISSION_GRANTED) {
1230 throw new SecurityException(
1231 "The caller does not have CHANGE_HDMI_CEC_ACTIVE_SOURCE permission");
1232 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001233 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -07001234 Slog.d(TAG, "setMainSession(sessionToken=" + sessionToken + ")");
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001235 }
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001236 final int callingUid = Binder.getCallingUid();
1237 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1238 userId, "setMainSession");
1239 final long identity = Binder.clearCallingIdentity();
1240 try {
1241 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001242 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001243 if (userState.mainSessionToken == sessionToken) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001244 return;
1245 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001246 if (DEBUG) {
1247 Slog.d(TAG, "mainSessionToken=" + sessionToken);
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001248 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001249 IBinder oldMainSessionToken = userState.mainSessionToken;
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001250 userState.mainSessionToken = sessionToken;
1251
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001252 // Inform the new main session first.
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001253 // See {@link TvInputService.Session#onSetMain}.
1254 if (sessionToken != null) {
1255 setMainLocked(sessionToken, true, callingUid, userId);
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001256 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001257 if (oldMainSessionToken != null) {
1258 setMainLocked(oldMainSessionToken, false, Process.SYSTEM_UID, userId);
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001259 }
1260 }
1261 } finally {
1262 Binder.restoreCallingIdentity(identity);
1263 }
1264 }
1265
1266 @Override
Jae Seo39570912014-02-20 18:23:25 -08001267 public void setSurface(IBinder sessionToken, Surface surface, int userId) {
1268 final int callingUid = Binder.getCallingUid();
1269 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1270 userId, "setSurface");
1271 final long identity = Binder.clearCallingIdentity();
1272 try {
1273 synchronized (mLock) {
1274 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001275 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1276 resolvedUserId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001277 if (sessionState.hardwareSessionToken == null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001278 getSessionLocked(sessionState).setSurface(surface);
1279 } else {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001280 getSessionLocked(sessionState.hardwareSessionToken,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001281 Process.SYSTEM_UID, resolvedUserId).setSurface(surface);
1282 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001283 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001284 Slog.e(TAG, "error in setSurface", e);
Jae Seo39570912014-02-20 18:23:25 -08001285 }
1286 }
1287 } finally {
Youngsang Chof8362062014-04-30 17:24:20 +09001288 if (surface != null) {
1289 // surface is not used in TvInputManagerService.
1290 surface.release();
1291 }
Jae Seo39570912014-02-20 18:23:25 -08001292 Binder.restoreCallingIdentity(identity);
1293 }
1294 }
1295
1296 @Override
Youngsang Choe821d712014-07-16 14:22:19 -07001297 public void dispatchSurfaceChanged(IBinder sessionToken, int format, int width,
1298 int height, int userId) {
1299 final int callingUid = Binder.getCallingUid();
1300 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1301 userId, "dispatchSurfaceChanged");
1302 final long identity = Binder.clearCallingIdentity();
1303 try {
1304 synchronized (mLock) {
1305 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001306 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1307 resolvedUserId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001308 getSessionLocked(sessionState).dispatchSurfaceChanged(format, width,
1309 height);
1310 if (sessionState.hardwareSessionToken != null) {
1311 getSessionLocked(sessionState.hardwareSessionToken, Process.SYSTEM_UID,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001312 resolvedUserId).dispatchSurfaceChanged(format, width, height);
1313 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001314 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Choe821d712014-07-16 14:22:19 -07001315 Slog.e(TAG, "error in dispatchSurfaceChanged", e);
1316 }
1317 }
1318 } finally {
1319 Binder.restoreCallingIdentity(identity);
1320 }
1321 }
1322
1323 @Override
Jae Seo39570912014-02-20 18:23:25 -08001324 public void setVolume(IBinder sessionToken, float volume, int userId) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001325 final float REMOTE_VOLUME_ON = 1.0f;
1326 final float REMOTE_VOLUME_OFF = 0f;
Jae Seo39570912014-02-20 18:23:25 -08001327 final int callingUid = Binder.getCallingUid();
1328 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1329 userId, "setVolume");
1330 final long identity = Binder.clearCallingIdentity();
1331 try {
1332 synchronized (mLock) {
1333 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001334 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1335 resolvedUserId);
1336 getSessionLocked(sessionState).setVolume(volume);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001337 if (sessionState.hardwareSessionToken != null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001338 // Here, we let the hardware session know only whether volume is on or
1339 // off to prevent that the volume is controlled in the both side.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001340 getSessionLocked(sessionState.hardwareSessionToken,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001341 Process.SYSTEM_UID, resolvedUserId).setVolume((volume > 0.0f)
1342 ? REMOTE_VOLUME_ON : REMOTE_VOLUME_OFF);
1343 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001344 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001345 Slog.e(TAG, "error in setVolume", e);
Jae Seo39570912014-02-20 18:23:25 -08001346 }
1347 }
1348 } finally {
1349 Binder.restoreCallingIdentity(identity);
1350 }
1351 }
1352
1353 @Override
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +09001354 public void tune(IBinder sessionToken, final Uri channelUri, Bundle params, int userId) {
Jae Seo39570912014-02-20 18:23:25 -08001355 final int callingUid = Binder.getCallingUid();
1356 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1357 userId, "tune");
1358 final long identity = Binder.clearCallingIdentity();
1359 try {
1360 synchronized (mLock) {
Jae Seo39570912014-02-20 18:23:25 -08001361 try {
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +09001362 getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(
1363 channelUri, params);
Jae Seoc22d0c02014-08-15 13:03:21 -07001364 if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
Youngsang Cho008f6d42014-07-22 21:29:47 -07001365 // Do not log the watch history for passthrough inputs.
1366 return;
1367 }
Jae Seo31dc634be2014-04-15 17:40:23 -07001368
Jae Seo4f1a6d42015-07-20 16:15:01 -07001369 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo31dc634be2014-04-15 17:40:23 -07001370 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Jae Seoe3c11e82016-02-08 23:18:49 -08001371 if (sessionState.isRecordingSession) {
1372 return;
1373 }
Jae Seo31dc634be2014-04-15 17:40:23 -07001374
Jae Seo7eb75df2014-08-08 22:20:48 -07001375 // Log the start of watch.
Jae Seo31dc634be2014-04-15 17:40:23 -07001376 SomeArgs args = SomeArgs.obtain();
Jae Seo2cdb05e2016-02-04 22:17:13 +09001377 args.arg1 = sessionState.componentName.getPackageName();
Jae Seo7eb75df2014-08-08 22:20:48 -07001378 args.arg2 = System.currentTimeMillis();
1379 args.arg3 = ContentUris.parseId(channelUri);
1380 args.arg4 = params;
1381 args.arg5 = sessionToken;
1382 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_START, args)
1383 .sendToTarget();
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001384 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001385 Slog.e(TAG, "error in tune", e);
Jae Seo39570912014-02-20 18:23:25 -08001386 }
1387 }
1388 } finally {
1389 Binder.restoreCallingIdentity(identity);
1390 }
1391 }
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001392
1393 @Override
Jae Seoa9033832015-03-11 19:29:46 -07001394 public void unblockContent(
Sungsoo Lim9bf671f2014-07-19 12:59:51 +09001395 IBinder sessionToken, String unblockedRating, int userId) {
Dongwon Kange12d8102016-03-04 16:45:39 -08001396 ensureParentalControlsPermission();
Jaewan Kim903d6b72014-07-16 11:28:56 +09001397 final int callingUid = Binder.getCallingUid();
1398 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1399 userId, "unblockContent");
1400 final long identity = Binder.clearCallingIdentity();
1401 try {
1402 synchronized (mLock) {
1403 try {
1404 getSessionLocked(sessionToken, callingUid, resolvedUserId)
Jae Seoa9033832015-03-11 19:29:46 -07001405 .unblockContent(unblockedRating);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001406 } catch (RemoteException | SessionNotFoundException e) {
Jae Seoa9033832015-03-11 19:29:46 -07001407 Slog.e(TAG, "error in unblockContent", e);
Jaewan Kim903d6b72014-07-16 11:28:56 +09001408 }
1409 }
1410 } finally {
1411 Binder.restoreCallingIdentity(identity);
1412 }
1413 }
1414
1415 @Override
Jae Seo2c1c31c2014-07-10 14:57:01 -07001416 public void setCaptionEnabled(IBinder sessionToken, boolean enabled, int userId) {
1417 final int callingUid = Binder.getCallingUid();
1418 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1419 userId, "setCaptionEnabled");
1420 final long identity = Binder.clearCallingIdentity();
1421 try {
1422 synchronized (mLock) {
1423 try {
1424 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1425 .setCaptionEnabled(enabled);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001426 } catch (RemoteException | SessionNotFoundException e) {
Jae Seo2c1c31c2014-07-10 14:57:01 -07001427 Slog.e(TAG, "error in setCaptionEnabled", e);
1428 }
1429 }
1430 } finally {
1431 Binder.restoreCallingIdentity(identity);
1432 }
1433 }
1434
1435 @Override
Jae Seo10d285a2014-07-31 22:46:47 +09001436 public void selectTrack(IBinder sessionToken, int type, String trackId, int userId) {
Dongwon Kang1f213912014-07-02 18:35:08 +09001437 final int callingUid = Binder.getCallingUid();
1438 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1439 userId, "selectTrack");
1440 final long identity = Binder.clearCallingIdentity();
1441 try {
1442 synchronized (mLock) {
1443 try {
1444 getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack(
Jae Seo10d285a2014-07-31 22:46:47 +09001445 type, trackId);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001446 } catch (RemoteException | SessionNotFoundException e) {
Dongwon Kang1f213912014-07-02 18:35:08 +09001447 Slog.e(TAG, "error in selectTrack", e);
1448 }
1449 }
1450 } finally {
1451 Binder.restoreCallingIdentity(identity);
1452 }
1453 }
1454
1455 @Override
Jae Seoa759b112014-07-18 22:16:08 -07001456 public void sendAppPrivateCommand(IBinder sessionToken, String command, Bundle data,
1457 int userId) {
1458 final int callingUid = Binder.getCallingUid();
1459 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1460 userId, "sendAppPrivateCommand");
1461 final long identity = Binder.clearCallingIdentity();
1462 try {
1463 synchronized (mLock) {
1464 try {
1465 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1466 .appPrivateCommand(command, data);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001467 } catch (RemoteException | SessionNotFoundException e) {
Jae Seofea8dd42014-08-26 13:57:41 -07001468 Slog.e(TAG, "error in appPrivateCommand", e);
Jae Seoa759b112014-07-18 22:16:08 -07001469 }
1470 }
1471 } finally {
1472 Binder.restoreCallingIdentity(identity);
1473 }
1474 }
1475
1476 @Override
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001477 public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
1478 int userId) {
1479 final int callingUid = Binder.getCallingUid();
1480 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1481 userId, "createOverlayView");
1482 final long identity = Binder.clearCallingIdentity();
1483 try {
1484 synchronized (mLock) {
1485 try {
1486 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1487 .createOverlayView(windowToken, frame);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001488 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001489 Slog.e(TAG, "error in createOverlayView", e);
1490 }
1491 }
1492 } finally {
1493 Binder.restoreCallingIdentity(identity);
1494 }
1495 }
1496
1497 @Override
1498 public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
1499 final int callingUid = Binder.getCallingUid();
1500 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1501 userId, "relayoutOverlayView");
1502 final long identity = Binder.clearCallingIdentity();
1503 try {
1504 synchronized (mLock) {
1505 try {
1506 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1507 .relayoutOverlayView(frame);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001508 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001509 Slog.e(TAG, "error in relayoutOverlayView", e);
1510 }
1511 }
1512 } finally {
1513 Binder.restoreCallingIdentity(identity);
1514 }
1515 }
1516
1517 @Override
1518 public void removeOverlayView(IBinder sessionToken, int userId) {
1519 final int callingUid = Binder.getCallingUid();
1520 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1521 userId, "removeOverlayView");
1522 final long identity = Binder.clearCallingIdentity();
1523 try {
1524 synchronized (mLock) {
1525 try {
1526 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1527 .removeOverlayView();
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001528 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001529 Slog.e(TAG, "error in removeOverlayView", e);
1530 }
1531 }
1532 } finally {
1533 Binder.restoreCallingIdentity(identity);
1534 }
1535 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001536
1537 @Override
Jae Seoa826d012016-01-18 13:03:35 -08001538 public void timeShiftPlay(IBinder sessionToken, final Uri recordedProgramUri, int userId) {
1539 final int callingUid = Binder.getCallingUid();
1540 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1541 userId, "timeShiftPlay");
1542 final long identity = Binder.clearCallingIdentity();
1543 try {
1544 synchronized (mLock) {
1545 try {
1546 getSessionLocked(sessionToken, callingUid, resolvedUserId).timeShiftPlay(
1547 recordedProgramUri);
1548 } catch (RemoteException | SessionNotFoundException e) {
1549 Slog.e(TAG, "error in timeShiftPlay", e);
1550 }
1551 }
1552 } finally {
1553 Binder.restoreCallingIdentity(identity);
1554 }
1555 }
1556
1557 @Override
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001558 public void timeShiftPause(IBinder sessionToken, int userId) {
1559 final int callingUid = Binder.getCallingUid();
1560 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1561 userId, "timeShiftPause");
1562 final long identity = Binder.clearCallingIdentity();
1563 try {
1564 synchronized (mLock) {
1565 try {
Jae Seoa826d012016-01-18 13:03:35 -08001566 getSessionLocked(sessionToken, callingUid, resolvedUserId).timeShiftPause();
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001567 } catch (RemoteException | SessionNotFoundException e) {
1568 Slog.e(TAG, "error in timeShiftPause", e);
1569 }
1570 }
1571 } finally {
1572 Binder.restoreCallingIdentity(identity);
1573 }
1574 }
1575
1576 @Override
1577 public void timeShiftResume(IBinder sessionToken, int userId) {
1578 final int callingUid = Binder.getCallingUid();
1579 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1580 userId, "timeShiftResume");
1581 final long identity = Binder.clearCallingIdentity();
1582 try {
1583 synchronized (mLock) {
1584 try {
1585 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1586 .timeShiftResume();
1587 } catch (RemoteException | SessionNotFoundException e) {
1588 Slog.e(TAG, "error in timeShiftResume", e);
1589 }
1590 }
1591 } finally {
1592 Binder.restoreCallingIdentity(identity);
1593 }
1594 }
1595
1596 @Override
1597 public void timeShiftSeekTo(IBinder sessionToken, long timeMs, int userId) {
1598 final int callingUid = Binder.getCallingUid();
1599 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1600 userId, "timeShiftSeekTo");
1601 final long identity = Binder.clearCallingIdentity();
1602 try {
1603 synchronized (mLock) {
1604 try {
1605 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1606 .timeShiftSeekTo(timeMs);
1607 } catch (RemoteException | SessionNotFoundException e) {
1608 Slog.e(TAG, "error in timeShiftSeekTo", e);
1609 }
1610 }
1611 } finally {
1612 Binder.restoreCallingIdentity(identity);
1613 }
1614 }
1615
1616 @Override
Jae Seo4b34cc72015-05-15 17:29:39 -07001617 public void timeShiftSetPlaybackParams(IBinder sessionToken, PlaybackParams params,
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001618 int userId) {
1619 final int callingUid = Binder.getCallingUid();
1620 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
Jae Seo4b34cc72015-05-15 17:29:39 -07001621 userId, "timeShiftSetPlaybackParams");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001622 final long identity = Binder.clearCallingIdentity();
1623 try {
1624 synchronized (mLock) {
1625 try {
1626 getSessionLocked(sessionToken, callingUid, resolvedUserId)
Jae Seo4b34cc72015-05-15 17:29:39 -07001627 .timeShiftSetPlaybackParams(params);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001628 } catch (RemoteException | SessionNotFoundException e) {
Jae Seo4b34cc72015-05-15 17:29:39 -07001629 Slog.e(TAG, "error in timeShiftSetPlaybackParams", e);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001630 }
1631 }
1632 } finally {
1633 Binder.restoreCallingIdentity(identity);
1634 }
1635 }
1636
1637 @Override
Jae Seo465f0d62015-04-06 18:40:46 -07001638 public void timeShiftEnablePositionTracking(IBinder sessionToken, boolean enable,
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001639 int userId) {
1640 final int callingUid = Binder.getCallingUid();
1641 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
Jae Seo465f0d62015-04-06 18:40:46 -07001642 userId, "timeShiftEnablePositionTracking");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001643 final long identity = Binder.clearCallingIdentity();
1644 try {
1645 synchronized (mLock) {
1646 try {
1647 getSessionLocked(sessionToken, callingUid, resolvedUserId)
Jae Seo465f0d62015-04-06 18:40:46 -07001648 .timeShiftEnablePositionTracking(enable);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001649 } catch (RemoteException | SessionNotFoundException e) {
Jae Seo465f0d62015-04-06 18:40:46 -07001650 Slog.e(TAG, "error in timeShiftEnablePositionTracking", e);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001651 }
1652 }
1653 } finally {
1654 Binder.restoreCallingIdentity(identity);
1655 }
1656 }
1657
1658 @Override
Dongwon Kang0cb52442016-04-22 11:00:03 -07001659 public void startRecording(IBinder sessionToken, @Nullable Uri programUri, int userId) {
Jae Seoa826d012016-01-18 13:03:35 -08001660 final int callingUid = Binder.getCallingUid();
1661 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1662 userId, "startRecording");
1663 final long identity = Binder.clearCallingIdentity();
1664 try {
1665 synchronized (mLock) {
1666 try {
Jae Seo4eee6a72016-02-06 11:11:35 +09001667 getSessionLocked(sessionToken, callingUid, resolvedUserId).startRecording(
Dongwon Kang0cb52442016-04-22 11:00:03 -07001668 programUri);
Jae Seoa826d012016-01-18 13:03:35 -08001669 } catch (RemoteException | SessionNotFoundException e) {
1670 Slog.e(TAG, "error in startRecording", e);
1671 }
1672 }
1673 } finally {
1674 Binder.restoreCallingIdentity(identity);
1675 }
1676 }
1677
1678 @Override
1679 public void stopRecording(IBinder sessionToken, int userId) {
1680 final int callingUid = Binder.getCallingUid();
1681 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1682 userId, "stopRecording");
1683 final long identity = Binder.clearCallingIdentity();
1684 try {
1685 synchronized (mLock) {
1686 try {
1687 getSessionLocked(sessionToken, callingUid, resolvedUserId).stopRecording();
1688 } catch (RemoteException | SessionNotFoundException e) {
1689 Slog.e(TAG, "error in stopRecording", e);
1690 }
1691 }
1692 } finally {
1693 Binder.restoreCallingIdentity(identity);
1694 }
1695 }
1696
1697 @Override
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001698 public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
Wonsik Kim969167d2014-06-24 16:33:17 +09001699 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001700 != PackageManager.PERMISSION_GRANTED) {
1701 return null;
1702 }
1703
1704 final long identity = Binder.clearCallingIdentity();
1705 try {
1706 return mTvInputHardwareManager.getHardwareList();
1707 } finally {
1708 Binder.restoreCallingIdentity(identity);
1709 }
1710 }
1711
1712 @Override
1713 public ITvInputHardware acquireTvInputHardware(int deviceId,
Wonsik Kim969167d2014-06-24 16:33:17 +09001714 ITvInputHardwareCallback callback, TvInputInfo info, int userId)
1715 throws RemoteException {
1716 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001717 != PackageManager.PERMISSION_GRANTED) {
1718 return null;
1719 }
1720
1721 final long identity = Binder.clearCallingIdentity();
1722 final int callingUid = Binder.getCallingUid();
1723 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1724 userId, "acquireTvInputHardware");
1725 try {
1726 return mTvInputHardwareManager.acquireHardware(
Wonsik Kim969167d2014-06-24 16:33:17 +09001727 deviceId, callback, info, callingUid, resolvedUserId);
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001728 } finally {
1729 Binder.restoreCallingIdentity(identity);
1730 }
1731 }
1732
1733 @Override
1734 public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
1735 throws RemoteException {
Wonsik Kim969167d2014-06-24 16:33:17 +09001736 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001737 != PackageManager.PERMISSION_GRANTED) {
1738 return;
1739 }
1740
1741 final long identity = Binder.clearCallingIdentity();
1742 final int callingUid = Binder.getCallingUid();
1743 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1744 userId, "releaseTvInputHardware");
1745 try {
1746 mTvInputHardwareManager.releaseHardware(
1747 deviceId, hardware, callingUid, resolvedUserId);
1748 } finally {
1749 Binder.restoreCallingIdentity(identity);
1750 }
1751 }
Jaewan Kime14c3f42014-06-27 13:47:48 +09001752
1753 @Override
Jaesung Chung58739e72015-04-24 19:39:59 +09001754 public List<DvbDeviceInfo> getDvbDeviceList() throws RemoteException {
1755 if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)
1756 != PackageManager.PERMISSION_GRANTED) {
1757 throw new SecurityException("Requires DVB_DEVICE permission");
1758 }
1759
1760 final long identity = Binder.clearCallingIdentity();
1761 try {
Jiabinef1659f2016-11-18 10:50:15 +09001762 // Pattern1: /dev/dvb%d.frontend%d
1763 ArrayList<DvbDeviceInfo> deviceInfosFromPattern1 = new ArrayList<>();
Jaesung Chung58739e72015-04-24 19:39:59 +09001764 File devDirectory = new File("/dev");
Jiabinef1659f2016-11-18 10:50:15 +09001765 boolean dvbDirectoryFound = false;
Jaesung Chung58739e72015-04-24 19:39:59 +09001766 for (String fileName : devDirectory.list()) {
1767 Matcher matcher = sFrontEndDevicePattern.matcher(fileName);
1768 if (matcher.find()) {
1769 int adapterId = Integer.parseInt(matcher.group(1));
1770 int deviceId = Integer.parseInt(matcher.group(2));
Jiabinef1659f2016-11-18 10:50:15 +09001771 deviceInfosFromPattern1.add(new DvbDeviceInfo(adapterId, deviceId));
1772 }
1773 if (TextUtils.equals("dvb", fileName)) {
1774 dvbDirectoryFound = true;
Jaesung Chung58739e72015-04-24 19:39:59 +09001775 }
1776 }
Jiabinef1659f2016-11-18 10:50:15 +09001777 if (!dvbDirectoryFound) {
1778 return Collections.unmodifiableList(deviceInfosFromPattern1);
1779 }
1780 File dvbDirectory = new File(DVB_DIRECTORY);
1781 // Pattern2: /dev/dvb/adapter%d/frontend%d
1782 ArrayList<DvbDeviceInfo> deviceInfosFromPattern2 = new ArrayList<>();
1783 for (String fileNameInDvb : dvbDirectory.list()) {
1784 Matcher adapterMatcher = sAdapterDirPattern.matcher(fileNameInDvb);
1785 if (adapterMatcher.find()) {
1786 int adapterId = Integer.parseInt(adapterMatcher.group(1));
1787 File adapterDirectory = new File(DVB_DIRECTORY + "/" + fileNameInDvb);
1788 for (String fileNameInAdapter : adapterDirectory.list()) {
1789 Matcher frontendMatcher = sFrontEndInAdapterDirPattern.matcher(
1790 fileNameInAdapter);
1791 if (frontendMatcher.find()) {
1792 int deviceId = Integer.parseInt(frontendMatcher.group(1));
1793 deviceInfosFromPattern2.add(
1794 new DvbDeviceInfo(adapterId, deviceId));
1795 }
1796 }
1797 }
1798 }
1799 return deviceInfosFromPattern2.isEmpty()
1800 ? Collections.unmodifiableList(deviceInfosFromPattern1)
1801 : Collections.unmodifiableList(deviceInfosFromPattern2);
Jaesung Chung58739e72015-04-24 19:39:59 +09001802 } finally {
1803 Binder.restoreCallingIdentity(identity);
1804 }
1805 }
1806
1807 @Override
1808 public ParcelFileDescriptor openDvbDevice(DvbDeviceInfo info, int device)
1809 throws RemoteException {
1810 if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)
1811 != PackageManager.PERMISSION_GRANTED) {
1812 throw new SecurityException("Requires DVB_DEVICE permission");
1813 }
1814
Jiabinef1659f2016-11-18 10:50:15 +09001815 File devDirectory = new File("/dev");
1816 boolean dvbDeviceFound = false;
1817 for (String fileName : devDirectory.list()) {
1818 if (TextUtils.equals("dvb", fileName)) {
1819 File dvbDirectory = new File(DVB_DIRECTORY);
1820 for (String fileNameInDvb : dvbDirectory.list()) {
1821 Matcher adapterMatcher = sAdapterDirPattern.matcher(fileNameInDvb);
1822 if (adapterMatcher.find()) {
1823 File adapterDirectory = new File(DVB_DIRECTORY + "/" + fileNameInDvb);
1824 for (String fileNameInAdapter : adapterDirectory.list()) {
1825 Matcher frontendMatcher = sFrontEndInAdapterDirPattern.matcher(
1826 fileNameInAdapter);
1827 if (frontendMatcher.find()) {
1828 dvbDeviceFound = true;
1829 break;
1830 }
1831 }
1832 }
1833 if (dvbDeviceFound) {
1834 break;
1835 }
1836 }
1837 }
1838 if (dvbDeviceFound) {
1839 break;
1840 }
1841 }
1842
Jaesung Chung58739e72015-04-24 19:39:59 +09001843 final long identity = Binder.clearCallingIdentity();
1844 try {
1845 String deviceFileName;
1846 switch (device) {
1847 case TvInputManager.DVB_DEVICE_DEMUX:
Jiabinef1659f2016-11-18 10:50:15 +09001848 deviceFileName = String.format(dvbDeviceFound
1849 ? "/dev/dvb/adapter%d/demux%d" : "/dev/dvb%d.demux%d",
1850 info.getAdapterId(), info.getDeviceId());
Jaesung Chung58739e72015-04-24 19:39:59 +09001851 break;
1852 case TvInputManager.DVB_DEVICE_DVR:
Jiabinef1659f2016-11-18 10:50:15 +09001853 deviceFileName = String.format(dvbDeviceFound
1854 ? "/dev/dvb/adapter%d/dvr%d" : "/dev/dvb%d.dvr%d",
1855 info.getAdapterId(), info.getDeviceId());
Jaesung Chung58739e72015-04-24 19:39:59 +09001856 break;
1857 case TvInputManager.DVB_DEVICE_FRONTEND:
Jiabinef1659f2016-11-18 10:50:15 +09001858 deviceFileName = String.format(dvbDeviceFound
1859 ? "/dev/dvb/adapter%d/frontend%d" : "/dev/dvb%d.frontend%d",
1860 info.getAdapterId(), info.getDeviceId());
Jaesung Chung58739e72015-04-24 19:39:59 +09001861 break;
1862 default:
1863 throw new IllegalArgumentException("Invalid DVB device: " + device);
1864 }
1865 try {
1866 // The DVB frontend device only needs to be opened in read/write mode, which
1867 // allows performing tuning operations. The DVB demux and DVR device are enough
1868 // to be opened in read only mode.
1869 return ParcelFileDescriptor.open(new File(deviceFileName),
1870 TvInputManager.DVB_DEVICE_FRONTEND == device
1871 ? ParcelFileDescriptor.MODE_READ_WRITE
1872 : ParcelFileDescriptor.MODE_READ_ONLY);
1873 } catch (FileNotFoundException e) {
1874 return null;
1875 }
1876 } finally {
1877 Binder.restoreCallingIdentity(identity);
1878 }
1879 }
1880
1881 @Override
Terry Heoc086a3d2014-06-18 14:26:44 +09001882 public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int userId)
1883 throws RemoteException {
Shubangdd3ec0b2017-06-23 13:54:10 -07001884 ensureCaptureTvInputPermission();
Terry Heoc086a3d2014-06-18 14:26:44 +09001885
1886 final long identity = Binder.clearCallingIdentity();
1887 final int callingUid = Binder.getCallingUid();
1888 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1889 userId, "getAvailableTvStreamConfigList");
1890 try {
1891 return mTvInputHardwareManager.getAvailableTvStreamConfigList(
1892 inputId, callingUid, resolvedUserId);
1893 } finally {
1894 Binder.restoreCallingIdentity(identity);
1895 }
1896 }
1897
1898 @Override
1899 public boolean captureFrame(String inputId, Surface surface, TvStreamConfig config,
1900 int userId)
1901 throws RemoteException {
Shubangdd3ec0b2017-06-23 13:54:10 -07001902 ensureCaptureTvInputPermission();
Terry Heoc086a3d2014-06-18 14:26:44 +09001903
1904 final long identity = Binder.clearCallingIdentity();
1905 final int callingUid = Binder.getCallingUid();
1906 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1907 userId, "captureFrame");
1908 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001909 String hardwareInputId = null;
Terry Heo79124a72014-07-21 15:17:17 +09001910 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001911 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001912 if (userState.inputMap.get(inputId) == null) {
Jae Seofea8dd42014-08-26 13:57:41 -07001913 Slog.e(TAG, "input not found for " + inputId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001914 return false;
1915 }
1916 for (SessionState sessionState : userState.sessionStateMap.values()) {
Jae Seo2cdb05e2016-02-04 22:17:13 +09001917 if (sessionState.inputId.equals(inputId)
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001918 && sessionState.hardwareSessionToken != null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001919 hardwareInputId = userState.sessionStateMap.get(
Jae Seo2cdb05e2016-02-04 22:17:13 +09001920 sessionState.hardwareSessionToken).inputId;
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001921 break;
1922 }
1923 }
Terry Heo79124a72014-07-21 15:17:17 +09001924 }
Terry Heoc086a3d2014-06-18 14:26:44 +09001925 return mTvInputHardwareManager.captureFrame(
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001926 (hardwareInputId != null) ? hardwareInputId : inputId,
Terry Heo79124a72014-07-21 15:17:17 +09001927 surface, config, callingUid, resolvedUserId);
Terry Heoc086a3d2014-06-18 14:26:44 +09001928 } finally {
1929 Binder.restoreCallingIdentity(identity);
1930 }
1931 }
1932
1933 @Override
Terry Heodf9f0a32014-08-06 13:53:33 +09001934 public boolean isSingleSessionActive(int userId) throws RemoteException {
Shubangdd3ec0b2017-06-23 13:54:10 -07001935 ensureCaptureTvInputPermission();
Terry Heodf9f0a32014-08-06 13:53:33 +09001936 final long identity = Binder.clearCallingIdentity();
1937 final int callingUid = Binder.getCallingUid();
1938 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1939 userId, "isSingleSessionActive");
1940 try {
1941 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001942 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Terry Heodf9f0a32014-08-06 13:53:33 +09001943 if (userState.sessionStateMap.size() == 1) {
1944 return true;
Jae Seo93ff14b2015-06-21 14:08:54 -07001945 } else if (userState.sessionStateMap.size() == 2) {
Terry Heodf9f0a32014-08-06 13:53:33 +09001946 SessionState[] sessionStates = userState.sessionStateMap.values().toArray(
Jae Seo93ff14b2015-06-21 14:08:54 -07001947 new SessionState[2]);
Terry Heodf9f0a32014-08-06 13:53:33 +09001948 // Check if there is a wrapper input.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001949 if (sessionStates[0].hardwareSessionToken != null
1950 || sessionStates[1].hardwareSessionToken != null) {
Terry Heodf9f0a32014-08-06 13:53:33 +09001951 return true;
1952 }
1953 }
1954 return false;
1955 }
1956 } finally {
1957 Binder.restoreCallingIdentity(identity);
1958 }
1959 }
1960
Shubangdd3ec0b2017-06-23 13:54:10 -07001961 private void ensureCaptureTvInputPermission() {
1962 if (mContext.checkCallingPermission(
1963 android.Manifest.permission.CAPTURE_TV_INPUT)
1964 != PackageManager.PERMISSION_GRANTED) {
1965 throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1966 }
1967 }
1968
Terry Heodf9f0a32014-08-06 13:53:33 +09001969 @Override
Dongwon Kang2e7f5ce2017-04-05 18:33:50 -07001970 public void requestChannelBrowsable(Uri channelUri, int userId)
1971 throws RemoteException {
1972 final String callingPackageName = getCallingPackageName();
1973 final long identity = Binder.clearCallingIdentity();
1974 final int callingUid = Binder.getCallingUid();
1975 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1976 userId, "requestChannelBrowsable");
1977 try {
1978 Intent intent = new Intent(TvContract.ACTION_CHANNEL_BROWSABLE_REQUESTED);
1979 List<ResolveInfo> list = getContext().getPackageManager()
1980 .queryBroadcastReceivers(intent, 0);
1981 if (list != null) {
1982 for (ResolveInfo info : list) {
1983 String receiverPackageName = info.activityInfo.packageName;
1984 intent.putExtra(TvContract.EXTRA_CHANNEL_ID, ContentUris.parseId(
1985 channelUri));
1986 intent.putExtra(TvContract.EXTRA_PACKAGE_NAME, callingPackageName);
1987 intent.setPackage(receiverPackageName);
1988 getContext().sendBroadcastAsUser(intent, new UserHandle(resolvedUserId));
1989 }
1990 }
1991 } finally {
1992 Binder.restoreCallingIdentity(identity);
1993 }
1994 }
1995
1996 @Override
Jae Seo0f8fc342014-07-02 10:47:08 -07001997 @SuppressWarnings("resource")
Jaewan Kime14c3f42014-06-27 13:47:48 +09001998 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1999 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -06002000 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
Jaewan Kime14c3f42014-06-27 13:47:48 +09002001
2002 synchronized (mLock) {
2003 pw.println("User Ids (Current user: " + mCurrentUserId + "):");
2004 pw.increaseIndent();
2005 for (int i = 0; i < mUserStates.size(); i++) {
2006 int userId = mUserStates.keyAt(i);
2007 pw.println(Integer.valueOf(userId));
2008 }
2009 pw.decreaseIndent();
2010
2011 for (int i = 0; i < mUserStates.size(); i++) {
2012 int userId = mUserStates.keyAt(i);
Jae Seo4f1a6d42015-07-20 16:15:01 -07002013 UserState userState = getOrCreateUserStateLocked(userId);
Jaewan Kime14c3f42014-06-27 13:47:48 +09002014 pw.println("UserState (" + userId + "):");
2015 pw.increaseIndent();
2016
Wonsik Kim969167d2014-06-24 16:33:17 +09002017 pw.println("inputMap: inputId -> TvInputState");
Jaewan Kime14c3f42014-06-27 13:47:48 +09002018 pw.increaseIndent();
Jaewan Kim8e6b51b2014-07-15 13:01:57 +09002019 for (Map.Entry<String, TvInputState> entry: userState.inputMap.entrySet()) {
2020 pw.println(entry.getKey() + ": " + entry.getValue());
Jaewan Kime14c3f42014-06-27 13:47:48 +09002021 }
2022 pw.decreaseIndent();
2023
Wonsik Kim969167d2014-06-24 16:33:17 +09002024 pw.println("packageSet:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09002025 pw.increaseIndent();
Wonsik Kim969167d2014-06-24 16:33:17 +09002026 for (String packageName : userState.packageSet) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09002027 pw.println(packageName);
2028 }
2029 pw.decreaseIndent();
2030
2031 pw.println("clientStateMap: ITvInputClient -> ClientState");
2032 pw.increaseIndent();
2033 for (Map.Entry<IBinder, ClientState> entry :
2034 userState.clientStateMap.entrySet()) {
2035 ClientState client = entry.getValue();
2036 pw.println(entry.getKey() + ": " + client);
2037
2038 pw.increaseIndent();
2039
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002040 pw.println("sessionTokens:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09002041 pw.increaseIndent();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002042 for (IBinder token : client.sessionTokens) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09002043 pw.println("" + token);
2044 }
2045 pw.decreaseIndent();
2046
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002047 pw.println("clientTokens: " + client.clientToken);
2048 pw.println("userId: " + client.userId);
Jaewan Kime14c3f42014-06-27 13:47:48 +09002049
2050 pw.decreaseIndent();
2051 }
2052 pw.decreaseIndent();
2053
Wonsik Kim187423c2014-06-25 14:12:48 +09002054 pw.println("serviceStateMap: ComponentName -> ServiceState");
Jaewan Kime14c3f42014-06-27 13:47:48 +09002055 pw.increaseIndent();
Wonsik Kim187423c2014-06-25 14:12:48 +09002056 for (Map.Entry<ComponentName, ServiceState> entry :
Jaewan Kime14c3f42014-06-27 13:47:48 +09002057 userState.serviceStateMap.entrySet()) {
2058 ServiceState service = entry.getValue();
2059 pw.println(entry.getKey() + ": " + service);
2060
2061 pw.increaseIndent();
2062
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002063 pw.println("sessionTokens:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09002064 pw.increaseIndent();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002065 for (IBinder token : service.sessionTokens) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09002066 pw.println("" + token);
2067 }
2068 pw.decreaseIndent();
2069
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002070 pw.println("service: " + service.service);
2071 pw.println("callback: " + service.callback);
2072 pw.println("bound: " + service.bound);
2073 pw.println("reconnecting: " + service.reconnecting);
Jaewan Kime14c3f42014-06-27 13:47:48 +09002074
2075 pw.decreaseIndent();
2076 }
2077 pw.decreaseIndent();
2078
2079 pw.println("sessionStateMap: ITvInputSession -> SessionState");
2080 pw.increaseIndent();
2081 for (Map.Entry<IBinder, SessionState> entry :
2082 userState.sessionStateMap.entrySet()) {
2083 SessionState session = entry.getValue();
2084 pw.println(entry.getKey() + ": " + session);
2085
2086 pw.increaseIndent();
Jae Seo2cdb05e2016-02-04 22:17:13 +09002087 pw.println("inputId: " + session.inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002088 pw.println("client: " + session.client);
2089 pw.println("seq: " + session.seq);
2090 pw.println("callingUid: " + session.callingUid);
2091 pw.println("userId: " + session.userId);
2092 pw.println("sessionToken: " + session.sessionToken);
2093 pw.println("session: " + session.session);
2094 pw.println("logUri: " + session.logUri);
2095 pw.println("hardwareSessionToken: " + session.hardwareSessionToken);
Jaewan Kime14c3f42014-06-27 13:47:48 +09002096 pw.decreaseIndent();
2097 }
2098 pw.decreaseIndent();
2099
Wonsik Kim969167d2014-06-24 16:33:17 +09002100 pw.println("callbackSet:");
2101 pw.increaseIndent();
2102 for (ITvInputManagerCallback callback : userState.callbackSet) {
2103 pw.println(callback.toString());
2104 }
2105 pw.decreaseIndent();
2106
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09002107 pw.println("mainSessionToken: " + userState.mainSessionToken);
Jaewan Kime14c3f42014-06-27 13:47:48 +09002108 pw.decreaseIndent();
2109 }
2110 }
yangren8d718e12016-02-15 17:02:49 +09002111 mTvInputHardwareManager.dump(fd, writer, args);
Jaewan Kime14c3f42014-06-27 13:47:48 +09002112 }
Jae Seo39570912014-02-20 18:23:25 -08002113 }
2114
Wonsik Kim969167d2014-06-24 16:33:17 +09002115 private static final class UserState {
2116 // A mapping from the TV input id to its TvInputState.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002117 private Map<String, TvInputState> inputMap = new HashMap<>();
Wonsik Kim969167d2014-06-24 16:33:17 +09002118
2119 // A set of all TV input packages.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002120 private final Set<String> packageSet = new HashSet<>();
Jae Seo5c80ad22014-06-12 19:52:58 -07002121
Jae Seo9c165d62014-08-25 14:39:26 -07002122 // A list of all TV content rating systems defined.
2123 private final List<TvContentRatingSystemInfo>
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002124 contentRatingSystemList = new ArrayList<>();
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +09002125
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002126 // A mapping from the token of a client to its state.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002127 private final Map<IBinder, ClientState> clientStateMap = new HashMap<>();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002128
Jae Seo39570912014-02-20 18:23:25 -08002129 // A mapping from the name of a TV input service to its state.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002130 private final Map<ComponentName, ServiceState> serviceStateMap = new HashMap<>();
Jae Seo39570912014-02-20 18:23:25 -08002131
2132 // A mapping from the token of a TV input session to its state.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002133 private final Map<IBinder, SessionState> sessionStateMap = new HashMap<>();
Wonsik Kim969167d2014-06-24 16:33:17 +09002134
2135 // A set of callbacks.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002136 private final Set<ITvInputManagerCallback> callbackSet = new HashSet<>();
Terry Heo79124a72014-07-21 15:17:17 +09002137
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09002138 // The token of a "main" TV input session.
2139 private IBinder mainSessionToken = null;
Jae Seo783645e2014-07-28 17:30:50 +09002140
2141 // Persistent data store for all internal settings maintained by the TV input manager
2142 // service.
2143 private final PersistentDataStore persistentDataStore;
2144
2145 private UserState(Context context, int userId) {
2146 persistentDataStore = new PersistentDataStore(context, userId);
2147 }
Jae Seo39570912014-02-20 18:23:25 -08002148 }
2149
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002150 private final class ClientState implements IBinder.DeathRecipient {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002151 private final List<IBinder> sessionTokens = new ArrayList<>();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002152
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002153 private IBinder clientToken;
2154 private final int userId;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002155
2156 ClientState(IBinder clientToken, int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002157 this.clientToken = clientToken;
2158 this.userId = userId;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002159 }
2160
2161 public boolean isEmpty() {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002162 return sessionTokens.isEmpty();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002163 }
2164
2165 @Override
2166 public void binderDied() {
2167 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002168 UserState userState = getOrCreateUserStateLocked(userId);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002169 // DO NOT remove the client state of clientStateMap in this method. It will be
Ji-Hwan Leea65118e2014-07-24 16:30:02 +09002170 // removed in releaseSessionLocked().
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002171 ClientState clientState = userState.clientStateMap.get(clientToken);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002172 if (clientState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002173 while (clientState.sessionTokens.size() > 0) {
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002174 releaseSessionLocked(
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002175 clientState.sessionTokens.get(0), Process.SYSTEM_UID, userId);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002176 }
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002177 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002178 clientToken = null;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002179 }
2180 }
2181 }
2182
Jae Seo39570912014-02-20 18:23:25 -08002183 private final class ServiceState {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002184 private final List<IBinder> sessionTokens = new ArrayList<>();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002185 private final ServiceConnection connection;
2186 private final ComponentName component;
2187 private final boolean isHardware;
Shubang71d5c762017-02-23 15:46:40 -08002188 private final Map<String, TvInputInfo> hardwareInputMap = new HashMap<>();
Jae Seo39570912014-02-20 18:23:25 -08002189
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002190 private ITvInputService service;
2191 private ServiceCallback callback;
2192 private boolean bound;
2193 private boolean reconnecting;
Jae Seo39570912014-02-20 18:23:25 -08002194
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002195 private ServiceState(ComponentName component, int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002196 this.component = component;
2197 this.connection = new InputServiceConnection(component, userId);
2198 this.isHardware = hasHardwarePermission(mContext.getPackageManager(), component);
2199 }
2200 }
2201
2202 private static final class TvInputState {
2203 // A TvInputInfo object which represents the TV input.
2204 private TvInputInfo info;
2205
2206 // The state of TV input. Connected by default.
2207 private int state = INPUT_STATE_CONNECTED;
2208
2209 @Override
2210 public String toString() {
2211 return "info: " + info + "; state: " + state;
Jae Seo39570912014-02-20 18:23:25 -08002212 }
2213 }
2214
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002215 private final class SessionState implements IBinder.DeathRecipient {
Jae Seo2cdb05e2016-02-04 22:17:13 +09002216 private final String inputId;
2217 private final ComponentName componentName;
Jae Seoa826d012016-01-18 13:03:35 -08002218 private final boolean isRecordingSession;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002219 private final ITvInputClient client;
2220 private final int seq;
2221 private final int callingUid;
2222 private final int userId;
2223 private final IBinder sessionToken;
2224 private ITvInputSession session;
2225 private Uri logUri;
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09002226 // Not null if this session represents an external device connected to a hardware TV input.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002227 private IBinder hardwareSessionToken;
Jae Seo39570912014-02-20 18:23:25 -08002228
Jae Seo2cdb05e2016-02-04 22:17:13 +09002229 private SessionState(IBinder sessionToken, String inputId, ComponentName componentName,
2230 boolean isRecordingSession, ITvInputClient client, int seq, int callingUid,
2231 int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002232 this.sessionToken = sessionToken;
Jae Seo2cdb05e2016-02-04 22:17:13 +09002233 this.inputId = inputId;
2234 this.componentName = componentName;
Jae Seoa826d012016-01-18 13:03:35 -08002235 this.isRecordingSession = isRecordingSession;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002236 this.client = client;
2237 this.seq = seq;
2238 this.callingUid = callingUid;
2239 this.userId = userId;
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002240 }
2241
2242 @Override
2243 public void binderDied() {
2244 synchronized (mLock) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002245 session = null;
shubang8049f202016-04-25 11:21:42 -07002246 clearSessionAndNotifyClientLocked(this);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002247 }
Jae Seo39570912014-02-20 18:23:25 -08002248 }
2249 }
2250
2251 private final class InputServiceConnection implements ServiceConnection {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002252 private final ComponentName mComponent;
Jae Seo39570912014-02-20 18:23:25 -08002253 private final int mUserId;
2254
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002255 private InputServiceConnection(ComponentName component, int userId) {
2256 mComponent = component;
Jae Seo39570912014-02-20 18:23:25 -08002257 mUserId = userId;
2258 }
2259
2260 @Override
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002261 public void onServiceConnected(ComponentName component, IBinder service) {
Jae Seo39570912014-02-20 18:23:25 -08002262 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002263 Slog.d(TAG, "onServiceConnected(component=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -08002264 }
2265 synchronized (mLock) {
Dongwon Kang81e3c3e2015-09-11 15:24:25 -07002266 UserState userState = mUserStates.get(mUserId);
2267 if (userState == null) {
2268 // The user was removed while connecting.
2269 mContext.unbindService(this);
2270 return;
2271 }
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002272 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002273 serviceState.service = ITvInputService.Stub.asInterface(service);
Jae Seo39570912014-02-20 18:23:25 -08002274
2275 // Register a callback, if we need to.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002276 if (serviceState.isHardware && serviceState.callback == null) {
2277 serviceState.callback = new ServiceCallback(mComponent, mUserId);
Jae Seo39570912014-02-20 18:23:25 -08002278 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002279 serviceState.service.registerCallback(serviceState.callback);
Jae Seo39570912014-02-20 18:23:25 -08002280 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09002281 Slog.e(TAG, "error in registerCallback", e);
Jae Seo39570912014-02-20 18:23:25 -08002282 }
2283 }
2284
2285 // And create sessions, if any.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002286 for (IBinder sessionToken : serviceState.sessionTokens) {
2287 createSessionInternalLocked(serviceState.service, sessionToken, mUserId);
Jae Seo39570912014-02-20 18:23:25 -08002288 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002289
Wonsik Kim187423c2014-06-25 14:12:48 +09002290 for (TvInputState inputState : userState.inputMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002291 if (inputState.info.getComponent().equals(component)
Jae Seo82fce642015-04-20 15:37:50 -07002292 && inputState.state != INPUT_STATE_CONNECTED) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002293 notifyInputStateChangedLocked(userState, inputState.info.getId(),
2294 inputState.state, null);
Wonsik Kim187423c2014-06-25 14:12:48 +09002295 }
2296 }
2297
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002298 if (serviceState.isHardware) {
Shubang71d5c762017-02-23 15:46:40 -08002299 serviceState.hardwareInputMap.clear();
Jae Seoc980f43d2016-02-09 23:46:58 -08002300 for (TvInputHardwareInfo hardware : mTvInputHardwareManager.getHardwareList()) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002301 try {
Jae Seoc980f43d2016-02-09 23:46:58 -08002302 serviceState.service.notifyHardwareAdded(hardware);
Wonsik Kim187423c2014-06-25 14:12:48 +09002303 } catch (RemoteException e) {
2304 Slog.e(TAG, "error in notifyHardwareAdded", e);
2305 }
2306 }
Jae Seoc980f43d2016-02-09 23:46:58 -08002307 for (HdmiDeviceInfo device : mTvInputHardwareManager.getHdmiDeviceList()) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002308 try {
Jae Seoc980f43d2016-02-09 23:46:58 -08002309 serviceState.service.notifyHdmiDeviceAdded(device);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002310 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002311 Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002312 }
2313 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002314 }
Jae Seo39570912014-02-20 18:23:25 -08002315 }
2316 }
2317
2318 @Override
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002319 public void onServiceDisconnected(ComponentName component) {
Jae Seo39570912014-02-20 18:23:25 -08002320 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002321 Slog.d(TAG, "onServiceDisconnected(component=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -08002322 }
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002323 if (!mComponent.equals(component)) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002324 throw new IllegalArgumentException("Mismatched ComponentName: "
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002325 + mComponent + " (expected), " + component + " (actual).");
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002326 }
2327 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002328 UserState userState = getOrCreateUserStateLocked(mUserId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002329 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002330 if (serviceState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002331 serviceState.reconnecting = true;
2332 serviceState.bound = false;
2333 serviceState.service = null;
2334 serviceState.callback = null;
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002335
Dongwon Kang426c9a42014-08-26 17:39:21 +09002336 abortPendingCreateSessionRequestsLocked(serviceState, null, mUserId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002337 }
2338 }
Jae Seo39570912014-02-20 18:23:25 -08002339 }
2340 }
2341
2342 private final class ServiceCallback extends ITvInputServiceCallback.Stub {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002343 private final ComponentName mComponent;
Jae Seo39570912014-02-20 18:23:25 -08002344 private final int mUserId;
2345
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002346 ServiceCallback(ComponentName component, int userId) {
2347 mComponent = component;
Jae Seo39570912014-02-20 18:23:25 -08002348 mUserId = userId;
2349 }
2350
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002351 private void ensureHardwarePermission() {
2352 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
2353 != PackageManager.PERMISSION_GRANTED) {
2354 throw new SecurityException("The caller does not have hardware permission");
2355 }
2356 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002357
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002358 private void ensureValidInput(TvInputInfo inputInfo) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002359 if (inputInfo.getId() == null || !mComponent.equals(inputInfo.getComponent())) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002360 throw new IllegalArgumentException("Invalid TvInputInfo");
2361 }
2362 }
2363
Jae Seo1abbbcd2016-01-28 22:20:41 -08002364 private void addHardwareInputLocked(TvInputInfo inputInfo) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002365 ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
Shubang71d5c762017-02-23 15:46:40 -08002366 serviceState.hardwareInputMap.put(inputInfo.getId(), inputInfo);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +09002367 buildTvInputListLocked(mUserId, null);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002368 }
2369
Jae Seo1abbbcd2016-01-28 22:20:41 -08002370 public void addHardwareInput(int deviceId, TvInputInfo inputInfo) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002371 ensureHardwarePermission();
2372 ensureValidInput(inputInfo);
2373 synchronized (mLock) {
Jae Seo1abbbcd2016-01-28 22:20:41 -08002374 mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo);
2375 addHardwareInputLocked(inputInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002376 }
2377 }
2378
Jae Seo1abbbcd2016-01-28 22:20:41 -08002379 public void addHdmiInput(int id, TvInputInfo inputInfo) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002380 ensureHardwarePermission();
2381 ensureValidInput(inputInfo);
2382 synchronized (mLock) {
Jae Seo1abbbcd2016-01-28 22:20:41 -08002383 mTvInputHardwareManager.addHdmiInput(id, inputInfo);
2384 addHardwareInputLocked(inputInfo);
Wonsik Kim187423c2014-06-25 14:12:48 +09002385 }
2386 }
2387
Jae Seo1abbbcd2016-01-28 22:20:41 -08002388 public void removeHardwareInput(String inputId) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002389 ensureHardwarePermission();
Wonsik Kim187423c2014-06-25 14:12:48 +09002390 synchronized (mLock) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002391 ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
Shubang71d5c762017-02-23 15:46:40 -08002392 boolean removed = serviceState.hardwareInputMap.remove(inputId) != null;
Wonsik Kim187423c2014-06-25 14:12:48 +09002393 if (removed) {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +09002394 buildTvInputListLocked(mUserId, null);
Jae Seo1abbbcd2016-01-28 22:20:41 -08002395 mTvInputHardwareManager.removeHardwareInput(inputId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002396 } else {
Jae Seofea8dd42014-08-26 13:57:41 -07002397 Slog.e(TAG, "failed to remove input " + inputId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002398 }
Jae Seo39570912014-02-20 18:23:25 -08002399 }
2400 }
2401 }
Jae Seo31dc634be2014-04-15 17:40:23 -07002402
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002403 private final class SessionCallback extends ITvInputSessionCallback.Stub {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002404 private final SessionState mSessionState;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002405 private final InputChannel[] mChannels;
2406
2407 SessionCallback(SessionState sessionState, InputChannel[] channels) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002408 mSessionState = sessionState;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002409 mChannels = channels;
2410 }
2411
2412 @Override
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002413 public void onSessionCreated(ITvInputSession session, IBinder hardwareSessionToken) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002414 if (DEBUG) {
Jae Seo2cdb05e2016-02-04 22:17:13 +09002415 Slog.d(TAG, "onSessionCreated(inputId=" + mSessionState.inputId + ")");
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002416 }
2417 synchronized (mLock) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002418 mSessionState.session = session;
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002419 mSessionState.hardwareSessionToken = hardwareSessionToken;
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002420 if (session != null && addSessionTokenToClientStateLocked(session)) {
2421 sendSessionTokenToClientLocked(mSessionState.client,
Jae Seo2cdb05e2016-02-04 22:17:13 +09002422 mSessionState.inputId, mSessionState.sessionToken, mChannels[0],
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002423 mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002424 } else {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002425 removeSessionStateLocked(mSessionState.sessionToken, mSessionState.userId);
2426 sendSessionTokenToClientLocked(mSessionState.client,
Jae Seo2cdb05e2016-02-04 22:17:13 +09002427 mSessionState.inputId, null, null, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002428 }
2429 mChannels[0].dispose();
2430 }
2431 }
2432
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002433 private boolean addSessionTokenToClientStateLocked(ITvInputSession session) {
2434 try {
2435 session.asBinder().linkToDeath(mSessionState, 0);
2436 } catch (RemoteException e) {
2437 Slog.e(TAG, "session process has already died", e);
2438 return false;
2439 }
2440
2441 IBinder clientToken = mSessionState.client.asBinder();
Jae Seo4f1a6d42015-07-20 16:15:01 -07002442 UserState userState = getOrCreateUserStateLocked(mSessionState.userId);
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002443 ClientState clientState = userState.clientStateMap.get(clientToken);
2444 if (clientState == null) {
2445 clientState = new ClientState(clientToken, mSessionState.userId);
2446 try {
2447 clientToken.linkToDeath(clientState, 0);
2448 } catch (RemoteException e) {
2449 Slog.e(TAG, "client process has already died", e);
2450 return false;
2451 }
2452 userState.clientStateMap.put(clientToken, clientState);
2453 }
2454 clientState.sessionTokens.add(mSessionState.sessionToken);
2455 return true;
2456 }
2457
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002458 @Override
2459 public void onChannelRetuned(Uri channelUri) {
2460 synchronized (mLock) {
2461 if (DEBUG) {
2462 Slog.d(TAG, "onChannelRetuned(" + channelUri + ")");
2463 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002464 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002465 return;
2466 }
2467 try {
2468 // TODO: Consider adding this channel change in the watch log. When we do
2469 // that, how we can protect the watch log from malicious tv inputs should
2470 // be addressed. e.g. add a field which represents where the channel change
2471 // originated from.
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002472 mSessionState.client.onChannelRetuned(channelUri, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002473 } catch (RemoteException e) {
2474 Slog.e(TAG, "error in onChannelRetuned", e);
2475 }
2476 }
2477 }
2478
2479 @Override
2480 public void onTracksChanged(List<TvTrackInfo> tracks) {
2481 synchronized (mLock) {
2482 if (DEBUG) {
2483 Slog.d(TAG, "onTracksChanged(" + tracks + ")");
2484 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002485 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002486 return;
2487 }
2488 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002489 mSessionState.client.onTracksChanged(tracks, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002490 } catch (RemoteException e) {
2491 Slog.e(TAG, "error in onTracksChanged", e);
2492 }
2493 }
2494 }
2495
2496 @Override
2497 public void onTrackSelected(int type, String trackId) {
2498 synchronized (mLock) {
2499 if (DEBUG) {
2500 Slog.d(TAG, "onTrackSelected(type=" + type + ", trackId=" + trackId + ")");
2501 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002502 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002503 return;
2504 }
2505 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002506 mSessionState.client.onTrackSelected(type, trackId, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002507 } catch (RemoteException e) {
2508 Slog.e(TAG, "error in onTrackSelected", e);
2509 }
2510 }
2511 }
2512
2513 @Override
2514 public void onVideoAvailable() {
2515 synchronized (mLock) {
2516 if (DEBUG) {
2517 Slog.d(TAG, "onVideoAvailable()");
2518 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002519 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002520 return;
2521 }
2522 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002523 mSessionState.client.onVideoAvailable(mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002524 } catch (RemoteException e) {
2525 Slog.e(TAG, "error in onVideoAvailable", e);
2526 }
2527 }
2528 }
2529
2530 @Override
2531 public void onVideoUnavailable(int reason) {
2532 synchronized (mLock) {
2533 if (DEBUG) {
2534 Slog.d(TAG, "onVideoUnavailable(" + reason + ")");
2535 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002536 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002537 return;
2538 }
2539 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002540 mSessionState.client.onVideoUnavailable(reason, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002541 } catch (RemoteException e) {
2542 Slog.e(TAG, "error in onVideoUnavailable", e);
2543 }
2544 }
2545 }
2546
2547 @Override
2548 public void onContentAllowed() {
2549 synchronized (mLock) {
2550 if (DEBUG) {
2551 Slog.d(TAG, "onContentAllowed()");
2552 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002553 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002554 return;
2555 }
2556 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002557 mSessionState.client.onContentAllowed(mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002558 } catch (RemoteException e) {
2559 Slog.e(TAG, "error in onContentAllowed", e);
2560 }
2561 }
2562 }
2563
2564 @Override
2565 public void onContentBlocked(String rating) {
2566 synchronized (mLock) {
2567 if (DEBUG) {
2568 Slog.d(TAG, "onContentBlocked()");
2569 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002570 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002571 return;
2572 }
2573 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002574 mSessionState.client.onContentBlocked(rating, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002575 } catch (RemoteException e) {
2576 Slog.e(TAG, "error in onContentBlocked", e);
2577 }
2578 }
2579 }
2580
2581 @Override
2582 public void onLayoutSurface(int left, int top, int right, int bottom) {
2583 synchronized (mLock) {
2584 if (DEBUG) {
2585 Slog.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top
2586 + ", right=" + right + ", bottom=" + bottom + ",)");
2587 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002588 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002589 return;
2590 }
2591 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002592 mSessionState.client.onLayoutSurface(left, top, right, bottom,
2593 mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002594 } catch (RemoteException e) {
2595 Slog.e(TAG, "error in onLayoutSurface", e);
2596 }
2597 }
2598 }
2599
2600 @Override
2601 public void onSessionEvent(String eventType, Bundle eventArgs) {
2602 synchronized (mLock) {
2603 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002604 Slog.d(TAG, "onEvent(eventType=" + eventType + ", eventArgs=" + eventArgs
2605 + ")");
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002606 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002607 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002608 return;
2609 }
2610 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002611 mSessionState.client.onSessionEvent(eventType, eventArgs, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002612 } catch (RemoteException e) {
2613 Slog.e(TAG, "error in onSessionEvent", e);
2614 }
2615 }
2616 }
Dongwon Kang6f0240c2015-03-31 17:56:36 -07002617
2618 @Override
2619 public void onTimeShiftStatusChanged(int status) {
2620 synchronized (mLock) {
2621 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002622 Slog.d(TAG, "onTimeShiftStatusChanged(status=" + status + ")");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07002623 }
2624 if (mSessionState.session == null || mSessionState.client == null) {
2625 return;
2626 }
2627 try {
2628 mSessionState.client.onTimeShiftStatusChanged(status, mSessionState.seq);
2629 } catch (RemoteException e) {
2630 Slog.e(TAG, "error in onTimeShiftStatusChanged", e);
2631 }
2632 }
2633 }
2634
2635 @Override
2636 public void onTimeShiftStartPositionChanged(long timeMs) {
2637 synchronized (mLock) {
2638 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002639 Slog.d(TAG, "onTimeShiftStartPositionChanged(timeMs=" + timeMs + ")");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07002640 }
2641 if (mSessionState.session == null || mSessionState.client == null) {
2642 return;
2643 }
2644 try {
2645 mSessionState.client.onTimeShiftStartPositionChanged(timeMs, mSessionState.seq);
2646 } catch (RemoteException e) {
2647 Slog.e(TAG, "error in onTimeShiftStartPositionChanged", e);
2648 }
2649 }
2650 }
2651
2652 @Override
2653 public void onTimeShiftCurrentPositionChanged(long timeMs) {
2654 synchronized (mLock) {
2655 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002656 Slog.d(TAG, "onTimeShiftCurrentPositionChanged(timeMs=" + timeMs + ")");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07002657 }
2658 if (mSessionState.session == null || mSessionState.client == null) {
2659 return;
2660 }
2661 try {
2662 mSessionState.client.onTimeShiftCurrentPositionChanged(timeMs,
2663 mSessionState.seq);
2664 } catch (RemoteException e) {
2665 Slog.e(TAG, "error in onTimeShiftCurrentPositionChanged", e);
2666 }
2667 }
2668 }
Jae Seoa826d012016-01-18 13:03:35 -08002669
2670 // For the recording session only
2671 @Override
Dongwon Kangb55c7512016-03-01 09:36:07 -08002672 public void onTuned(Uri channelUri) {
Jae Seoa826d012016-01-18 13:03:35 -08002673 synchronized (mLock) {
2674 if (DEBUG) {
Jae Seoe3c11e82016-02-08 23:18:49 -08002675 Slog.d(TAG, "onTuned()");
Jae Seoa826d012016-01-18 13:03:35 -08002676 }
2677 if (mSessionState.session == null || mSessionState.client == null) {
2678 return;
2679 }
2680 try {
Dongwon Kangb55c7512016-03-01 09:36:07 -08002681 mSessionState.client.onTuned(mSessionState.seq, channelUri);
Jae Seoa826d012016-01-18 13:03:35 -08002682 } catch (RemoteException e) {
Jae Seoe3c11e82016-02-08 23:18:49 -08002683 Slog.e(TAG, "error in onTuned", e);
Jae Seoa826d012016-01-18 13:03:35 -08002684 }
2685 }
2686 }
2687
2688 // For the recording session only
2689 @Override
2690 public void onRecordingStopped(Uri recordedProgramUri) {
2691 synchronized (mLock) {
2692 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002693 Slog.d(TAG, "onRecordingStopped(recordedProgramUri=" + recordedProgramUri
2694 + ")");
Jae Seoa826d012016-01-18 13:03:35 -08002695 }
2696 if (mSessionState.session == null || mSessionState.client == null) {
2697 return;
2698 }
2699 try {
2700 mSessionState.client.onRecordingStopped(recordedProgramUri, mSessionState.seq);
2701 } catch (RemoteException e) {
2702 Slog.e(TAG, "error in onRecordingStopped", e);
2703 }
2704 }
2705 }
2706
2707 // For the recording session only
2708 @Override
2709 public void onError(int error) {
2710 synchronized (mLock) {
2711 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002712 Slog.d(TAG, "onError(error=" + error + ")");
Jae Seoa826d012016-01-18 13:03:35 -08002713 }
2714 if (mSessionState.session == null || mSessionState.client == null) {
2715 return;
2716 }
2717 try {
2718 mSessionState.client.onError(error, mSessionState.seq);
2719 } catch (RemoteException e) {
2720 Slog.e(TAG, "error in onError", e);
2721 }
2722 }
2723 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002724 }
2725
2726 private static final class WatchLogHandler extends Handler {
Jae Seo7eb75df2014-08-08 22:20:48 -07002727 // There are only two kinds of watch events that can happen on the system:
2728 // 1. The current TV input session is tuned to a new channel.
2729 // 2. The session is released for some reason.
2730 // The former indicates the end of the previous log entry, if any, followed by the start of
2731 // a new entry. The latter indicates the end of the most recent entry for the given session.
2732 // Here the system supplies the database the smallest set of information only that is
2733 // sufficient to consolidate the log entries while minimizing database operations in the
2734 // system service.
Jae Seo8c375fe2015-06-23 20:33:25 -07002735 static final int MSG_LOG_WATCH_START = 1;
2736 static final int MSG_LOG_WATCH_END = 2;
2737 static final int MSG_SWITCH_CONTENT_RESOLVER = 3;
Jae Seo31dc634be2014-04-15 17:40:23 -07002738
Jae Seo8c375fe2015-06-23 20:33:25 -07002739 private ContentResolver mContentResolver;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002740
Jae Seo8c375fe2015-06-23 20:33:25 -07002741 WatchLogHandler(ContentResolver contentResolver, Looper looper) {
Jae Seo31dc634be2014-04-15 17:40:23 -07002742 super(looper);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002743 mContentResolver = contentResolver;
Jae Seo31dc634be2014-04-15 17:40:23 -07002744 }
2745
2746 @Override
2747 public void handleMessage(Message msg) {
2748 switch (msg.what) {
Jae Seo7eb75df2014-08-08 22:20:48 -07002749 case MSG_LOG_WATCH_START: {
Jae Seo31dc634be2014-04-15 17:40:23 -07002750 SomeArgs args = (SomeArgs) msg.obj;
Jae Seo7eb75df2014-08-08 22:20:48 -07002751 String packageName = (String) args.arg1;
2752 long watchStartTime = (long) args.arg2;
2753 long channelId = (long) args.arg3;
2754 Bundle tuneParams = (Bundle) args.arg4;
2755 IBinder sessionToken = (IBinder) args.arg5;
2756
2757 ContentValues values = new ContentValues();
2758 values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
2759 values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
2760 watchStartTime);
2761 values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
2762 if (tuneParams != null) {
2763 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS,
2764 encodeTuneParams(tuneParams));
2765 }
2766 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
2767 sessionToken.toString());
2768
2769 mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
Jae Seo31dc634be2014-04-15 17:40:23 -07002770 args.recycle();
Jae Seo8c375fe2015-06-23 20:33:25 -07002771 break;
Jae Seo31dc634be2014-04-15 17:40:23 -07002772 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002773 case MSG_LOG_WATCH_END: {
Jae Seo31dc634be2014-04-15 17:40:23 -07002774 SomeArgs args = (SomeArgs) msg.obj;
Jae Seo7eb75df2014-08-08 22:20:48 -07002775 IBinder sessionToken = (IBinder) args.arg1;
2776 long watchEndTime = (long) args.arg2;
2777
2778 ContentValues values = new ContentValues();
2779 values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
2780 watchEndTime);
2781 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
2782 sessionToken.toString());
2783
2784 mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
Jae Seo31dc634be2014-04-15 17:40:23 -07002785 args.recycle();
Jae Seo8c375fe2015-06-23 20:33:25 -07002786 break;
2787 }
2788 case MSG_SWITCH_CONTENT_RESOLVER: {
2789 mContentResolver = (ContentResolver) msg.obj;
2790 break;
Jae Seo31dc634be2014-04-15 17:40:23 -07002791 }
2792 default: {
Jae Seo8c375fe2015-06-23 20:33:25 -07002793 Slog.w(TAG, "unhandled message code: " + msg.what);
2794 break;
Jae Seo31dc634be2014-04-15 17:40:23 -07002795 }
2796 }
2797 }
2798
Jae Seo7eb75df2014-08-08 22:20:48 -07002799 private String encodeTuneParams(Bundle tuneParams) {
2800 StringBuilder builder = new StringBuilder();
2801 Set<String> keySet = tuneParams.keySet();
2802 Iterator<String> it = keySet.iterator();
2803 while (it.hasNext()) {
2804 String key = it.next();
2805 Object value = tuneParams.get(key);
2806 if (value == null) {
2807 continue;
Jae Seo579befe2014-08-06 19:18:37 -07002808 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002809 builder.append(replaceEscapeCharacters(key));
2810 builder.append("=");
2811 builder.append(replaceEscapeCharacters(value.toString()));
2812 if (it.hasNext()) {
2813 builder.append(", ");
Jae Seo31dc634be2014-04-15 17:40:23 -07002814 }
2815 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002816 return builder.toString();
Jae Seo31dc634be2014-04-15 17:40:23 -07002817 }
2818
Jae Seo7eb75df2014-08-08 22:20:48 -07002819 private String replaceEscapeCharacters(String src) {
2820 final char ESCAPE_CHARACTER = '%';
2821 final String ENCODING_TARGET_CHARACTERS = "%=,";
2822 StringBuilder builder = new StringBuilder();
2823 for (char ch : src.toCharArray()) {
2824 if (ENCODING_TARGET_CHARACTERS.indexOf(ch) >= 0) {
2825 builder.append(ESCAPE_CHARACTER);
Chulwoo Lee8d4ded02014-07-10 03:56:39 +09002826 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002827 builder.append(ch);
Jae Seo31dc634be2014-04-15 17:40:23 -07002828 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002829 return builder.toString();
Jae Seo579befe2014-08-06 19:18:37 -07002830 }
Jae Seo31dc634be2014-04-15 17:40:23 -07002831 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002832
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002833 private final class HardwareListener implements TvInputHardwareManager.Listener {
Wonsik Kim187423c2014-06-25 14:12:48 +09002834 @Override
2835 public void onStateChanged(String inputId, int state) {
Wonsik Kim969167d2014-06-24 16:33:17 +09002836 synchronized (mLock) {
2837 setStateLocked(inputId, state, mCurrentUserId);
2838 }
2839 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002840
2841 @Override
2842 public void onHardwareDeviceAdded(TvInputHardwareInfo info) {
2843 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002844 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002845 // Broadcast the event to all hardware inputs.
2846 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002847 if (!serviceState.isHardware || serviceState.service == null) continue;
Wonsik Kim187423c2014-06-25 14:12:48 +09002848 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002849 serviceState.service.notifyHardwareAdded(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09002850 } catch (RemoteException e) {
2851 Slog.e(TAG, "error in notifyHardwareAdded", e);
2852 }
2853 }
2854 }
2855 }
2856
2857 @Override
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002858 public void onHardwareDeviceRemoved(TvInputHardwareInfo info) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002859 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002860 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002861 // Broadcast the event to all hardware inputs.
2862 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002863 if (!serviceState.isHardware || serviceState.service == null) continue;
Wonsik Kim187423c2014-06-25 14:12:48 +09002864 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002865 serviceState.service.notifyHardwareRemoved(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09002866 } catch (RemoteException e) {
2867 Slog.e(TAG, "error in notifyHardwareRemoved", e);
2868 }
2869 }
2870 }
2871 }
2872
2873 @Override
Jae Seo546c6352014-08-07 11:57:01 -07002874 public void onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002875 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002876 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002877 // Broadcast the event to all hardware inputs.
2878 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002879 if (!serviceState.isHardware || serviceState.service == null) continue;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002880 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002881 serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002882 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002883 Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002884 }
2885 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002886 }
2887 }
2888
2889 @Override
Jae Seo546c6352014-08-07 11:57:01 -07002890 public void onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002891 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002892 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002893 // Broadcast the event to all hardware inputs.
2894 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002895 if (!serviceState.isHardware || serviceState.service == null) continue;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002896 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002897 serviceState.service.notifyHdmiDeviceRemoved(deviceInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002898 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002899 Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002900 }
2901 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002902 }
2903 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09002904
2905 @Override
Wonsik Kime92f8572014-08-12 18:30:24 +09002906 public void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo deviceInfo) {
2907 synchronized (mLock) {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002908 Integer state;
Wonsik Kime92f8572014-08-12 18:30:24 +09002909 switch (deviceInfo.getDevicePowerStatus()) {
2910 case HdmiControlManager.POWER_STATUS_ON:
2911 state = INPUT_STATE_CONNECTED;
2912 break;
2913 case HdmiControlManager.POWER_STATUS_STANDBY:
2914 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON:
2915 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY:
2916 state = INPUT_STATE_CONNECTED_STANDBY;
2917 break;
2918 case HdmiControlManager.POWER_STATUS_UNKNOWN:
2919 default:
2920 state = null;
2921 break;
2922 }
2923 if (state != null) {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002924 setStateLocked(inputId, state, mCurrentUserId);
Wonsik Kime92f8572014-08-12 18:30:24 +09002925 }
2926 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09002927 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002928 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09002929
2930 private static class SessionNotFoundException extends IllegalArgumentException {
Dongwon Kangfdce9e52014-12-04 18:08:00 +09002931 public SessionNotFoundException(String name) {
2932 super(name);
2933 }
2934 }
Jae Seo39570912014-02-20 18:23:25 -08002935}