blob: 057b53e6232f26d59167b431f4348676f0e2a01b [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
Kyeongkab.Nam8d6b71a2019-01-16 10:44:37 +0900131 private IBinder.DeathRecipient mDeathRecipient;
132
Jae Seo39570912014-02-20 18:23:25 -0800133 public TvInputManagerService(Context context) {
134 super(context);
Jae Seo31dc634be2014-04-15 17:40:23 -0700135
Jae Seo39570912014-02-20 18:23:25 -0800136 mContext = context;
Jae Seo8c375fe2015-06-23 20:33:25 -0700137 mWatchLogHandler = new WatchLogHandler(mContext.getContentResolver(),
138 IoThread.get().getLooper());
Wonsik Kim187423c2014-06-25 14:12:48 +0900139 mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
Jae Seo31dc634be2014-04-15 17:40:23 -0700140
Jae Seo39570912014-02-20 18:23:25 -0800141 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700142 getOrCreateUserStateLocked(mCurrentUserId);
Jae Seo39570912014-02-20 18:23:25 -0800143 }
144 }
145
146 @Override
147 public void onStart() {
148 publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());
149 }
150
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900151 @Override
152 public void onBootPhase(int phase) {
153 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
154 registerBroadcastReceivers();
Wonsik Kim187423c2014-06-25 14:12:48 +0900155 } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900156 synchronized (mLock) {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900157 buildTvInputListLocked(mCurrentUserId, null);
Jae Seo9c165d62014-08-25 14:39:26 -0700158 buildTvContentRatingSystemListLocked(mCurrentUserId);
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900159 }
160 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900161 mTvInputHardwareManager.onBootPhase(phase);
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900162 }
163
Youngsang Cho19c47d72016-05-05 09:21:24 -0700164 @Override
165 public void onUnlockUser(int userHandle) {
166 if (DEBUG) Slog.d(TAG, "onUnlockUser(userHandle=" + userHandle + ")");
167 synchronized (mLock) {
168 if (mCurrentUserId != userHandle) {
169 return;
170 }
171 buildTvInputListLocked(mCurrentUserId, null);
172 buildTvContentRatingSystemListLocked(mCurrentUserId);
173 }
174 }
175
Jae Seo39570912014-02-20 18:23:25 -0800176 private void registerBroadcastReceivers() {
177 PackageMonitor monitor = new PackageMonitor() {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900178 private void buildTvInputList(String[] packages) {
179 synchronized (mLock) {
Dongwon Kanga897e5f2016-04-19 13:39:47 -0700180 if (mCurrentUserId == getChangingUserId()) {
181 buildTvInputListLocked(mCurrentUserId, packages);
182 buildTvContentRatingSystemListLocked(mCurrentUserId);
183 }
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900184 }
185 }
186
187 @Override
188 public void onPackageUpdateFinished(String packageName, int uid) {
189 if (DEBUG) Slog.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")");
190 // This callback is invoked when the TV input is reinstalled.
191 // In this case, isReplacing() always returns true.
192 buildTvInputList(new String[] { packageName });
193 }
194
195 @Override
196 public void onPackagesAvailable(String[] packages) {
197 if (DEBUG) {
198 Slog.d(TAG, "onPackagesAvailable(packages=" + Arrays.toString(packages) + ")");
199 }
200 // This callback is invoked when the media on which some packages exist become
201 // available.
202 if (isReplacing()) {
203 buildTvInputList(packages);
204 }
205 }
206
207 @Override
208 public void onPackagesUnavailable(String[] packages) {
209 // This callback is invoked when the media on which some packages exist become
210 // unavailable.
211 if (DEBUG) {
212 Slog.d(TAG, "onPackagesUnavailable(packages=" + Arrays.toString(packages)
213 + ")");
214 }
215 if (isReplacing()) {
216 buildTvInputList(packages);
217 }
218 }
219
Jae Seo39570912014-02-20 18:23:25 -0800220 @Override
221 public void onSomePackagesChanged() {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900222 // TODO: Use finer-grained methods(e.g. onPackageAdded, onPackageRemoved) to manage
223 // the TV inputs.
Dongwon Kang426c9a42014-08-26 17:39:21 +0900224 if (DEBUG) Slog.d(TAG, "onSomePackagesChanged()");
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900225 if (isReplacing()) {
226 if (DEBUG) Slog.d(TAG, "Skipped building TV input list due to replacing");
227 // When the package is updated, buildTvInputListLocked is called in other
228 // methods instead.
229 return;
Jae Seo39570912014-02-20 18:23:25 -0800230 }
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900231 buildTvInputList(null);
Jae Seo39570912014-02-20 18:23:25 -0800232 }
Jae Seo5c80ad22014-06-12 19:52:58 -0700233
234 @Override
Dongwon Kang31a8f842015-04-08 18:26:23 -0700235 public boolean onPackageChanged(String packageName, int uid, String[] components) {
236 // The input list needs to be updated in any cases, regardless of whether
237 // it happened to the whole package or a specific component. Returning true so that
238 // the update can be handled in {@link #onSomePackagesChanged}.
239 return true;
240 }
Jae Seo39570912014-02-20 18:23:25 -0800241 };
242 monitor.register(mContext, null, UserHandle.ALL, true);
243
244 IntentFilter intentFilter = new IntentFilter();
245 intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
246 intentFilter.addAction(Intent.ACTION_USER_REMOVED);
247 mContext.registerReceiverAsUser(new BroadcastReceiver() {
248 @Override
249 public void onReceive(Context context, Intent intent) {
250 String action = intent.getAction();
251 if (Intent.ACTION_USER_SWITCHED.equals(action)) {
252 switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
253 } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
254 removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
255 }
256 }
257 }, UserHandle.ALL, intentFilter, null, null);
258 }
259
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900260 private static boolean hasHardwarePermission(PackageManager pm, ComponentName component) {
Wonsik Kim187423c2014-06-25 14:12:48 +0900261 return pm.checkPermission(android.Manifest.permission.TV_INPUT_HARDWARE,
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900262 component.getPackageName()) == PackageManager.PERMISSION_GRANTED;
Wonsik Kim187423c2014-06-25 14:12:48 +0900263 }
264
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900265 private void buildTvInputListLocked(int userId, String[] updatedPackages) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700266 UserState userState = getOrCreateUserStateLocked(userId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900267 userState.packageSet.clear();
Jae Seo39570912014-02-20 18:23:25 -0800268
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900269 if (DEBUG) Slog.d(TAG, "buildTvInputList");
Jae Seo39570912014-02-20 18:23:25 -0800270 PackageManager pm = mContext.getPackageManager();
Jae Seo76976fa2015-05-20 21:51:16 -0700271 List<ResolveInfo> services = pm.queryIntentServicesAsUser(
Chulwoo Leee7bb7d62014-05-27 14:10:37 +0900272 new Intent(TvInputService.SERVICE_INTERFACE),
Jae Seo76976fa2015-05-20 21:51:16 -0700273 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
274 userId);
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700275 List<TvInputInfo> inputList = new ArrayList<>();
Jae Seo39570912014-02-20 18:23:25 -0800276 for (ResolveInfo ri : services) {
277 ServiceInfo si = ri.serviceInfo;
278 if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900279 Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
Jae Seo39570912014-02-20 18:23:25 -0800280 + android.Manifest.permission.BIND_TV_INPUT);
281 continue;
282 }
Jae Seo9cc28e52014-08-12 16:45:58 -0700283
284 ComponentName component = new ComponentName(si.packageName, si.name);
285 if (hasHardwarePermission(pm, component)) {
286 ServiceState serviceState = userState.serviceStateMap.get(component);
287 if (serviceState == null) {
Jae Seoc980f43d2016-02-09 23:46:58 -0800288 // New hardware input found. Create a new ServiceState and connect to the
289 // service to populate the hardware list.
Jae Seo9cc28e52014-08-12 16:45:58 -0700290 serviceState = new ServiceState(component, userId);
291 userState.serviceStateMap.put(component, serviceState);
Wonsik Kimf271eac2014-08-30 12:55:10 +0900292 updateServiceConnectionLocked(component, userId);
Wonsik Kim187423c2014-06-25 14:12:48 +0900293 } else {
Shubang71d5c762017-02-23 15:46:40 -0800294 inputList.addAll(serviceState.hardwareInputMap.values());
Jae Seo9cc28e52014-08-12 16:45:58 -0700295 }
296 } else {
297 try {
Jae Seoc03671f2016-01-26 15:15:22 -0800298 TvInputInfo info = new TvInputInfo.Builder(mContext, ri).build();
299 inputList.add(info);
Jae Seo18c0cfb2016-05-16 18:19:11 -0700300 } catch (Exception e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700301 Slog.e(TAG, "failed to load TV input " + si.name, e);
Jae Seo9cc28e52014-08-12 16:45:58 -0700302 continue;
Wonsik Kim969167d2014-06-24 16:33:17 +0900303 }
Chulwoo Leee7bb7d62014-05-27 14:10:37 +0900304 }
Jae Seo9cc28e52014-08-12 16:45:58 -0700305 userState.packageSet.add(si.packageName);
306 }
307
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700308 Map<String, TvInputState> inputMap = new HashMap<>();
Jae Seo9cc28e52014-08-12 16:45:58 -0700309 for (TvInputInfo info : inputList) {
Jae Seofea8dd42014-08-26 13:57:41 -0700310 if (DEBUG) {
311 Slog.d(TAG, "add " + info.getId());
312 }
Jae Seoabda4202016-01-28 19:13:04 -0800313 TvInputState inputState = userState.inputMap.get(info.getId());
314 if (inputState == null) {
315 inputState = new TvInputState();
Jae Seo9cc28e52014-08-12 16:45:58 -0700316 }
Shubang2263fb02016-05-13 16:51:53 -0700317 inputState.info = info;
Jae Seoabda4202016-01-28 19:13:04 -0800318 inputMap.put(info.getId(), inputState);
Jae Seo39570912014-02-20 18:23:25 -0800319 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900320
321 for (String inputId : inputMap.keySet()) {
322 if (!userState.inputMap.containsKey(inputId)) {
323 notifyInputAddedLocked(userState, inputId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900324 } else if (updatedPackages != null) {
325 // Notify the package updates
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +0900326 ComponentName component = inputMap.get(inputId).info.getComponent();
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900327 for (String updatedPackage : updatedPackages) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +0900328 if (component.getPackageName().equals(updatedPackage)) {
329 updateServiceConnectionLocked(component, userId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900330 notifyInputUpdatedLocked(userState, inputId);
331 break;
332 }
333 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900334 }
335 }
336
337 for (String inputId : userState.inputMap.keySet()) {
338 if (!inputMap.containsKey(inputId)) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900339 TvInputInfo info = userState.inputMap.get(inputId).info;
Dongwon Kang426c9a42014-08-26 17:39:21 +0900340 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
341 if (serviceState != null) {
342 abortPendingCreateSessionRequestsLocked(serviceState, inputId, userId);
343 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900344 notifyInputRemovedLocked(userState, inputId);
345 }
346 }
347
348 userState.inputMap.clear();
349 userState.inputMap = inputMap;
Jae Seo9c165d62014-08-25 14:39:26 -0700350 }
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900351
Jae Seo9c165d62014-08-25 14:39:26 -0700352 private void buildTvContentRatingSystemListLocked(int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700353 UserState userState = getOrCreateUserStateLocked(userId);
Jae Seo9c165d62014-08-25 14:39:26 -0700354 userState.contentRatingSystemList.clear();
355
356 final PackageManager pm = mContext.getPackageManager();
357 Intent intent = new Intent(TvInputManager.ACTION_QUERY_CONTENT_RATING_SYSTEMS);
358 for (ResolveInfo resolveInfo :
359 pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA)) {
360 ActivityInfo receiver = resolveInfo.activityInfo;
361 Bundle metaData = receiver.metaData;
362 if (metaData == null) {
363 continue;
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900364 }
Jae Seo9c165d62014-08-25 14:39:26 -0700365
366 int xmlResId = metaData.getInt(TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS);
367 if (xmlResId == 0) {
368 Slog.w(TAG, "Missing meta-data '"
369 + TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS + "' on receiver "
370 + receiver.packageName + "/" + receiver.name);
371 continue;
372 }
373 userState.contentRatingSystemList.add(
374 TvContentRatingSystemInfo.createTvContentRatingSystemInfo(xmlResId,
375 receiver.applicationInfo));
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900376 }
Jae Seo39570912014-02-20 18:23:25 -0800377 }
378
379 private void switchUser(int userId) {
380 synchronized (mLock) {
381 if (mCurrentUserId == userId) {
382 return;
383 }
shubang8049f202016-04-25 11:21:42 -0700384 UserState userState = mUserStates.get(mCurrentUserId);
385 List<SessionState> sessionStatesToRelease = new ArrayList<>();
386 for (SessionState sessionState : userState.sessionStateMap.values()) {
387 if (sessionState.session != null && !sessionState.isRecordingSession) {
388 sessionStatesToRelease.add(sessionState);
389 }
390 }
391 for (SessionState sessionState : sessionStatesToRelease) {
392 try {
393 sessionState.session.release();
394 } catch (RemoteException e) {
395 Slog.e(TAG, "error in release", e);
396 }
397 clearSessionAndNotifyClientLocked(sessionState);
398 }
399
400 for (Iterator<ComponentName> it = userState.serviceStateMap.keySet().iterator();
401 it.hasNext(); ) {
402 ComponentName component = it.next();
403 ServiceState serviceState = userState.serviceStateMap.get(component);
404 if (serviceState != null && serviceState.sessionTokens.isEmpty()) {
405 if (serviceState.callback != null) {
406 try {
407 serviceState.service.unregisterCallback(serviceState.callback);
408 } catch (RemoteException e) {
409 Slog.e(TAG, "error in unregisterCallback", e);
410 }
411 }
412 mContext.unbindService(serviceState.connection);
413 it.remove();
414 }
415 }
Jae Seo39570912014-02-20 18:23:25 -0800416
Jae Seo8c375fe2015-06-23 20:33:25 -0700417 mCurrentUserId = userId;
Jae Seo4f1a6d42015-07-20 16:15:01 -0700418 getOrCreateUserStateLocked(userId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900419 buildTvInputListLocked(userId, null);
Jae Seo9c165d62014-08-25 14:39:26 -0700420 buildTvContentRatingSystemListLocked(userId);
Jae Seo8c375fe2015-06-23 20:33:25 -0700421 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_SWITCH_CONTENT_RESOLVER,
422 getContentResolverForUser(userId)).sendToTarget();
Jae Seo39570912014-02-20 18:23:25 -0800423 }
424 }
425
shubang8049f202016-04-25 11:21:42 -0700426 private void clearSessionAndNotifyClientLocked(SessionState state) {
427 if (state.client != null) {
428 try {
429 state.client.onSessionReleased(state.seq);
430 } catch(RemoteException e) {
431 Slog.e(TAG, "error in onSessionReleased", e);
432 }
433 }
434 // If there are any other sessions based on this session, they should be released.
435 UserState userState = getOrCreateUserStateLocked(state.userId);
436 for (SessionState sessionState : userState.sessionStateMap.values()) {
437 if (state.sessionToken == sessionState.hardwareSessionToken) {
438 releaseSessionLocked(sessionState.sessionToken, Process.SYSTEM_UID, state.userId);
439 try {
440 sessionState.client.onSessionReleased(sessionState.seq);
441 } catch (RemoteException e) {
442 Slog.e(TAG, "error in onSessionReleased", e);
443 }
444 }
445 }
446 removeSessionStateLocked(state.sessionToken, state.userId);
447 }
448
Jae Seo39570912014-02-20 18:23:25 -0800449 private void removeUser(int userId) {
450 synchronized (mLock) {
Jae Seob06cb882014-04-09 12:08:17 -0700451 UserState userState = mUserStates.get(userId);
452 if (userState == null) {
453 return;
454 }
shubang8049f202016-04-25 11:21:42 -0700455 // Release all created sessions.
456 for (SessionState state : userState.sessionStateMap.values()) {
457 if (state.session != null) {
458 try {
459 state.session.release();
460 } catch (RemoteException e) {
461 Slog.e(TAG, "error in release", e);
462 }
463 }
464 }
465 userState.sessionStateMap.clear();
466
467 // Unregister all callbacks and unbind all services.
468 for (ServiceState serviceState : userState.serviceStateMap.values()) {
469 if (serviceState.service != null) {
470 if (serviceState.callback != null) {
471 try {
472 serviceState.service.unregisterCallback(serviceState.callback);
473 } catch (RemoteException e) {
474 Slog.e(TAG, "error in unregisterCallback", e);
475 }
476 }
477 mContext.unbindService(serviceState.connection);
478 }
479 }
480 userState.serviceStateMap.clear();
Jae Seo39570912014-02-20 18:23:25 -0800481
Jae Seofea8dd42014-08-26 13:57:41 -0700482 // Clear everything else.
483 userState.inputMap.clear();
484 userState.packageSet.clear();
Jae Seo9c165d62014-08-25 14:39:26 -0700485 userState.contentRatingSystemList.clear();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900486 userState.clientStateMap.clear();
Jae Seofea8dd42014-08-26 13:57:41 -0700487 userState.callbackSet.clear();
488 userState.mainSessionToken = null;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900489
Jae Seo39570912014-02-20 18:23:25 -0800490 mUserStates.remove(userId);
491 }
492 }
493
Jae Seo8c375fe2015-06-23 20:33:25 -0700494 private ContentResolver getContentResolverForUser(int userId) {
495 UserHandle user = new UserHandle(userId);
496 Context context;
497 try {
498 context = mContext.createPackageContextAsUser("android", 0, user);
499 } catch (NameNotFoundException e) {
Jae Seo2a2b2992016-01-12 23:13:14 -0800500 Slog.e(TAG, "failed to create package context as user " + user);
Jae Seo8c375fe2015-06-23 20:33:25 -0700501 context = mContext;
502 }
503 return context.getContentResolver();
504 }
505
Jae Seo4f1a6d42015-07-20 16:15:01 -0700506 private UserState getOrCreateUserStateLocked(int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800507 UserState userState = mUserStates.get(userId);
508 if (userState == null) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700509 userState = new UserState(mContext, userId);
510 mUserStates.put(userId, userState);
Jae Seo39570912014-02-20 18:23:25 -0800511 }
512 return userState;
513 }
514
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900515 private ServiceState getServiceStateLocked(ComponentName component, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700516 UserState userState = getOrCreateUserStateLocked(userId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900517 ServiceState serviceState = userState.serviceStateMap.get(component);
Jae Seo39570912014-02-20 18:23:25 -0800518 if (serviceState == null) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900519 throw new IllegalStateException("Service state not found for " + component + " (userId="
Sungsoo Lim7de5e232014-04-12 16:51:27 +0900520 + userId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800521 }
522 return serviceState;
523 }
524
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900525 private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700526 UserState userState = getOrCreateUserStateLocked(userId);
Jae Seo39570912014-02-20 18:23:25 -0800527 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
528 if (sessionState == null) {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900529 throw new SessionNotFoundException("Session state not found for token " + sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800530 }
531 // Only the application that requested this session or the system can access it.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900532 if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) {
Jae Seo39570912014-02-20 18:23:25 -0800533 throw new SecurityException("Illegal access to the session with token " + sessionToken
534 + " from uid " + callingUid);
535 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900536 return sessionState;
537 }
538
539 private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900540 return getSessionLocked(getSessionStateLocked(sessionToken, callingUid, userId));
541 }
542
543 private ITvInputSession getSessionLocked(SessionState sessionState) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900544 ITvInputSession session = sessionState.session;
Jae Seo39570912014-02-20 18:23:25 -0800545 if (session == null) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900546 throw new IllegalStateException("Session not yet created for token "
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900547 + sessionState.sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800548 }
549 return session;
550 }
551
552 private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
553 String methodName) {
554 return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
555 false, methodName, null);
556 }
557
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900558 private void updateServiceConnectionLocked(ComponentName component, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700559 UserState userState = getOrCreateUserStateLocked(userId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900560 ServiceState serviceState = userState.serviceStateMap.get(component);
Jae Seo39570912014-02-20 18:23:25 -0800561 if (serviceState == null) {
562 return;
563 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900564 if (serviceState.reconnecting) {
565 if (!serviceState.sessionTokens.isEmpty()) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900566 // wait until all the sessions are removed.
567 return;
568 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900569 serviceState.reconnecting = false;
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900570 }
shubang8049f202016-04-25 11:21:42 -0700571
572 boolean shouldBind;
573 if (userId == mCurrentUserId) {
574 shouldBind = !serviceState.sessionTokens.isEmpty() || serviceState.isHardware;
575 } else {
576 // For a non-current user,
577 // if sessionTokens is not empty, it contains recording sessions only
578 // because other sessions must have been removed while switching user
579 // and non-recording sessions are not created by createSession().
580 shouldBind = !serviceState.sessionTokens.isEmpty();
581 }
582
583 if (serviceState.service == null && shouldBind) {
Jae Seo39570912014-02-20 18:23:25 -0800584 // This means that the service is not yet connected but its state indicates that we
585 // have pending requests. Then, connect the service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900586 if (serviceState.bound) {
Jae Seo39570912014-02-20 18:23:25 -0800587 // We have already bound to the service so we don't try to bind again until after we
588 // unbind later on.
589 return;
590 }
591 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900592 Slog.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800593 }
Sungsoo Limd6672b52014-04-30 10:43:26 +0900594
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900595 Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900596 serviceState.bound = mContext.bindServiceAsUser(
Dianne Hackbornd69e4c12015-04-24 09:54:54 -0700597 i, serviceState.connection,
598 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
599 new UserHandle(userId));
shubang8049f202016-04-25 11:21:42 -0700600 } else if (serviceState.service != null && !shouldBind) {
Jae Seo39570912014-02-20 18:23:25 -0800601 // This means that the service is already connected but its state indicates that we have
602 // nothing to do with it. Then, disconnect the service.
603 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900604 Slog.d(TAG, "unbindService(service=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -0800605 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900606 mContext.unbindService(serviceState.connection);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900607 userState.serviceStateMap.remove(component);
Jae Seo39570912014-02-20 18:23:25 -0800608 }
609 }
610
Dongwon Kang426c9a42014-08-26 17:39:21 +0900611 private void abortPendingCreateSessionRequestsLocked(ServiceState serviceState,
612 String inputId, int userId) {
613 // Let clients know the create session requests are failed.
Jae Seo4f1a6d42015-07-20 16:15:01 -0700614 UserState userState = getOrCreateUserStateLocked(userId);
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900615 List<SessionState> sessionsToAbort = new ArrayList<>();
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900616 for (IBinder sessionToken : serviceState.sessionTokens) {
Dongwon Kang426c9a42014-08-26 17:39:21 +0900617 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900618 if (sessionState.session == null && (inputId == null
Jae Seo2cdb05e2016-02-04 22:17:13 +0900619 || sessionState.inputId.equals(inputId))) {
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900620 sessionsToAbort.add(sessionState);
Dongwon Kang426c9a42014-08-26 17:39:21 +0900621 }
622 }
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900623 for (SessionState sessionState : sessionsToAbort) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900624 removeSessionStateLocked(sessionState.sessionToken, sessionState.userId);
625 sendSessionTokenToClientLocked(sessionState.client,
Jae Seo2cdb05e2016-02-04 22:17:13 +0900626 sessionState.inputId, null, null, sessionState.seq);
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900627 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900628 updateServiceConnectionLocked(serviceState.component, userId);
Dongwon Kang426c9a42014-08-26 17:39:21 +0900629 }
630
shubangaf2fb662018-11-09 17:03:46 -0800631 private boolean createSessionInternalLocked(ITvInputService service, IBinder sessionToken,
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900632 int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700633 UserState userState = getOrCreateUserStateLocked(userId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900634 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800635 if (DEBUG) {
Jae Seo2cdb05e2016-02-04 22:17:13 +0900636 Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.inputId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800637 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900638 InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
Jae Seo6a6059a2014-04-17 21:35:29 -0700639
Jae Seo39570912014-02-20 18:23:25 -0800640 // Set up a callback to send the session token.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900641 ITvInputSessionCallback callback = new SessionCallback(sessionState, channels);
Jae Seo39570912014-02-20 18:23:25 -0800642
shubangaf2fb662018-11-09 17:03:46 -0800643 boolean created = true;
Jae Seo39570912014-02-20 18:23:25 -0800644 // Create a session. When failed, send a null token immediately.
645 try {
Jae Seoa826d012016-01-18 13:03:35 -0800646 if (sessionState.isRecordingSession) {
Jae Seo2cdb05e2016-02-04 22:17:13 +0900647 service.createRecordingSession(callback, sessionState.inputId);
Jae Seoa826d012016-01-18 13:03:35 -0800648 } else {
Jae Seo2cdb05e2016-02-04 22:17:13 +0900649 service.createSession(channels[1], callback, sessionState.inputId);
Jae Seoa826d012016-01-18 13:03:35 -0800650 }
Jae Seo39570912014-02-20 18:23:25 -0800651 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900652 Slog.e(TAG, "error in createSession", e);
Jae Seo2cdb05e2016-02-04 22:17:13 +0900653 sendSessionTokenToClientLocked(sessionState.client, sessionState.inputId, null,
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900654 null, sessionState.seq);
shubangaf2fb662018-11-09 17:03:46 -0800655 created = false;
Jae Seo39570912014-02-20 18:23:25 -0800656 }
Jae Seo6a6059a2014-04-17 21:35:29 -0700657 channels[1].dispose();
shubangaf2fb662018-11-09 17:03:46 -0800658 return created;
Jae Seo39570912014-02-20 18:23:25 -0800659 }
660
Sungsoo Limd6672b52014-04-30 10:43:26 +0900661 private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId,
Jae Seo5c80ad22014-06-12 19:52:58 -0700662 IBinder sessionToken, InputChannel channel, int seq) {
Jae Seo39570912014-02-20 18:23:25 -0800663 try {
Sungsoo Limd6672b52014-04-30 10:43:26 +0900664 client.onSessionCreated(inputId, sessionToken, channel, seq);
Jae Seofea8dd42014-08-26 13:57:41 -0700665 } catch (RemoteException e) {
666 Slog.e(TAG, "error in onSessionCreated", e);
Jae Seo39570912014-02-20 18:23:25 -0800667 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900668 }
Jae Seo39570912014-02-20 18:23:25 -0800669
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900670 private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900671 SessionState sessionState = null;
672 try {
673 sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
674 if (sessionState.session != null) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700675 UserState userState = getOrCreateUserStateLocked(userId);
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900676 if (sessionToken == userState.mainSessionToken) {
677 setMainLocked(sessionToken, false, callingUid, userId);
678 }
Kyeongkab.Nam8d6b71a2019-01-16 10:44:37 +0900679 sessionState.session.asBinder().unlinkToDeath(sessionState, 0);
Jae Seoe3c11e82016-02-08 23:18:49 -0800680 sessionState.session.release();
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900681 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900682 } catch (RemoteException | SessionNotFoundException e) {
683 Slog.e(TAG, "error in releaseSession", e);
684 } finally {
685 if (sessionState != null) {
686 sessionState.session = null;
687 }
Jae Seo39570912014-02-20 18:23:25 -0800688 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900689 removeSessionStateLocked(sessionToken, userId);
Jae Seo39570912014-02-20 18:23:25 -0800690 }
691
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900692 private void removeSessionStateLocked(IBinder sessionToken, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700693 UserState userState = getOrCreateUserStateLocked(userId);
Ji-Hwan Leeabca0ee2014-07-24 17:34:19 +0900694 if (sessionToken == userState.mainSessionToken) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900695 if (DEBUG) {
696 Slog.d(TAG, "mainSessionToken=null");
697 }
Ji-Hwan Leeabca0ee2014-07-24 17:34:19 +0900698 userState.mainSessionToken = null;
699 }
700
701 // Remove the session state from the global session state map of the current user.
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900702 SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
703
Chulwoo Lee8d4ded02014-07-10 03:56:39 +0900704 if (sessionState == null) {
705 return;
706 }
707
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900708 // Also remove the session token from the session token list of the current client and
709 // service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900710 ClientState clientState = userState.clientStateMap.get(sessionState.client.asBinder());
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900711 if (clientState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900712 clientState.sessionTokens.remove(sessionToken);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900713 if (clientState.isEmpty()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900714 userState.clientStateMap.remove(sessionState.client.asBinder());
Kyeongkab.Nam8d6b71a2019-01-16 10:44:37 +0900715 sessionState.client.asBinder().unlinkToDeath(clientState, 0);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900716 }
717 }
718
Jae Seo2cdb05e2016-02-04 22:17:13 +0900719 ServiceState serviceState = userState.serviceStateMap.get(sessionState.componentName);
720 if (serviceState != null) {
721 serviceState.sessionTokens.remove(sessionToken);
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900722 }
Jae Seo2cdb05e2016-02-04 22:17:13 +0900723 updateServiceConnectionLocked(sessionState.componentName, userId);
Jae Seo7eb75df2014-08-08 22:20:48 -0700724
725 // Log the end of watch.
726 SomeArgs args = SomeArgs.obtain();
727 args.arg1 = sessionToken;
728 args.arg2 = System.currentTimeMillis();
729 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_END, args).sendToTarget();
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900730 }
731
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900732 private void setMainLocked(IBinder sessionToken, boolean isMain, int callingUid, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900733 try {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900734 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
735 if (sessionState.hardwareSessionToken != null) {
736 sessionState = getSessionStateLocked(sessionState.hardwareSessionToken,
737 Process.SYSTEM_UID, userId);
738 }
Jae Seo2cdb05e2016-02-04 22:17:13 +0900739 ServiceState serviceState = getServiceStateLocked(sessionState.componentName, userId);
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900740 if (!serviceState.isHardware) {
741 return;
742 }
743 ITvInputSession session = getSessionLocked(sessionState);
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900744 session.setMain(isMain);
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900745 } catch (RemoteException | SessionNotFoundException e) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900746 Slog.e(TAG, "error in setMain", e);
747 }
748 }
749
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900750 private void notifyInputAddedLocked(UserState userState, String inputId) {
751 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700752 Slog.d(TAG, "notifyInputAddedLocked(inputId=" + inputId + ")");
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900753 }
754 for (ITvInputManagerCallback callback : userState.callbackSet) {
755 try {
756 callback.onInputAdded(inputId);
757 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700758 Slog.e(TAG, "failed to report added input to callback", e);
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900759 }
760 }
761 }
762
763 private void notifyInputRemovedLocked(UserState userState, String inputId) {
764 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700765 Slog.d(TAG, "notifyInputRemovedLocked(inputId=" + inputId + ")");
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900766 }
767 for (ITvInputManagerCallback callback : userState.callbackSet) {
768 try {
769 callback.onInputRemoved(inputId);
770 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700771 Slog.e(TAG, "failed to report removed input to callback", e);
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900772 }
773 }
774 }
775
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900776 private void notifyInputUpdatedLocked(UserState userState, String inputId) {
777 if (DEBUG) {
778 Slog.d(TAG, "notifyInputUpdatedLocked(inputId=" + inputId + ")");
779 }
780 for (ITvInputManagerCallback callback : userState.callbackSet) {
781 try {
782 callback.onInputUpdated(inputId);
783 } catch (RemoteException e) {
784 Slog.e(TAG, "failed to report updated input to callback", e);
785 }
786 }
787 }
788
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900789 private void notifyInputStateChangedLocked(UserState userState, String inputId,
Wonsik Kim969167d2014-06-24 16:33:17 +0900790 int state, ITvInputManagerCallback targetCallback) {
791 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700792 Slog.d(TAG, "notifyInputStateChangedLocked(inputId=" + inputId
793 + ", state=" + state + ")");
Wonsik Kim969167d2014-06-24 16:33:17 +0900794 }
795 if (targetCallback == null) {
796 for (ITvInputManagerCallback callback : userState.callbackSet) {
797 try {
798 callback.onInputStateChanged(inputId, state);
799 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700800 Slog.e(TAG, "failed to report state change to callback", e);
Wonsik Kim969167d2014-06-24 16:33:17 +0900801 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900802 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900803 } else {
804 try {
805 targetCallback.onInputStateChanged(inputId, state);
806 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700807 Slog.e(TAG, "failed to report state change to callback", e);
Wonsik Kim969167d2014-06-24 16:33:17 +0900808 }
809 }
810 }
811
Jae Seoaa5605f2016-02-13 01:38:08 -0800812 private void updateTvInputInfoLocked(UserState userState, TvInputInfo inputInfo) {
Jae Seoa826d012016-01-18 13:03:35 -0800813 if (DEBUG) {
Jae Seoaa5605f2016-02-13 01:38:08 -0800814 Slog.d(TAG, "updateTvInputInfoLocked(inputInfo=" + inputInfo + ")");
Jae Seoa826d012016-01-18 13:03:35 -0800815 }
Jae Seoabda4202016-01-28 19:13:04 -0800816 String inputId = inputInfo.getId();
817 TvInputState inputState = userState.inputMap.get(inputId);
818 if (inputState == null) {
819 Slog.e(TAG, "failed to set input info - unknown input id " + inputId);
820 return;
821 }
Jae Seoabda4202016-01-28 19:13:04 -0800822 inputState.info = inputInfo;
823
Jae Seoa826d012016-01-18 13:03:35 -0800824 for (ITvInputManagerCallback callback : userState.callbackSet) {
825 try {
Jae Seoaa5605f2016-02-13 01:38:08 -0800826 callback.onTvInputInfoUpdated(inputInfo);
Jae Seoa826d012016-01-18 13:03:35 -0800827 } catch (RemoteException e) {
Jae Seoaa5605f2016-02-13 01:38:08 -0800828 Slog.e(TAG, "failed to report updated input info to callback", e);
Jae Seoa826d012016-01-18 13:03:35 -0800829 }
830 }
831 }
832
Wonsik Kim969167d2014-06-24 16:33:17 +0900833 private void setStateLocked(String inputId, int state, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700834 UserState userState = getOrCreateUserStateLocked(userId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900835 TvInputState inputState = userState.inputMap.get(inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900836 ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent());
837 int oldState = inputState.state;
838 inputState.state = state;
839 if (serviceState != null && serviceState.service == null
shubang8049f202016-04-25 11:21:42 -0700840 && (!serviceState.sessionTokens.isEmpty() || serviceState.isHardware)) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900841 // We don't notify state change while reconnecting. It should remain disconnected.
842 return;
843 }
844 if (oldState != state) {
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900845 notifyInputStateChangedLocked(userState, inputId, state, null);
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900846 }
847 }
848
Jae Seo39570912014-02-20 18:23:25 -0800849 private final class BinderService extends ITvInputManager.Stub {
850 @Override
851 public List<TvInputInfo> getTvInputList(int userId) {
852 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
853 Binder.getCallingUid(), userId, "getTvInputList");
854 final long identity = Binder.clearCallingIdentity();
855 try {
856 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700857 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700858 List<TvInputInfo> inputList = new ArrayList<>();
Wonsik Kim969167d2014-06-24 16:33:17 +0900859 for (TvInputState state : userState.inputMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900860 inputList.add(state.info);
Jae Seo39570912014-02-20 18:23:25 -0800861 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900862 return inputList;
Jae Seo39570912014-02-20 18:23:25 -0800863 }
864 } finally {
865 Binder.restoreCallingIdentity(identity);
866 }
Jae Seo39570912014-02-20 18:23:25 -0800867 }
868
869 @Override
Jae Seob3758052014-07-12 19:25:24 -0700870 public TvInputInfo getTvInputInfo(String inputId, int userId) {
871 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
872 Binder.getCallingUid(), userId, "getTvInputInfo");
873 final long identity = Binder.clearCallingIdentity();
874 try {
875 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700876 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seob3758052014-07-12 19:25:24 -0700877 TvInputState state = userState.inputMap.get(inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900878 return state == null ? null : state.info;
Jae Seob3758052014-07-12 19:25:24 -0700879 }
880 } finally {
881 Binder.restoreCallingIdentity(identity);
882 }
883 }
884
Jae Seoaa5605f2016-02-13 01:38:08 -0800885 public void updateTvInputInfo(TvInputInfo inputInfo, int userId) {
Jae Seoc2a89512016-01-28 10:38:11 -0800886 String inputInfoPackageName = inputInfo.getServiceInfo().packageName;
887 String callingPackageName = getCallingPackageName();
Dongwon Kang9d0e0f12016-10-11 15:14:01 -0700888 if (!TextUtils.equals(inputInfoPackageName, callingPackageName)
889 && mContext.checkCallingPermission(
890 android.Manifest.permission.WRITE_SECURE_SETTINGS)
891 != PackageManager.PERMISSION_GRANTED) {
892 // Only the app owning the input and system settings are allowed to update info.
Jae Seoc2a89512016-01-28 10:38:11 -0800893 throw new IllegalArgumentException("calling package " + callingPackageName
894 + " is not allowed to change TvInputInfo for " + inputInfoPackageName);
895 }
896
897 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
Jae Seoaa5605f2016-02-13 01:38:08 -0800898 Binder.getCallingUid(), userId, "updateTvInputInfo");
Jae Seoc2a89512016-01-28 10:38:11 -0800899 final long identity = Binder.clearCallingIdentity();
900 try {
901 synchronized (mLock) {
902 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seoaa5605f2016-02-13 01:38:08 -0800903 updateTvInputInfoLocked(userState, inputInfo);
Jae Seoc2a89512016-01-28 10:38:11 -0800904 }
905 } finally {
906 Binder.restoreCallingIdentity(identity);
907 }
908 }
909
910 private String getCallingPackageName() {
911 final String[] packages = mContext.getPackageManager().getPackagesForUid(
912 Binder.getCallingUid());
913 if (packages != null && packages.length > 0) {
914 return packages[0];
915 }
916 return "unknown";
917 }
918
Jae Seob3758052014-07-12 19:25:24 -0700919 @Override
Dongwon Kang993f81e2014-11-27 19:34:18 +0900920 public int getTvInputState(String inputId, int userId) {
921 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
922 Binder.getCallingUid(), userId, "getTvInputState");
923 final long identity = Binder.clearCallingIdentity();
924 try {
925 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700926 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Dongwon Kang993f81e2014-11-27 19:34:18 +0900927 TvInputState state = userState.inputMap.get(inputId);
Jae Seo82fce642015-04-20 15:37:50 -0700928 return state == null ? INPUT_STATE_CONNECTED : state.state;
Dongwon Kang993f81e2014-11-27 19:34:18 +0900929 }
930 } finally {
931 Binder.restoreCallingIdentity(identity);
932 }
933 }
934
935 @Override
Jae Seo9c165d62014-08-25 14:39:26 -0700936 public List<TvContentRatingSystemInfo> getTvContentRatingSystemList(int userId) {
Shubangdd3ec0b2017-06-23 13:54:10 -0700937 if (mContext.checkCallingPermission(
938 android.Manifest.permission.READ_CONTENT_RATING_SYSTEMS)
939 != PackageManager.PERMISSION_GRANTED) {
940 throw new SecurityException(
941 "The caller does not have permission to read content rating systems");
942 }
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900943 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
Jae Seo9c165d62014-08-25 14:39:26 -0700944 Binder.getCallingUid(), userId, "getTvContentRatingSystemList");
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900945 final long identity = Binder.clearCallingIdentity();
946 try {
947 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700948 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo9c165d62014-08-25 14:39:26 -0700949 return userState.contentRatingSystemList;
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900950 }
951 } finally {
952 Binder.restoreCallingIdentity(identity);
953 }
954 }
955
956 @Override
Conrad Chen558acf92017-04-05 17:31:01 -0700957 public void sendTvInputNotifyIntent(Intent intent, int userId) {
958 if (mContext.checkCallingPermission(android.Manifest.permission.NOTIFY_TV_INPUTS)
959 != PackageManager.PERMISSION_GRANTED) {
960 throw new SecurityException("The caller: " + getCallingPackageName()
961 + " doesn't have permission: "
962 + android.Manifest.permission.NOTIFY_TV_INPUTS);
963 }
964 if (TextUtils.isEmpty(intent.getPackage())) {
965 throw new IllegalArgumentException("Must specify package name to notify.");
966 }
967 switch (intent.getAction()) {
968 case TvContract.ACTION_PREVIEW_PROGRAM_BROWSABLE_DISABLED:
969 if (intent.getLongExtra(TvContract.EXTRA_PREVIEW_PROGRAM_ID, -1) < 0) {
970 throw new IllegalArgumentException("Invalid preview program ID.");
971 }
972 break;
973 case TvContract.ACTION_WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED:
974 if (intent.getLongExtra(TvContract.EXTRA_WATCH_NEXT_PROGRAM_ID, -1) < 0) {
975 throw new IllegalArgumentException("Invalid watch next program ID.");
976 }
977 break;
978 case TvContract.ACTION_PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT:
979 if (intent.getLongExtra(TvContract.EXTRA_PREVIEW_PROGRAM_ID, -1) < 0) {
980 throw new IllegalArgumentException("Invalid preview program ID.");
981 }
982 if (intent.getLongExtra(TvContract.EXTRA_WATCH_NEXT_PROGRAM_ID, -1) < 0) {
983 throw new IllegalArgumentException("Invalid watch next program ID.");
984 }
985 break;
986 default:
987 throw new IllegalArgumentException("Invalid TV input notifying action: "
988 + intent.getAction());
989 }
990 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
991 Binder.getCallingUid(), userId, "sendTvInputNotifyIntent");
992 final long identity = Binder.clearCallingIdentity();
993 try {
994 getContext().sendBroadcastAsUser(intent, new UserHandle(resolvedUserId));
995 } finally {
996 Binder.restoreCallingIdentity(identity);
997 }
998 }
999
1000 @Override
Wonsik Kim969167d2014-06-24 16:33:17 +09001001 public void registerCallback(final ITvInputManagerCallback callback, int userId) {
Jae Seo39570912014-02-20 18:23:25 -08001002 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1003 Binder.getCallingUid(), userId, "registerCallback");
1004 final long identity = Binder.clearCallingIdentity();
1005 try {
1006 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001007 final UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Wonsik Kim969167d2014-06-24 16:33:17 +09001008 userState.callbackSet.add(callback);
Kyeongkab.Nam8d6b71a2019-01-16 10:44:37 +09001009 mDeathRecipient = new IBinder.DeathRecipient() {
1010 @Override
1011 public void binderDied() {
1012 synchronized (mLock) {
1013 if (userState.callbackSet != null) {
1014 userState.callbackSet.remove(callback);
Jae Seofea8dd42014-08-26 13:57:41 -07001015 }
1016 }
Kyeongkab.Nam8d6b71a2019-01-16 10:44:37 +09001017 }
1018 };
1019
1020 try {
1021 callback.asBinder().linkToDeath(mDeathRecipient, 0);
Jae Seofea8dd42014-08-26 13:57:41 -07001022 } catch (RemoteException e) {
1023 Slog.e(TAG, "client process has already died", e);
1024 }
Jae Seo39570912014-02-20 18:23:25 -08001025 }
1026 } finally {
1027 Binder.restoreCallingIdentity(identity);
1028 }
1029 }
1030
1031 @Override
Wonsik Kim969167d2014-06-24 16:33:17 +09001032 public void unregisterCallback(ITvInputManagerCallback callback, int userId) {
Jae Seo39570912014-02-20 18:23:25 -08001033 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1034 Binder.getCallingUid(), userId, "unregisterCallback");
1035 final long identity = Binder.clearCallingIdentity();
1036 try {
1037 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001038 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Wonsik Kim969167d2014-06-24 16:33:17 +09001039 userState.callbackSet.remove(callback);
Kyeongkab.Nam8d6b71a2019-01-16 10:44:37 +09001040 callback.asBinder().unlinkToDeath(mDeathRecipient, 0);
Jae Seo39570912014-02-20 18:23:25 -08001041 }
1042 } finally {
1043 Binder.restoreCallingIdentity(identity);
1044 }
1045 }
1046
1047 @Override
Jae Seo783645e2014-07-28 17:30:50 +09001048 public boolean isParentalControlsEnabled(int userId) {
1049 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1050 Binder.getCallingUid(), userId, "isParentalControlsEnabled");
1051 final long identity = Binder.clearCallingIdentity();
1052 try {
1053 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001054 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +09001055 return userState.persistentDataStore.isParentalControlsEnabled();
1056 }
1057 } finally {
1058 Binder.restoreCallingIdentity(identity);
1059 }
1060 }
1061
1062 @Override
1063 public void setParentalControlsEnabled(boolean enabled, int userId) {
1064 ensureParentalControlsPermission();
1065 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1066 Binder.getCallingUid(), userId, "setParentalControlsEnabled");
1067 final long identity = Binder.clearCallingIdentity();
1068 try {
1069 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001070 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +09001071 userState.persistentDataStore.setParentalControlsEnabled(enabled);
1072 }
1073 } finally {
1074 Binder.restoreCallingIdentity(identity);
1075 }
1076 }
1077
1078 @Override
1079 public boolean isRatingBlocked(String rating, int userId) {
1080 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1081 Binder.getCallingUid(), userId, "isRatingBlocked");
1082 final long identity = Binder.clearCallingIdentity();
1083 try {
1084 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001085 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +09001086 return userState.persistentDataStore.isRatingBlocked(
1087 TvContentRating.unflattenFromString(rating));
1088 }
1089 } finally {
1090 Binder.restoreCallingIdentity(identity);
1091 }
1092 }
1093
1094 @Override
1095 public List<String> getBlockedRatings(int userId) {
1096 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1097 Binder.getCallingUid(), userId, "getBlockedRatings");
1098 final long identity = Binder.clearCallingIdentity();
1099 try {
1100 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001101 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001102 List<String> ratings = new ArrayList<>();
Jae Seo783645e2014-07-28 17:30:50 +09001103 for (TvContentRating rating
1104 : userState.persistentDataStore.getBlockedRatings()) {
1105 ratings.add(rating.flattenToString());
1106 }
1107 return ratings;
1108 }
1109 } finally {
1110 Binder.restoreCallingIdentity(identity);
1111 }
1112 }
1113
1114 @Override
1115 public void addBlockedRating(String rating, int userId) {
1116 ensureParentalControlsPermission();
1117 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1118 Binder.getCallingUid(), userId, "addBlockedRating");
1119 final long identity = Binder.clearCallingIdentity();
1120 try {
1121 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001122 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +09001123 userState.persistentDataStore.addBlockedRating(
1124 TvContentRating.unflattenFromString(rating));
1125 }
1126 } finally {
1127 Binder.restoreCallingIdentity(identity);
1128 }
1129 }
1130
1131 @Override
1132 public void removeBlockedRating(String rating, int userId) {
1133 ensureParentalControlsPermission();
1134 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1135 Binder.getCallingUid(), userId, "removeBlockedRating");
1136 final long identity = Binder.clearCallingIdentity();
1137 try {
1138 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001139 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +09001140 userState.persistentDataStore.removeBlockedRating(
1141 TvContentRating.unflattenFromString(rating));
1142 }
1143 } finally {
1144 Binder.restoreCallingIdentity(identity);
1145 }
1146 }
1147
1148 private void ensureParentalControlsPermission() {
Jae Seofc836f62014-08-27 00:47:56 +00001149 if (mContext.checkCallingPermission(
1150 android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
1151 != PackageManager.PERMISSION_GRANTED) {
1152 throw new SecurityException(
1153 "The caller does not have parental controls permission");
1154 }
Jae Seo783645e2014-07-28 17:30:50 +09001155 }
1156
1157 @Override
Sungsoo Limd6672b52014-04-30 10:43:26 +09001158 public void createSession(final ITvInputClient client, final String inputId,
Jae Seoa826d012016-01-18 13:03:35 -08001159 boolean isRecordingSession, int seq, int userId) {
Jae Seo39570912014-02-20 18:23:25 -08001160 final int callingUid = Binder.getCallingUid();
1161 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1162 userId, "createSession");
1163 final long identity = Binder.clearCallingIdentity();
1164 try {
1165 synchronized (mLock) {
shubang8049f202016-04-25 11:21:42 -07001166 if (userId != mCurrentUserId && !isRecordingSession) {
1167 // A non-recording session of a backgroud (non-current) user
1168 // should not be created.
1169 // Let the client get onConnectionFailed callback for this case.
1170 sendSessionTokenToClientLocked(client, inputId, null, null, seq);
1171 return;
1172 }
Jae Seo4f1a6d42015-07-20 16:15:01 -07001173 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Dongwon Kang426c9a42014-08-26 17:39:21 +09001174 TvInputState inputState = userState.inputMap.get(inputId);
1175 if (inputState == null) {
1176 Slog.w(TAG, "Failed to find input state for inputId=" + inputId);
1177 sendSessionTokenToClientLocked(client, inputId, null, null, seq);
1178 return;
1179 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001180 TvInputInfo info = inputState.info;
Wonsik Kim187423c2014-06-25 14:12:48 +09001181 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
Jae Seo39570912014-02-20 18:23:25 -08001182 if (serviceState == null) {
Wonsik Kim187423c2014-06-25 14:12:48 +09001183 serviceState = new ServiceState(info.getComponent(), resolvedUserId);
1184 userState.serviceStateMap.put(info.getComponent(), serviceState);
Jae Seo39570912014-02-20 18:23:25 -08001185 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001186 // Send a null token immediately while reconnecting.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001187 if (serviceState.reconnecting) {
Jae Seo5c80ad22014-06-12 19:52:58 -07001188 sendSessionTokenToClientLocked(client, inputId, null, null, seq);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001189 return;
1190 }
1191
1192 // Create a new session token and a session state.
1193 IBinder sessionToken = new Binder();
Jae Seo2cdb05e2016-02-04 22:17:13 +09001194 SessionState sessionState = new SessionState(sessionToken, info.getId(),
1195 info.getComponent(), isRecordingSession, client, seq, callingUid,
1196 resolvedUserId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001197
1198 // Add them to the global session state map of the current user.
1199 userState.sessionStateMap.put(sessionToken, sessionState);
1200
1201 // Also, add them to the session state map of the current service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001202 serviceState.sessionTokens.add(sessionToken);
Jae Seo39570912014-02-20 18:23:25 -08001203
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001204 if (serviceState.service != null) {
shubangaf2fb662018-11-09 17:03:46 -08001205 if (!createSessionInternalLocked(serviceState.service, sessionToken,
1206 resolvedUserId)) {
1207 removeSessionStateLocked(sessionToken, resolvedUserId);
1208 }
Jae Seo39570912014-02-20 18:23:25 -08001209 } else {
Wonsik Kim187423c2014-06-25 14:12:48 +09001210 updateServiceConnectionLocked(info.getComponent(), resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001211 }
1212 }
1213 } finally {
1214 Binder.restoreCallingIdentity(identity);
1215 }
1216 }
1217
1218 @Override
1219 public void releaseSession(IBinder sessionToken, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001220 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -07001221 Slog.d(TAG, "releaseSession(sessionToken=" + sessionToken + ")");
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001222 }
Jae Seo39570912014-02-20 18:23:25 -08001223 final int callingUid = Binder.getCallingUid();
1224 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1225 userId, "releaseSession");
1226 final long identity = Binder.clearCallingIdentity();
1227 try {
1228 synchronized (mLock) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001229 releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001230 }
1231 } finally {
1232 Binder.restoreCallingIdentity(identity);
1233 }
1234 }
1235
1236 @Override
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001237 public void setMainSession(IBinder sessionToken, int userId) {
Shubange41b76f2017-06-07 13:23:12 -07001238 if (mContext.checkCallingPermission(
1239 android.Manifest.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE)
1240 != PackageManager.PERMISSION_GRANTED) {
1241 throw new SecurityException(
1242 "The caller does not have CHANGE_HDMI_CEC_ACTIVE_SOURCE permission");
1243 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001244 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -07001245 Slog.d(TAG, "setMainSession(sessionToken=" + sessionToken + ")");
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001246 }
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001247 final int callingUid = Binder.getCallingUid();
1248 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1249 userId, "setMainSession");
1250 final long identity = Binder.clearCallingIdentity();
1251 try {
1252 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001253 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001254 if (userState.mainSessionToken == sessionToken) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001255 return;
1256 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001257 if (DEBUG) {
1258 Slog.d(TAG, "mainSessionToken=" + sessionToken);
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001259 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001260 IBinder oldMainSessionToken = userState.mainSessionToken;
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001261 userState.mainSessionToken = sessionToken;
1262
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001263 // Inform the new main session first.
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001264 // See {@link TvInputService.Session#onSetMain}.
1265 if (sessionToken != null) {
1266 setMainLocked(sessionToken, true, callingUid, userId);
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001267 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001268 if (oldMainSessionToken != null) {
1269 setMainLocked(oldMainSessionToken, false, Process.SYSTEM_UID, userId);
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001270 }
1271 }
1272 } finally {
1273 Binder.restoreCallingIdentity(identity);
1274 }
1275 }
1276
1277 @Override
Jae Seo39570912014-02-20 18:23:25 -08001278 public void setSurface(IBinder sessionToken, Surface surface, int userId) {
1279 final int callingUid = Binder.getCallingUid();
1280 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1281 userId, "setSurface");
1282 final long identity = Binder.clearCallingIdentity();
1283 try {
1284 synchronized (mLock) {
1285 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001286 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1287 resolvedUserId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001288 if (sessionState.hardwareSessionToken == null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001289 getSessionLocked(sessionState).setSurface(surface);
1290 } else {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001291 getSessionLocked(sessionState.hardwareSessionToken,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001292 Process.SYSTEM_UID, resolvedUserId).setSurface(surface);
1293 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001294 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001295 Slog.e(TAG, "error in setSurface", e);
Jae Seo39570912014-02-20 18:23:25 -08001296 }
1297 }
1298 } finally {
Youngsang Chof8362062014-04-30 17:24:20 +09001299 if (surface != null) {
1300 // surface is not used in TvInputManagerService.
1301 surface.release();
1302 }
Jae Seo39570912014-02-20 18:23:25 -08001303 Binder.restoreCallingIdentity(identity);
1304 }
1305 }
1306
1307 @Override
Youngsang Choe821d712014-07-16 14:22:19 -07001308 public void dispatchSurfaceChanged(IBinder sessionToken, int format, int width,
1309 int height, int userId) {
1310 final int callingUid = Binder.getCallingUid();
1311 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1312 userId, "dispatchSurfaceChanged");
1313 final long identity = Binder.clearCallingIdentity();
1314 try {
1315 synchronized (mLock) {
1316 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001317 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1318 resolvedUserId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001319 getSessionLocked(sessionState).dispatchSurfaceChanged(format, width,
1320 height);
1321 if (sessionState.hardwareSessionToken != null) {
1322 getSessionLocked(sessionState.hardwareSessionToken, Process.SYSTEM_UID,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001323 resolvedUserId).dispatchSurfaceChanged(format, width, height);
1324 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001325 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Choe821d712014-07-16 14:22:19 -07001326 Slog.e(TAG, "error in dispatchSurfaceChanged", e);
1327 }
1328 }
1329 } finally {
1330 Binder.restoreCallingIdentity(identity);
1331 }
1332 }
1333
1334 @Override
Jae Seo39570912014-02-20 18:23:25 -08001335 public void setVolume(IBinder sessionToken, float volume, int userId) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001336 final float REMOTE_VOLUME_ON = 1.0f;
1337 final float REMOTE_VOLUME_OFF = 0f;
Jae Seo39570912014-02-20 18:23:25 -08001338 final int callingUid = Binder.getCallingUid();
1339 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1340 userId, "setVolume");
1341 final long identity = Binder.clearCallingIdentity();
1342 try {
1343 synchronized (mLock) {
1344 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001345 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1346 resolvedUserId);
1347 getSessionLocked(sessionState).setVolume(volume);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001348 if (sessionState.hardwareSessionToken != null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001349 // Here, we let the hardware session know only whether volume is on or
1350 // off to prevent that the volume is controlled in the both side.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001351 getSessionLocked(sessionState.hardwareSessionToken,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001352 Process.SYSTEM_UID, resolvedUserId).setVolume((volume > 0.0f)
1353 ? REMOTE_VOLUME_ON : REMOTE_VOLUME_OFF);
1354 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001355 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001356 Slog.e(TAG, "error in setVolume", e);
Jae Seo39570912014-02-20 18:23:25 -08001357 }
1358 }
1359 } finally {
1360 Binder.restoreCallingIdentity(identity);
1361 }
1362 }
1363
1364 @Override
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +09001365 public void tune(IBinder sessionToken, final Uri channelUri, Bundle params, int userId) {
Jae Seo39570912014-02-20 18:23:25 -08001366 final int callingUid = Binder.getCallingUid();
1367 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1368 userId, "tune");
1369 final long identity = Binder.clearCallingIdentity();
1370 try {
1371 synchronized (mLock) {
Jae Seo39570912014-02-20 18:23:25 -08001372 try {
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +09001373 getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(
1374 channelUri, params);
Jae Seoc22d0c02014-08-15 13:03:21 -07001375 if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
Youngsang Cho008f6d42014-07-22 21:29:47 -07001376 // Do not log the watch history for passthrough inputs.
1377 return;
1378 }
Jae Seo31dc634be2014-04-15 17:40:23 -07001379
Jae Seo4f1a6d42015-07-20 16:15:01 -07001380 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo31dc634be2014-04-15 17:40:23 -07001381 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Jae Seoe3c11e82016-02-08 23:18:49 -08001382 if (sessionState.isRecordingSession) {
1383 return;
1384 }
Jae Seo31dc634be2014-04-15 17:40:23 -07001385
Jae Seo7eb75df2014-08-08 22:20:48 -07001386 // Log the start of watch.
Jae Seo31dc634be2014-04-15 17:40:23 -07001387 SomeArgs args = SomeArgs.obtain();
Jae Seo2cdb05e2016-02-04 22:17:13 +09001388 args.arg1 = sessionState.componentName.getPackageName();
Jae Seo7eb75df2014-08-08 22:20:48 -07001389 args.arg2 = System.currentTimeMillis();
1390 args.arg3 = ContentUris.parseId(channelUri);
1391 args.arg4 = params;
1392 args.arg5 = sessionToken;
1393 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_START, args)
1394 .sendToTarget();
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001395 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001396 Slog.e(TAG, "error in tune", e);
Jae Seo39570912014-02-20 18:23:25 -08001397 }
1398 }
1399 } finally {
1400 Binder.restoreCallingIdentity(identity);
1401 }
1402 }
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001403
1404 @Override
Jae Seoa9033832015-03-11 19:29:46 -07001405 public void unblockContent(
Sungsoo Lim9bf671f2014-07-19 12:59:51 +09001406 IBinder sessionToken, String unblockedRating, int userId) {
Dongwon Kange12d8102016-03-04 16:45:39 -08001407 ensureParentalControlsPermission();
Jaewan Kim903d6b72014-07-16 11:28:56 +09001408 final int callingUid = Binder.getCallingUid();
1409 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1410 userId, "unblockContent");
1411 final long identity = Binder.clearCallingIdentity();
1412 try {
1413 synchronized (mLock) {
1414 try {
1415 getSessionLocked(sessionToken, callingUid, resolvedUserId)
Jae Seoa9033832015-03-11 19:29:46 -07001416 .unblockContent(unblockedRating);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001417 } catch (RemoteException | SessionNotFoundException e) {
Jae Seoa9033832015-03-11 19:29:46 -07001418 Slog.e(TAG, "error in unblockContent", e);
Jaewan Kim903d6b72014-07-16 11:28:56 +09001419 }
1420 }
1421 } finally {
1422 Binder.restoreCallingIdentity(identity);
1423 }
1424 }
1425
1426 @Override
Jae Seo2c1c31c2014-07-10 14:57:01 -07001427 public void setCaptionEnabled(IBinder sessionToken, boolean enabled, int userId) {
1428 final int callingUid = Binder.getCallingUid();
1429 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1430 userId, "setCaptionEnabled");
1431 final long identity = Binder.clearCallingIdentity();
1432 try {
1433 synchronized (mLock) {
1434 try {
1435 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1436 .setCaptionEnabled(enabled);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001437 } catch (RemoteException | SessionNotFoundException e) {
Jae Seo2c1c31c2014-07-10 14:57:01 -07001438 Slog.e(TAG, "error in setCaptionEnabled", e);
1439 }
1440 }
1441 } finally {
1442 Binder.restoreCallingIdentity(identity);
1443 }
1444 }
1445
1446 @Override
Jae Seo10d285a2014-07-31 22:46:47 +09001447 public void selectTrack(IBinder sessionToken, int type, String trackId, int userId) {
Dongwon Kang1f213912014-07-02 18:35:08 +09001448 final int callingUid = Binder.getCallingUid();
1449 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1450 userId, "selectTrack");
1451 final long identity = Binder.clearCallingIdentity();
1452 try {
1453 synchronized (mLock) {
1454 try {
1455 getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack(
Jae Seo10d285a2014-07-31 22:46:47 +09001456 type, trackId);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001457 } catch (RemoteException | SessionNotFoundException e) {
Dongwon Kang1f213912014-07-02 18:35:08 +09001458 Slog.e(TAG, "error in selectTrack", e);
1459 }
1460 }
1461 } finally {
1462 Binder.restoreCallingIdentity(identity);
1463 }
1464 }
1465
1466 @Override
Jae Seoa759b112014-07-18 22:16:08 -07001467 public void sendAppPrivateCommand(IBinder sessionToken, String command, Bundle data,
1468 int userId) {
1469 final int callingUid = Binder.getCallingUid();
1470 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1471 userId, "sendAppPrivateCommand");
1472 final long identity = Binder.clearCallingIdentity();
1473 try {
1474 synchronized (mLock) {
1475 try {
1476 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1477 .appPrivateCommand(command, data);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001478 } catch (RemoteException | SessionNotFoundException e) {
Jae Seofea8dd42014-08-26 13:57:41 -07001479 Slog.e(TAG, "error in appPrivateCommand", e);
Jae Seoa759b112014-07-18 22:16:08 -07001480 }
1481 }
1482 } finally {
1483 Binder.restoreCallingIdentity(identity);
1484 }
1485 }
1486
1487 @Override
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001488 public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
1489 int userId) {
1490 final int callingUid = Binder.getCallingUid();
1491 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1492 userId, "createOverlayView");
1493 final long identity = Binder.clearCallingIdentity();
1494 try {
1495 synchronized (mLock) {
1496 try {
1497 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1498 .createOverlayView(windowToken, frame);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001499 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001500 Slog.e(TAG, "error in createOverlayView", e);
1501 }
1502 }
1503 } finally {
1504 Binder.restoreCallingIdentity(identity);
1505 }
1506 }
1507
1508 @Override
1509 public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
1510 final int callingUid = Binder.getCallingUid();
1511 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1512 userId, "relayoutOverlayView");
1513 final long identity = Binder.clearCallingIdentity();
1514 try {
1515 synchronized (mLock) {
1516 try {
1517 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1518 .relayoutOverlayView(frame);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001519 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001520 Slog.e(TAG, "error in relayoutOverlayView", e);
1521 }
1522 }
1523 } finally {
1524 Binder.restoreCallingIdentity(identity);
1525 }
1526 }
1527
1528 @Override
1529 public void removeOverlayView(IBinder sessionToken, int userId) {
1530 final int callingUid = Binder.getCallingUid();
1531 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1532 userId, "removeOverlayView");
1533 final long identity = Binder.clearCallingIdentity();
1534 try {
1535 synchronized (mLock) {
1536 try {
1537 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1538 .removeOverlayView();
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001539 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001540 Slog.e(TAG, "error in removeOverlayView", e);
1541 }
1542 }
1543 } finally {
1544 Binder.restoreCallingIdentity(identity);
1545 }
1546 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001547
1548 @Override
Jae Seoa826d012016-01-18 13:03:35 -08001549 public void timeShiftPlay(IBinder sessionToken, final Uri recordedProgramUri, int userId) {
1550 final int callingUid = Binder.getCallingUid();
1551 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1552 userId, "timeShiftPlay");
1553 final long identity = Binder.clearCallingIdentity();
1554 try {
1555 synchronized (mLock) {
1556 try {
1557 getSessionLocked(sessionToken, callingUid, resolvedUserId).timeShiftPlay(
1558 recordedProgramUri);
1559 } catch (RemoteException | SessionNotFoundException e) {
1560 Slog.e(TAG, "error in timeShiftPlay", e);
1561 }
1562 }
1563 } finally {
1564 Binder.restoreCallingIdentity(identity);
1565 }
1566 }
1567
1568 @Override
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001569 public void timeShiftPause(IBinder sessionToken, int userId) {
1570 final int callingUid = Binder.getCallingUid();
1571 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1572 userId, "timeShiftPause");
1573 final long identity = Binder.clearCallingIdentity();
1574 try {
1575 synchronized (mLock) {
1576 try {
Jae Seoa826d012016-01-18 13:03:35 -08001577 getSessionLocked(sessionToken, callingUid, resolvedUserId).timeShiftPause();
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001578 } catch (RemoteException | SessionNotFoundException e) {
1579 Slog.e(TAG, "error in timeShiftPause", e);
1580 }
1581 }
1582 } finally {
1583 Binder.restoreCallingIdentity(identity);
1584 }
1585 }
1586
1587 @Override
1588 public void timeShiftResume(IBinder sessionToken, int userId) {
1589 final int callingUid = Binder.getCallingUid();
1590 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1591 userId, "timeShiftResume");
1592 final long identity = Binder.clearCallingIdentity();
1593 try {
1594 synchronized (mLock) {
1595 try {
1596 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1597 .timeShiftResume();
1598 } catch (RemoteException | SessionNotFoundException e) {
1599 Slog.e(TAG, "error in timeShiftResume", e);
1600 }
1601 }
1602 } finally {
1603 Binder.restoreCallingIdentity(identity);
1604 }
1605 }
1606
1607 @Override
1608 public void timeShiftSeekTo(IBinder sessionToken, long timeMs, int userId) {
1609 final int callingUid = Binder.getCallingUid();
1610 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1611 userId, "timeShiftSeekTo");
1612 final long identity = Binder.clearCallingIdentity();
1613 try {
1614 synchronized (mLock) {
1615 try {
1616 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1617 .timeShiftSeekTo(timeMs);
1618 } catch (RemoteException | SessionNotFoundException e) {
1619 Slog.e(TAG, "error in timeShiftSeekTo", e);
1620 }
1621 }
1622 } finally {
1623 Binder.restoreCallingIdentity(identity);
1624 }
1625 }
1626
1627 @Override
Jae Seo4b34cc72015-05-15 17:29:39 -07001628 public void timeShiftSetPlaybackParams(IBinder sessionToken, PlaybackParams params,
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001629 int userId) {
1630 final int callingUid = Binder.getCallingUid();
1631 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
Jae Seo4b34cc72015-05-15 17:29:39 -07001632 userId, "timeShiftSetPlaybackParams");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001633 final long identity = Binder.clearCallingIdentity();
1634 try {
1635 synchronized (mLock) {
1636 try {
1637 getSessionLocked(sessionToken, callingUid, resolvedUserId)
Jae Seo4b34cc72015-05-15 17:29:39 -07001638 .timeShiftSetPlaybackParams(params);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001639 } catch (RemoteException | SessionNotFoundException e) {
Jae Seo4b34cc72015-05-15 17:29:39 -07001640 Slog.e(TAG, "error in timeShiftSetPlaybackParams", e);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001641 }
1642 }
1643 } finally {
1644 Binder.restoreCallingIdentity(identity);
1645 }
1646 }
1647
1648 @Override
Jae Seo465f0d62015-04-06 18:40:46 -07001649 public void timeShiftEnablePositionTracking(IBinder sessionToken, boolean enable,
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001650 int userId) {
1651 final int callingUid = Binder.getCallingUid();
1652 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
Jae Seo465f0d62015-04-06 18:40:46 -07001653 userId, "timeShiftEnablePositionTracking");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001654 final long identity = Binder.clearCallingIdentity();
1655 try {
1656 synchronized (mLock) {
1657 try {
1658 getSessionLocked(sessionToken, callingUid, resolvedUserId)
Jae Seo465f0d62015-04-06 18:40:46 -07001659 .timeShiftEnablePositionTracking(enable);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001660 } catch (RemoteException | SessionNotFoundException e) {
Jae Seo465f0d62015-04-06 18:40:46 -07001661 Slog.e(TAG, "error in timeShiftEnablePositionTracking", e);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001662 }
1663 }
1664 } finally {
1665 Binder.restoreCallingIdentity(identity);
1666 }
1667 }
1668
1669 @Override
Dongwon Kang0cb52442016-04-22 11:00:03 -07001670 public void startRecording(IBinder sessionToken, @Nullable Uri programUri, int userId) {
Jae Seoa826d012016-01-18 13:03:35 -08001671 final int callingUid = Binder.getCallingUid();
1672 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1673 userId, "startRecording");
1674 final long identity = Binder.clearCallingIdentity();
1675 try {
1676 synchronized (mLock) {
1677 try {
Jae Seo4eee6a72016-02-06 11:11:35 +09001678 getSessionLocked(sessionToken, callingUid, resolvedUserId).startRecording(
Dongwon Kang0cb52442016-04-22 11:00:03 -07001679 programUri);
Jae Seoa826d012016-01-18 13:03:35 -08001680 } catch (RemoteException | SessionNotFoundException e) {
1681 Slog.e(TAG, "error in startRecording", e);
1682 }
1683 }
1684 } finally {
1685 Binder.restoreCallingIdentity(identity);
1686 }
1687 }
1688
1689 @Override
1690 public void stopRecording(IBinder sessionToken, int userId) {
1691 final int callingUid = Binder.getCallingUid();
1692 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1693 userId, "stopRecording");
1694 final long identity = Binder.clearCallingIdentity();
1695 try {
1696 synchronized (mLock) {
1697 try {
1698 getSessionLocked(sessionToken, callingUid, resolvedUserId).stopRecording();
1699 } catch (RemoteException | SessionNotFoundException e) {
1700 Slog.e(TAG, "error in stopRecording", e);
1701 }
1702 }
1703 } finally {
1704 Binder.restoreCallingIdentity(identity);
1705 }
1706 }
1707
1708 @Override
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001709 public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
Wonsik Kim969167d2014-06-24 16:33:17 +09001710 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001711 != PackageManager.PERMISSION_GRANTED) {
1712 return null;
1713 }
1714
1715 final long identity = Binder.clearCallingIdentity();
1716 try {
1717 return mTvInputHardwareManager.getHardwareList();
1718 } finally {
1719 Binder.restoreCallingIdentity(identity);
1720 }
1721 }
1722
1723 @Override
1724 public ITvInputHardware acquireTvInputHardware(int deviceId,
Wonsik Kim969167d2014-06-24 16:33:17 +09001725 ITvInputHardwareCallback callback, TvInputInfo info, int userId)
1726 throws RemoteException {
1727 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001728 != PackageManager.PERMISSION_GRANTED) {
1729 return null;
1730 }
1731
1732 final long identity = Binder.clearCallingIdentity();
1733 final int callingUid = Binder.getCallingUid();
1734 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1735 userId, "acquireTvInputHardware");
1736 try {
1737 return mTvInputHardwareManager.acquireHardware(
Wonsik Kim969167d2014-06-24 16:33:17 +09001738 deviceId, callback, info, callingUid, resolvedUserId);
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001739 } finally {
1740 Binder.restoreCallingIdentity(identity);
1741 }
1742 }
1743
1744 @Override
1745 public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
1746 throws RemoteException {
Wonsik Kim969167d2014-06-24 16:33:17 +09001747 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001748 != PackageManager.PERMISSION_GRANTED) {
1749 return;
1750 }
1751
1752 final long identity = Binder.clearCallingIdentity();
1753 final int callingUid = Binder.getCallingUid();
1754 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1755 userId, "releaseTvInputHardware");
1756 try {
1757 mTvInputHardwareManager.releaseHardware(
1758 deviceId, hardware, callingUid, resolvedUserId);
1759 } finally {
1760 Binder.restoreCallingIdentity(identity);
1761 }
1762 }
Jaewan Kime14c3f42014-06-27 13:47:48 +09001763
1764 @Override
Jaesung Chung58739e72015-04-24 19:39:59 +09001765 public List<DvbDeviceInfo> getDvbDeviceList() throws RemoteException {
1766 if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)
1767 != PackageManager.PERMISSION_GRANTED) {
1768 throw new SecurityException("Requires DVB_DEVICE permission");
1769 }
1770
1771 final long identity = Binder.clearCallingIdentity();
1772 try {
Jiabinef1659f2016-11-18 10:50:15 +09001773 // Pattern1: /dev/dvb%d.frontend%d
1774 ArrayList<DvbDeviceInfo> deviceInfosFromPattern1 = new ArrayList<>();
Jaesung Chung58739e72015-04-24 19:39:59 +09001775 File devDirectory = new File("/dev");
Jiabinef1659f2016-11-18 10:50:15 +09001776 boolean dvbDirectoryFound = false;
Jaesung Chung58739e72015-04-24 19:39:59 +09001777 for (String fileName : devDirectory.list()) {
1778 Matcher matcher = sFrontEndDevicePattern.matcher(fileName);
1779 if (matcher.find()) {
1780 int adapterId = Integer.parseInt(matcher.group(1));
1781 int deviceId = Integer.parseInt(matcher.group(2));
Jiabinef1659f2016-11-18 10:50:15 +09001782 deviceInfosFromPattern1.add(new DvbDeviceInfo(adapterId, deviceId));
1783 }
1784 if (TextUtils.equals("dvb", fileName)) {
1785 dvbDirectoryFound = true;
Jaesung Chung58739e72015-04-24 19:39:59 +09001786 }
1787 }
Jiabinef1659f2016-11-18 10:50:15 +09001788 if (!dvbDirectoryFound) {
1789 return Collections.unmodifiableList(deviceInfosFromPattern1);
1790 }
1791 File dvbDirectory = new File(DVB_DIRECTORY);
1792 // Pattern2: /dev/dvb/adapter%d/frontend%d
1793 ArrayList<DvbDeviceInfo> deviceInfosFromPattern2 = new ArrayList<>();
1794 for (String fileNameInDvb : dvbDirectory.list()) {
1795 Matcher adapterMatcher = sAdapterDirPattern.matcher(fileNameInDvb);
1796 if (adapterMatcher.find()) {
1797 int adapterId = Integer.parseInt(adapterMatcher.group(1));
1798 File adapterDirectory = new File(DVB_DIRECTORY + "/" + fileNameInDvb);
1799 for (String fileNameInAdapter : adapterDirectory.list()) {
1800 Matcher frontendMatcher = sFrontEndInAdapterDirPattern.matcher(
1801 fileNameInAdapter);
1802 if (frontendMatcher.find()) {
1803 int deviceId = Integer.parseInt(frontendMatcher.group(1));
1804 deviceInfosFromPattern2.add(
1805 new DvbDeviceInfo(adapterId, deviceId));
1806 }
1807 }
1808 }
1809 }
1810 return deviceInfosFromPattern2.isEmpty()
1811 ? Collections.unmodifiableList(deviceInfosFromPattern1)
1812 : Collections.unmodifiableList(deviceInfosFromPattern2);
Jaesung Chung58739e72015-04-24 19:39:59 +09001813 } finally {
1814 Binder.restoreCallingIdentity(identity);
1815 }
1816 }
1817
1818 @Override
1819 public ParcelFileDescriptor openDvbDevice(DvbDeviceInfo info, int device)
1820 throws RemoteException {
1821 if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)
1822 != PackageManager.PERMISSION_GRANTED) {
1823 throw new SecurityException("Requires DVB_DEVICE permission");
1824 }
1825
Jiabinef1659f2016-11-18 10:50:15 +09001826 File devDirectory = new File("/dev");
1827 boolean dvbDeviceFound = false;
1828 for (String fileName : devDirectory.list()) {
1829 if (TextUtils.equals("dvb", fileName)) {
1830 File dvbDirectory = new File(DVB_DIRECTORY);
1831 for (String fileNameInDvb : dvbDirectory.list()) {
1832 Matcher adapterMatcher = sAdapterDirPattern.matcher(fileNameInDvb);
1833 if (adapterMatcher.find()) {
1834 File adapterDirectory = new File(DVB_DIRECTORY + "/" + fileNameInDvb);
1835 for (String fileNameInAdapter : adapterDirectory.list()) {
1836 Matcher frontendMatcher = sFrontEndInAdapterDirPattern.matcher(
1837 fileNameInAdapter);
1838 if (frontendMatcher.find()) {
1839 dvbDeviceFound = true;
1840 break;
1841 }
1842 }
1843 }
1844 if (dvbDeviceFound) {
1845 break;
1846 }
1847 }
1848 }
1849 if (dvbDeviceFound) {
1850 break;
1851 }
1852 }
1853
Jaesung Chung58739e72015-04-24 19:39:59 +09001854 final long identity = Binder.clearCallingIdentity();
1855 try {
1856 String deviceFileName;
1857 switch (device) {
1858 case TvInputManager.DVB_DEVICE_DEMUX:
Jiabinef1659f2016-11-18 10:50:15 +09001859 deviceFileName = String.format(dvbDeviceFound
1860 ? "/dev/dvb/adapter%d/demux%d" : "/dev/dvb%d.demux%d",
1861 info.getAdapterId(), info.getDeviceId());
Jaesung Chung58739e72015-04-24 19:39:59 +09001862 break;
1863 case TvInputManager.DVB_DEVICE_DVR:
Jiabinef1659f2016-11-18 10:50:15 +09001864 deviceFileName = String.format(dvbDeviceFound
1865 ? "/dev/dvb/adapter%d/dvr%d" : "/dev/dvb%d.dvr%d",
1866 info.getAdapterId(), info.getDeviceId());
Jaesung Chung58739e72015-04-24 19:39:59 +09001867 break;
1868 case TvInputManager.DVB_DEVICE_FRONTEND:
Jiabinef1659f2016-11-18 10:50:15 +09001869 deviceFileName = String.format(dvbDeviceFound
1870 ? "/dev/dvb/adapter%d/frontend%d" : "/dev/dvb%d.frontend%d",
1871 info.getAdapterId(), info.getDeviceId());
Jaesung Chung58739e72015-04-24 19:39:59 +09001872 break;
1873 default:
1874 throw new IllegalArgumentException("Invalid DVB device: " + device);
1875 }
1876 try {
1877 // The DVB frontend device only needs to be opened in read/write mode, which
1878 // allows performing tuning operations. The DVB demux and DVR device are enough
1879 // to be opened in read only mode.
1880 return ParcelFileDescriptor.open(new File(deviceFileName),
1881 TvInputManager.DVB_DEVICE_FRONTEND == device
1882 ? ParcelFileDescriptor.MODE_READ_WRITE
1883 : ParcelFileDescriptor.MODE_READ_ONLY);
1884 } catch (FileNotFoundException e) {
1885 return null;
1886 }
1887 } finally {
1888 Binder.restoreCallingIdentity(identity);
1889 }
1890 }
1891
1892 @Override
Terry Heoc086a3d2014-06-18 14:26:44 +09001893 public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int userId)
1894 throws RemoteException {
Shubangdd3ec0b2017-06-23 13:54:10 -07001895 ensureCaptureTvInputPermission();
Terry Heoc086a3d2014-06-18 14:26:44 +09001896
1897 final long identity = Binder.clearCallingIdentity();
1898 final int callingUid = Binder.getCallingUid();
1899 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1900 userId, "getAvailableTvStreamConfigList");
1901 try {
1902 return mTvInputHardwareManager.getAvailableTvStreamConfigList(
1903 inputId, callingUid, resolvedUserId);
1904 } finally {
1905 Binder.restoreCallingIdentity(identity);
1906 }
1907 }
1908
1909 @Override
1910 public boolean captureFrame(String inputId, Surface surface, TvStreamConfig config,
1911 int userId)
1912 throws RemoteException {
Shubangdd3ec0b2017-06-23 13:54:10 -07001913 ensureCaptureTvInputPermission();
Terry Heoc086a3d2014-06-18 14:26:44 +09001914
1915 final long identity = Binder.clearCallingIdentity();
1916 final int callingUid = Binder.getCallingUid();
1917 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1918 userId, "captureFrame");
1919 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001920 String hardwareInputId = null;
Terry Heo79124a72014-07-21 15:17:17 +09001921 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001922 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001923 if (userState.inputMap.get(inputId) == null) {
Jae Seofea8dd42014-08-26 13:57:41 -07001924 Slog.e(TAG, "input not found for " + inputId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001925 return false;
1926 }
1927 for (SessionState sessionState : userState.sessionStateMap.values()) {
Jae Seo2cdb05e2016-02-04 22:17:13 +09001928 if (sessionState.inputId.equals(inputId)
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001929 && sessionState.hardwareSessionToken != null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001930 hardwareInputId = userState.sessionStateMap.get(
Jae Seo2cdb05e2016-02-04 22:17:13 +09001931 sessionState.hardwareSessionToken).inputId;
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001932 break;
1933 }
1934 }
Terry Heo79124a72014-07-21 15:17:17 +09001935 }
Terry Heoc086a3d2014-06-18 14:26:44 +09001936 return mTvInputHardwareManager.captureFrame(
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001937 (hardwareInputId != null) ? hardwareInputId : inputId,
Terry Heo79124a72014-07-21 15:17:17 +09001938 surface, config, callingUid, resolvedUserId);
Terry Heoc086a3d2014-06-18 14:26:44 +09001939 } finally {
1940 Binder.restoreCallingIdentity(identity);
1941 }
1942 }
1943
1944 @Override
Terry Heodf9f0a32014-08-06 13:53:33 +09001945 public boolean isSingleSessionActive(int userId) throws RemoteException {
Shubangdd3ec0b2017-06-23 13:54:10 -07001946 ensureCaptureTvInputPermission();
Terry Heodf9f0a32014-08-06 13:53:33 +09001947 final long identity = Binder.clearCallingIdentity();
1948 final int callingUid = Binder.getCallingUid();
1949 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1950 userId, "isSingleSessionActive");
1951 try {
1952 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001953 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Terry Heodf9f0a32014-08-06 13:53:33 +09001954 if (userState.sessionStateMap.size() == 1) {
1955 return true;
Jae Seo93ff14b2015-06-21 14:08:54 -07001956 } else if (userState.sessionStateMap.size() == 2) {
Terry Heodf9f0a32014-08-06 13:53:33 +09001957 SessionState[] sessionStates = userState.sessionStateMap.values().toArray(
Jae Seo93ff14b2015-06-21 14:08:54 -07001958 new SessionState[2]);
Terry Heodf9f0a32014-08-06 13:53:33 +09001959 // Check if there is a wrapper input.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001960 if (sessionStates[0].hardwareSessionToken != null
1961 || sessionStates[1].hardwareSessionToken != null) {
Terry Heodf9f0a32014-08-06 13:53:33 +09001962 return true;
1963 }
1964 }
1965 return false;
1966 }
1967 } finally {
1968 Binder.restoreCallingIdentity(identity);
1969 }
1970 }
1971
Shubangdd3ec0b2017-06-23 13:54:10 -07001972 private void ensureCaptureTvInputPermission() {
1973 if (mContext.checkCallingPermission(
1974 android.Manifest.permission.CAPTURE_TV_INPUT)
1975 != PackageManager.PERMISSION_GRANTED) {
1976 throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1977 }
1978 }
1979
Terry Heodf9f0a32014-08-06 13:53:33 +09001980 @Override
Dongwon Kang2e7f5ce2017-04-05 18:33:50 -07001981 public void requestChannelBrowsable(Uri channelUri, int userId)
1982 throws RemoteException {
1983 final String callingPackageName = getCallingPackageName();
1984 final long identity = Binder.clearCallingIdentity();
1985 final int callingUid = Binder.getCallingUid();
1986 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1987 userId, "requestChannelBrowsable");
1988 try {
1989 Intent intent = new Intent(TvContract.ACTION_CHANNEL_BROWSABLE_REQUESTED);
1990 List<ResolveInfo> list = getContext().getPackageManager()
1991 .queryBroadcastReceivers(intent, 0);
1992 if (list != null) {
1993 for (ResolveInfo info : list) {
1994 String receiverPackageName = info.activityInfo.packageName;
1995 intent.putExtra(TvContract.EXTRA_CHANNEL_ID, ContentUris.parseId(
1996 channelUri));
1997 intent.putExtra(TvContract.EXTRA_PACKAGE_NAME, callingPackageName);
1998 intent.setPackage(receiverPackageName);
1999 getContext().sendBroadcastAsUser(intent, new UserHandle(resolvedUserId));
2000 }
2001 }
2002 } finally {
2003 Binder.restoreCallingIdentity(identity);
2004 }
2005 }
2006
2007 @Override
Jae Seo0f8fc342014-07-02 10:47:08 -07002008 @SuppressWarnings("resource")
Jaewan Kime14c3f42014-06-27 13:47:48 +09002009 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
2010 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -06002011 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
Jaewan Kime14c3f42014-06-27 13:47:48 +09002012
2013 synchronized (mLock) {
2014 pw.println("User Ids (Current user: " + mCurrentUserId + "):");
2015 pw.increaseIndent();
2016 for (int i = 0; i < mUserStates.size(); i++) {
2017 int userId = mUserStates.keyAt(i);
2018 pw.println(Integer.valueOf(userId));
2019 }
2020 pw.decreaseIndent();
2021
2022 for (int i = 0; i < mUserStates.size(); i++) {
2023 int userId = mUserStates.keyAt(i);
Jae Seo4f1a6d42015-07-20 16:15:01 -07002024 UserState userState = getOrCreateUserStateLocked(userId);
Jaewan Kime14c3f42014-06-27 13:47:48 +09002025 pw.println("UserState (" + userId + "):");
2026 pw.increaseIndent();
2027
Wonsik Kim969167d2014-06-24 16:33:17 +09002028 pw.println("inputMap: inputId -> TvInputState");
Jaewan Kime14c3f42014-06-27 13:47:48 +09002029 pw.increaseIndent();
Jaewan Kim8e6b51b2014-07-15 13:01:57 +09002030 for (Map.Entry<String, TvInputState> entry: userState.inputMap.entrySet()) {
2031 pw.println(entry.getKey() + ": " + entry.getValue());
Jaewan Kime14c3f42014-06-27 13:47:48 +09002032 }
2033 pw.decreaseIndent();
2034
Wonsik Kim969167d2014-06-24 16:33:17 +09002035 pw.println("packageSet:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09002036 pw.increaseIndent();
Wonsik Kim969167d2014-06-24 16:33:17 +09002037 for (String packageName : userState.packageSet) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09002038 pw.println(packageName);
2039 }
2040 pw.decreaseIndent();
2041
2042 pw.println("clientStateMap: ITvInputClient -> ClientState");
2043 pw.increaseIndent();
2044 for (Map.Entry<IBinder, ClientState> entry :
2045 userState.clientStateMap.entrySet()) {
2046 ClientState client = entry.getValue();
2047 pw.println(entry.getKey() + ": " + client);
2048
2049 pw.increaseIndent();
2050
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002051 pw.println("sessionTokens:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09002052 pw.increaseIndent();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002053 for (IBinder token : client.sessionTokens) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09002054 pw.println("" + token);
2055 }
2056 pw.decreaseIndent();
2057
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002058 pw.println("clientTokens: " + client.clientToken);
2059 pw.println("userId: " + client.userId);
Jaewan Kime14c3f42014-06-27 13:47:48 +09002060
2061 pw.decreaseIndent();
2062 }
2063 pw.decreaseIndent();
2064
Wonsik Kim187423c2014-06-25 14:12:48 +09002065 pw.println("serviceStateMap: ComponentName -> ServiceState");
Jaewan Kime14c3f42014-06-27 13:47:48 +09002066 pw.increaseIndent();
Wonsik Kim187423c2014-06-25 14:12:48 +09002067 for (Map.Entry<ComponentName, ServiceState> entry :
Jaewan Kime14c3f42014-06-27 13:47:48 +09002068 userState.serviceStateMap.entrySet()) {
2069 ServiceState service = entry.getValue();
2070 pw.println(entry.getKey() + ": " + service);
2071
2072 pw.increaseIndent();
2073
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002074 pw.println("sessionTokens:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09002075 pw.increaseIndent();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002076 for (IBinder token : service.sessionTokens) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09002077 pw.println("" + token);
2078 }
2079 pw.decreaseIndent();
2080
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002081 pw.println("service: " + service.service);
2082 pw.println("callback: " + service.callback);
2083 pw.println("bound: " + service.bound);
2084 pw.println("reconnecting: " + service.reconnecting);
Jaewan Kime14c3f42014-06-27 13:47:48 +09002085
2086 pw.decreaseIndent();
2087 }
2088 pw.decreaseIndent();
2089
2090 pw.println("sessionStateMap: ITvInputSession -> SessionState");
2091 pw.increaseIndent();
2092 for (Map.Entry<IBinder, SessionState> entry :
2093 userState.sessionStateMap.entrySet()) {
2094 SessionState session = entry.getValue();
2095 pw.println(entry.getKey() + ": " + session);
2096
2097 pw.increaseIndent();
Jae Seo2cdb05e2016-02-04 22:17:13 +09002098 pw.println("inputId: " + session.inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002099 pw.println("client: " + session.client);
2100 pw.println("seq: " + session.seq);
2101 pw.println("callingUid: " + session.callingUid);
2102 pw.println("userId: " + session.userId);
2103 pw.println("sessionToken: " + session.sessionToken);
2104 pw.println("session: " + session.session);
2105 pw.println("logUri: " + session.logUri);
2106 pw.println("hardwareSessionToken: " + session.hardwareSessionToken);
Jaewan Kime14c3f42014-06-27 13:47:48 +09002107 pw.decreaseIndent();
2108 }
2109 pw.decreaseIndent();
2110
Wonsik Kim969167d2014-06-24 16:33:17 +09002111 pw.println("callbackSet:");
2112 pw.increaseIndent();
2113 for (ITvInputManagerCallback callback : userState.callbackSet) {
2114 pw.println(callback.toString());
2115 }
2116 pw.decreaseIndent();
2117
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09002118 pw.println("mainSessionToken: " + userState.mainSessionToken);
Jaewan Kime14c3f42014-06-27 13:47:48 +09002119 pw.decreaseIndent();
2120 }
2121 }
yangren8d718e12016-02-15 17:02:49 +09002122 mTvInputHardwareManager.dump(fd, writer, args);
Jaewan Kime14c3f42014-06-27 13:47:48 +09002123 }
Jae Seo39570912014-02-20 18:23:25 -08002124 }
2125
Wonsik Kim969167d2014-06-24 16:33:17 +09002126 private static final class UserState {
2127 // A mapping from the TV input id to its TvInputState.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002128 private Map<String, TvInputState> inputMap = new HashMap<>();
Wonsik Kim969167d2014-06-24 16:33:17 +09002129
2130 // A set of all TV input packages.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002131 private final Set<String> packageSet = new HashSet<>();
Jae Seo5c80ad22014-06-12 19:52:58 -07002132
Jae Seo9c165d62014-08-25 14:39:26 -07002133 // A list of all TV content rating systems defined.
2134 private final List<TvContentRatingSystemInfo>
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002135 contentRatingSystemList = new ArrayList<>();
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +09002136
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002137 // A mapping from the token of a client to its state.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002138 private final Map<IBinder, ClientState> clientStateMap = new HashMap<>();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002139
Jae Seo39570912014-02-20 18:23:25 -08002140 // A mapping from the name of a TV input service to its state.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002141 private final Map<ComponentName, ServiceState> serviceStateMap = new HashMap<>();
Jae Seo39570912014-02-20 18:23:25 -08002142
2143 // A mapping from the token of a TV input session to its state.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002144 private final Map<IBinder, SessionState> sessionStateMap = new HashMap<>();
Wonsik Kim969167d2014-06-24 16:33:17 +09002145
2146 // A set of callbacks.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002147 private final Set<ITvInputManagerCallback> callbackSet = new HashSet<>();
Terry Heo79124a72014-07-21 15:17:17 +09002148
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09002149 // The token of a "main" TV input session.
2150 private IBinder mainSessionToken = null;
Jae Seo783645e2014-07-28 17:30:50 +09002151
2152 // Persistent data store for all internal settings maintained by the TV input manager
2153 // service.
2154 private final PersistentDataStore persistentDataStore;
2155
2156 private UserState(Context context, int userId) {
2157 persistentDataStore = new PersistentDataStore(context, userId);
2158 }
Jae Seo39570912014-02-20 18:23:25 -08002159 }
2160
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002161 private final class ClientState implements IBinder.DeathRecipient {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002162 private final List<IBinder> sessionTokens = new ArrayList<>();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002163
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002164 private IBinder clientToken;
2165 private final int userId;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002166
2167 ClientState(IBinder clientToken, int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002168 this.clientToken = clientToken;
2169 this.userId = userId;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002170 }
2171
2172 public boolean isEmpty() {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002173 return sessionTokens.isEmpty();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002174 }
2175
2176 @Override
2177 public void binderDied() {
2178 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002179 UserState userState = getOrCreateUserStateLocked(userId);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002180 // DO NOT remove the client state of clientStateMap in this method. It will be
Ji-Hwan Leea65118e2014-07-24 16:30:02 +09002181 // removed in releaseSessionLocked().
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002182 ClientState clientState = userState.clientStateMap.get(clientToken);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002183 if (clientState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002184 while (clientState.sessionTokens.size() > 0) {
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002185 releaseSessionLocked(
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002186 clientState.sessionTokens.get(0), Process.SYSTEM_UID, userId);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002187 }
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002188 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002189 clientToken = null;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002190 }
2191 }
2192 }
2193
Jae Seo39570912014-02-20 18:23:25 -08002194 private final class ServiceState {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002195 private final List<IBinder> sessionTokens = new ArrayList<>();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002196 private final ServiceConnection connection;
2197 private final ComponentName component;
2198 private final boolean isHardware;
Shubang71d5c762017-02-23 15:46:40 -08002199 private final Map<String, TvInputInfo> hardwareInputMap = new HashMap<>();
Jae Seo39570912014-02-20 18:23:25 -08002200
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002201 private ITvInputService service;
2202 private ServiceCallback callback;
2203 private boolean bound;
2204 private boolean reconnecting;
Jae Seo39570912014-02-20 18:23:25 -08002205
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002206 private ServiceState(ComponentName component, int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002207 this.component = component;
2208 this.connection = new InputServiceConnection(component, userId);
2209 this.isHardware = hasHardwarePermission(mContext.getPackageManager(), component);
2210 }
2211 }
2212
2213 private static final class TvInputState {
2214 // A TvInputInfo object which represents the TV input.
2215 private TvInputInfo info;
2216
2217 // The state of TV input. Connected by default.
2218 private int state = INPUT_STATE_CONNECTED;
2219
2220 @Override
2221 public String toString() {
2222 return "info: " + info + "; state: " + state;
Jae Seo39570912014-02-20 18:23:25 -08002223 }
2224 }
2225
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002226 private final class SessionState implements IBinder.DeathRecipient {
Jae Seo2cdb05e2016-02-04 22:17:13 +09002227 private final String inputId;
2228 private final ComponentName componentName;
Jae Seoa826d012016-01-18 13:03:35 -08002229 private final boolean isRecordingSession;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002230 private final ITvInputClient client;
2231 private final int seq;
2232 private final int callingUid;
2233 private final int userId;
2234 private final IBinder sessionToken;
2235 private ITvInputSession session;
2236 private Uri logUri;
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09002237 // Not null if this session represents an external device connected to a hardware TV input.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002238 private IBinder hardwareSessionToken;
Jae Seo39570912014-02-20 18:23:25 -08002239
Jae Seo2cdb05e2016-02-04 22:17:13 +09002240 private SessionState(IBinder sessionToken, String inputId, ComponentName componentName,
2241 boolean isRecordingSession, ITvInputClient client, int seq, int callingUid,
2242 int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002243 this.sessionToken = sessionToken;
Jae Seo2cdb05e2016-02-04 22:17:13 +09002244 this.inputId = inputId;
2245 this.componentName = componentName;
Jae Seoa826d012016-01-18 13:03:35 -08002246 this.isRecordingSession = isRecordingSession;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002247 this.client = client;
2248 this.seq = seq;
2249 this.callingUid = callingUid;
2250 this.userId = userId;
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002251 }
2252
2253 @Override
2254 public void binderDied() {
2255 synchronized (mLock) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002256 session = null;
shubang8049f202016-04-25 11:21:42 -07002257 clearSessionAndNotifyClientLocked(this);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002258 }
Jae Seo39570912014-02-20 18:23:25 -08002259 }
2260 }
2261
2262 private final class InputServiceConnection implements ServiceConnection {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002263 private final ComponentName mComponent;
Jae Seo39570912014-02-20 18:23:25 -08002264 private final int mUserId;
2265
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002266 private InputServiceConnection(ComponentName component, int userId) {
2267 mComponent = component;
Jae Seo39570912014-02-20 18:23:25 -08002268 mUserId = userId;
2269 }
2270
2271 @Override
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002272 public void onServiceConnected(ComponentName component, IBinder service) {
Jae Seo39570912014-02-20 18:23:25 -08002273 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002274 Slog.d(TAG, "onServiceConnected(component=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -08002275 }
2276 synchronized (mLock) {
Dongwon Kang81e3c3e2015-09-11 15:24:25 -07002277 UserState userState = mUserStates.get(mUserId);
2278 if (userState == null) {
2279 // The user was removed while connecting.
2280 mContext.unbindService(this);
2281 return;
2282 }
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002283 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002284 serviceState.service = ITvInputService.Stub.asInterface(service);
Jae Seo39570912014-02-20 18:23:25 -08002285
2286 // Register a callback, if we need to.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002287 if (serviceState.isHardware && serviceState.callback == null) {
2288 serviceState.callback = new ServiceCallback(mComponent, mUserId);
Jae Seo39570912014-02-20 18:23:25 -08002289 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002290 serviceState.service.registerCallback(serviceState.callback);
Jae Seo39570912014-02-20 18:23:25 -08002291 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09002292 Slog.e(TAG, "error in registerCallback", e);
Jae Seo39570912014-02-20 18:23:25 -08002293 }
2294 }
2295
shubangaf2fb662018-11-09 17:03:46 -08002296 List<IBinder> tokensToBeRemoved = new ArrayList<>();
2297
Jae Seo39570912014-02-20 18:23:25 -08002298 // And create sessions, if any.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002299 for (IBinder sessionToken : serviceState.sessionTokens) {
shubangaf2fb662018-11-09 17:03:46 -08002300 if (!createSessionInternalLocked(serviceState.service, sessionToken, mUserId)) {
2301 tokensToBeRemoved.add(sessionToken);
2302 }
2303 }
2304
2305 for (IBinder sessionToken : tokensToBeRemoved) {
2306 removeSessionStateLocked(sessionToken, mUserId);
Jae Seo39570912014-02-20 18:23:25 -08002307 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002308
Wonsik Kim187423c2014-06-25 14:12:48 +09002309 for (TvInputState inputState : userState.inputMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002310 if (inputState.info.getComponent().equals(component)
Jae Seo82fce642015-04-20 15:37:50 -07002311 && inputState.state != INPUT_STATE_CONNECTED) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002312 notifyInputStateChangedLocked(userState, inputState.info.getId(),
2313 inputState.state, null);
Wonsik Kim187423c2014-06-25 14:12:48 +09002314 }
2315 }
2316
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002317 if (serviceState.isHardware) {
Shubang71d5c762017-02-23 15:46:40 -08002318 serviceState.hardwareInputMap.clear();
Jae Seoc980f43d2016-02-09 23:46:58 -08002319 for (TvInputHardwareInfo hardware : mTvInputHardwareManager.getHardwareList()) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002320 try {
Jae Seoc980f43d2016-02-09 23:46:58 -08002321 serviceState.service.notifyHardwareAdded(hardware);
Wonsik Kim187423c2014-06-25 14:12:48 +09002322 } catch (RemoteException e) {
2323 Slog.e(TAG, "error in notifyHardwareAdded", e);
2324 }
2325 }
Jae Seoc980f43d2016-02-09 23:46:58 -08002326 for (HdmiDeviceInfo device : mTvInputHardwareManager.getHdmiDeviceList()) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002327 try {
Jae Seoc980f43d2016-02-09 23:46:58 -08002328 serviceState.service.notifyHdmiDeviceAdded(device);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002329 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002330 Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002331 }
2332 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002333 }
Jae Seo39570912014-02-20 18:23:25 -08002334 }
2335 }
2336
2337 @Override
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002338 public void onServiceDisconnected(ComponentName component) {
Jae Seo39570912014-02-20 18:23:25 -08002339 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002340 Slog.d(TAG, "onServiceDisconnected(component=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -08002341 }
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002342 if (!mComponent.equals(component)) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002343 throw new IllegalArgumentException("Mismatched ComponentName: "
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002344 + mComponent + " (expected), " + component + " (actual).");
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002345 }
2346 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002347 UserState userState = getOrCreateUserStateLocked(mUserId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002348 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002349 if (serviceState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002350 serviceState.reconnecting = true;
2351 serviceState.bound = false;
2352 serviceState.service = null;
2353 serviceState.callback = null;
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002354
Dongwon Kang426c9a42014-08-26 17:39:21 +09002355 abortPendingCreateSessionRequestsLocked(serviceState, null, mUserId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002356 }
2357 }
Jae Seo39570912014-02-20 18:23:25 -08002358 }
2359 }
2360
2361 private final class ServiceCallback extends ITvInputServiceCallback.Stub {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002362 private final ComponentName mComponent;
Jae Seo39570912014-02-20 18:23:25 -08002363 private final int mUserId;
2364
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002365 ServiceCallback(ComponentName component, int userId) {
2366 mComponent = component;
Jae Seo39570912014-02-20 18:23:25 -08002367 mUserId = userId;
2368 }
2369
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002370 private void ensureHardwarePermission() {
2371 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
2372 != PackageManager.PERMISSION_GRANTED) {
2373 throw new SecurityException("The caller does not have hardware permission");
2374 }
2375 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002376
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002377 private void ensureValidInput(TvInputInfo inputInfo) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002378 if (inputInfo.getId() == null || !mComponent.equals(inputInfo.getComponent())) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002379 throw new IllegalArgumentException("Invalid TvInputInfo");
2380 }
2381 }
2382
Jae Seo1abbbcd2016-01-28 22:20:41 -08002383 private void addHardwareInputLocked(TvInputInfo inputInfo) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002384 ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
Shubang71d5c762017-02-23 15:46:40 -08002385 serviceState.hardwareInputMap.put(inputInfo.getId(), inputInfo);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +09002386 buildTvInputListLocked(mUserId, null);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002387 }
2388
Jae Seo1abbbcd2016-01-28 22:20:41 -08002389 public void addHardwareInput(int deviceId, TvInputInfo inputInfo) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002390 ensureHardwarePermission();
2391 ensureValidInput(inputInfo);
2392 synchronized (mLock) {
Jae Seo1abbbcd2016-01-28 22:20:41 -08002393 mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo);
2394 addHardwareInputLocked(inputInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002395 }
2396 }
2397
Jae Seo1abbbcd2016-01-28 22:20:41 -08002398 public void addHdmiInput(int id, TvInputInfo inputInfo) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002399 ensureHardwarePermission();
2400 ensureValidInput(inputInfo);
2401 synchronized (mLock) {
Jae Seo1abbbcd2016-01-28 22:20:41 -08002402 mTvInputHardwareManager.addHdmiInput(id, inputInfo);
2403 addHardwareInputLocked(inputInfo);
Wonsik Kim187423c2014-06-25 14:12:48 +09002404 }
2405 }
2406
Jae Seo1abbbcd2016-01-28 22:20:41 -08002407 public void removeHardwareInput(String inputId) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002408 ensureHardwarePermission();
Wonsik Kim187423c2014-06-25 14:12:48 +09002409 synchronized (mLock) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002410 ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
Shubang71d5c762017-02-23 15:46:40 -08002411 boolean removed = serviceState.hardwareInputMap.remove(inputId) != null;
Wonsik Kim187423c2014-06-25 14:12:48 +09002412 if (removed) {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +09002413 buildTvInputListLocked(mUserId, null);
Jae Seo1abbbcd2016-01-28 22:20:41 -08002414 mTvInputHardwareManager.removeHardwareInput(inputId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002415 } else {
Jae Seofea8dd42014-08-26 13:57:41 -07002416 Slog.e(TAG, "failed to remove input " + inputId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002417 }
Jae Seo39570912014-02-20 18:23:25 -08002418 }
2419 }
2420 }
Jae Seo31dc634be2014-04-15 17:40:23 -07002421
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002422 private final class SessionCallback extends ITvInputSessionCallback.Stub {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002423 private final SessionState mSessionState;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002424 private final InputChannel[] mChannels;
2425
2426 SessionCallback(SessionState sessionState, InputChannel[] channels) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002427 mSessionState = sessionState;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002428 mChannels = channels;
2429 }
2430
2431 @Override
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002432 public void onSessionCreated(ITvInputSession session, IBinder hardwareSessionToken) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002433 if (DEBUG) {
Jae Seo2cdb05e2016-02-04 22:17:13 +09002434 Slog.d(TAG, "onSessionCreated(inputId=" + mSessionState.inputId + ")");
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002435 }
2436 synchronized (mLock) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002437 mSessionState.session = session;
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002438 mSessionState.hardwareSessionToken = hardwareSessionToken;
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002439 if (session != null && addSessionTokenToClientStateLocked(session)) {
2440 sendSessionTokenToClientLocked(mSessionState.client,
Jae Seo2cdb05e2016-02-04 22:17:13 +09002441 mSessionState.inputId, mSessionState.sessionToken, mChannels[0],
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002442 mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002443 } else {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002444 removeSessionStateLocked(mSessionState.sessionToken, mSessionState.userId);
2445 sendSessionTokenToClientLocked(mSessionState.client,
Jae Seo2cdb05e2016-02-04 22:17:13 +09002446 mSessionState.inputId, null, null, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002447 }
2448 mChannels[0].dispose();
2449 }
2450 }
2451
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002452 private boolean addSessionTokenToClientStateLocked(ITvInputSession session) {
2453 try {
2454 session.asBinder().linkToDeath(mSessionState, 0);
2455 } catch (RemoteException e) {
2456 Slog.e(TAG, "session process has already died", e);
2457 return false;
2458 }
2459
2460 IBinder clientToken = mSessionState.client.asBinder();
Jae Seo4f1a6d42015-07-20 16:15:01 -07002461 UserState userState = getOrCreateUserStateLocked(mSessionState.userId);
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002462 ClientState clientState = userState.clientStateMap.get(clientToken);
2463 if (clientState == null) {
2464 clientState = new ClientState(clientToken, mSessionState.userId);
2465 try {
2466 clientToken.linkToDeath(clientState, 0);
2467 } catch (RemoteException e) {
2468 Slog.e(TAG, "client process has already died", e);
2469 return false;
2470 }
2471 userState.clientStateMap.put(clientToken, clientState);
2472 }
2473 clientState.sessionTokens.add(mSessionState.sessionToken);
2474 return true;
2475 }
2476
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002477 @Override
2478 public void onChannelRetuned(Uri channelUri) {
2479 synchronized (mLock) {
2480 if (DEBUG) {
2481 Slog.d(TAG, "onChannelRetuned(" + channelUri + ")");
2482 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002483 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002484 return;
2485 }
2486 try {
2487 // TODO: Consider adding this channel change in the watch log. When we do
2488 // that, how we can protect the watch log from malicious tv inputs should
2489 // be addressed. e.g. add a field which represents where the channel change
2490 // originated from.
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002491 mSessionState.client.onChannelRetuned(channelUri, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002492 } catch (RemoteException e) {
2493 Slog.e(TAG, "error in onChannelRetuned", e);
2494 }
2495 }
2496 }
2497
2498 @Override
2499 public void onTracksChanged(List<TvTrackInfo> tracks) {
2500 synchronized (mLock) {
2501 if (DEBUG) {
2502 Slog.d(TAG, "onTracksChanged(" + tracks + ")");
2503 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002504 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002505 return;
2506 }
2507 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002508 mSessionState.client.onTracksChanged(tracks, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002509 } catch (RemoteException e) {
2510 Slog.e(TAG, "error in onTracksChanged", e);
2511 }
2512 }
2513 }
2514
2515 @Override
2516 public void onTrackSelected(int type, String trackId) {
2517 synchronized (mLock) {
2518 if (DEBUG) {
2519 Slog.d(TAG, "onTrackSelected(type=" + type + ", trackId=" + trackId + ")");
2520 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002521 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002522 return;
2523 }
2524 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002525 mSessionState.client.onTrackSelected(type, trackId, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002526 } catch (RemoteException e) {
2527 Slog.e(TAG, "error in onTrackSelected", e);
2528 }
2529 }
2530 }
2531
2532 @Override
2533 public void onVideoAvailable() {
2534 synchronized (mLock) {
2535 if (DEBUG) {
2536 Slog.d(TAG, "onVideoAvailable()");
2537 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002538 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002539 return;
2540 }
2541 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002542 mSessionState.client.onVideoAvailable(mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002543 } catch (RemoteException e) {
2544 Slog.e(TAG, "error in onVideoAvailable", e);
2545 }
2546 }
2547 }
2548
2549 @Override
2550 public void onVideoUnavailable(int reason) {
2551 synchronized (mLock) {
2552 if (DEBUG) {
2553 Slog.d(TAG, "onVideoUnavailable(" + reason + ")");
2554 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002555 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002556 return;
2557 }
2558 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002559 mSessionState.client.onVideoUnavailable(reason, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002560 } catch (RemoteException e) {
2561 Slog.e(TAG, "error in onVideoUnavailable", e);
2562 }
2563 }
2564 }
2565
2566 @Override
2567 public void onContentAllowed() {
2568 synchronized (mLock) {
2569 if (DEBUG) {
2570 Slog.d(TAG, "onContentAllowed()");
2571 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002572 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002573 return;
2574 }
2575 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002576 mSessionState.client.onContentAllowed(mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002577 } catch (RemoteException e) {
2578 Slog.e(TAG, "error in onContentAllowed", e);
2579 }
2580 }
2581 }
2582
2583 @Override
2584 public void onContentBlocked(String rating) {
2585 synchronized (mLock) {
2586 if (DEBUG) {
2587 Slog.d(TAG, "onContentBlocked()");
2588 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002589 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002590 return;
2591 }
2592 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002593 mSessionState.client.onContentBlocked(rating, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002594 } catch (RemoteException e) {
2595 Slog.e(TAG, "error in onContentBlocked", e);
2596 }
2597 }
2598 }
2599
2600 @Override
2601 public void onLayoutSurface(int left, int top, int right, int bottom) {
2602 synchronized (mLock) {
2603 if (DEBUG) {
2604 Slog.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top
2605 + ", right=" + right + ", bottom=" + bottom + ",)");
2606 }
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.onLayoutSurface(left, top, right, bottom,
2612 mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002613 } catch (RemoteException e) {
2614 Slog.e(TAG, "error in onLayoutSurface", e);
2615 }
2616 }
2617 }
2618
2619 @Override
2620 public void onSessionEvent(String eventType, Bundle eventArgs) {
2621 synchronized (mLock) {
2622 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002623 Slog.d(TAG, "onEvent(eventType=" + eventType + ", eventArgs=" + eventArgs
2624 + ")");
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002625 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002626 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002627 return;
2628 }
2629 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002630 mSessionState.client.onSessionEvent(eventType, eventArgs, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002631 } catch (RemoteException e) {
2632 Slog.e(TAG, "error in onSessionEvent", e);
2633 }
2634 }
2635 }
Dongwon Kang6f0240c2015-03-31 17:56:36 -07002636
2637 @Override
2638 public void onTimeShiftStatusChanged(int status) {
2639 synchronized (mLock) {
2640 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002641 Slog.d(TAG, "onTimeShiftStatusChanged(status=" + status + ")");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07002642 }
2643 if (mSessionState.session == null || mSessionState.client == null) {
2644 return;
2645 }
2646 try {
2647 mSessionState.client.onTimeShiftStatusChanged(status, mSessionState.seq);
2648 } catch (RemoteException e) {
2649 Slog.e(TAG, "error in onTimeShiftStatusChanged", e);
2650 }
2651 }
2652 }
2653
2654 @Override
2655 public void onTimeShiftStartPositionChanged(long timeMs) {
2656 synchronized (mLock) {
2657 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002658 Slog.d(TAG, "onTimeShiftStartPositionChanged(timeMs=" + timeMs + ")");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07002659 }
2660 if (mSessionState.session == null || mSessionState.client == null) {
2661 return;
2662 }
2663 try {
2664 mSessionState.client.onTimeShiftStartPositionChanged(timeMs, mSessionState.seq);
2665 } catch (RemoteException e) {
2666 Slog.e(TAG, "error in onTimeShiftStartPositionChanged", e);
2667 }
2668 }
2669 }
2670
2671 @Override
2672 public void onTimeShiftCurrentPositionChanged(long timeMs) {
2673 synchronized (mLock) {
2674 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002675 Slog.d(TAG, "onTimeShiftCurrentPositionChanged(timeMs=" + timeMs + ")");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07002676 }
2677 if (mSessionState.session == null || mSessionState.client == null) {
2678 return;
2679 }
2680 try {
2681 mSessionState.client.onTimeShiftCurrentPositionChanged(timeMs,
2682 mSessionState.seq);
2683 } catch (RemoteException e) {
2684 Slog.e(TAG, "error in onTimeShiftCurrentPositionChanged", e);
2685 }
2686 }
2687 }
Jae Seoa826d012016-01-18 13:03:35 -08002688
2689 // For the recording session only
2690 @Override
Dongwon Kangb55c7512016-03-01 09:36:07 -08002691 public void onTuned(Uri channelUri) {
Jae Seoa826d012016-01-18 13:03:35 -08002692 synchronized (mLock) {
2693 if (DEBUG) {
Jae Seoe3c11e82016-02-08 23:18:49 -08002694 Slog.d(TAG, "onTuned()");
Jae Seoa826d012016-01-18 13:03:35 -08002695 }
2696 if (mSessionState.session == null || mSessionState.client == null) {
2697 return;
2698 }
2699 try {
Dongwon Kangb55c7512016-03-01 09:36:07 -08002700 mSessionState.client.onTuned(mSessionState.seq, channelUri);
Jae Seoa826d012016-01-18 13:03:35 -08002701 } catch (RemoteException e) {
Jae Seoe3c11e82016-02-08 23:18:49 -08002702 Slog.e(TAG, "error in onTuned", e);
Jae Seoa826d012016-01-18 13:03:35 -08002703 }
2704 }
2705 }
2706
2707 // For the recording session only
2708 @Override
2709 public void onRecordingStopped(Uri recordedProgramUri) {
2710 synchronized (mLock) {
2711 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002712 Slog.d(TAG, "onRecordingStopped(recordedProgramUri=" + recordedProgramUri
2713 + ")");
Jae Seoa826d012016-01-18 13:03:35 -08002714 }
2715 if (mSessionState.session == null || mSessionState.client == null) {
2716 return;
2717 }
2718 try {
2719 mSessionState.client.onRecordingStopped(recordedProgramUri, mSessionState.seq);
2720 } catch (RemoteException e) {
2721 Slog.e(TAG, "error in onRecordingStopped", e);
2722 }
2723 }
2724 }
2725
2726 // For the recording session only
2727 @Override
2728 public void onError(int error) {
2729 synchronized (mLock) {
2730 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002731 Slog.d(TAG, "onError(error=" + error + ")");
Jae Seoa826d012016-01-18 13:03:35 -08002732 }
2733 if (mSessionState.session == null || mSessionState.client == null) {
2734 return;
2735 }
2736 try {
2737 mSessionState.client.onError(error, mSessionState.seq);
2738 } catch (RemoteException e) {
2739 Slog.e(TAG, "error in onError", e);
2740 }
2741 }
2742 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002743 }
2744
2745 private static final class WatchLogHandler extends Handler {
Jae Seo7eb75df2014-08-08 22:20:48 -07002746 // There are only two kinds of watch events that can happen on the system:
2747 // 1. The current TV input session is tuned to a new channel.
2748 // 2. The session is released for some reason.
2749 // The former indicates the end of the previous log entry, if any, followed by the start of
2750 // a new entry. The latter indicates the end of the most recent entry for the given session.
2751 // Here the system supplies the database the smallest set of information only that is
2752 // sufficient to consolidate the log entries while minimizing database operations in the
2753 // system service.
Jae Seo8c375fe2015-06-23 20:33:25 -07002754 static final int MSG_LOG_WATCH_START = 1;
2755 static final int MSG_LOG_WATCH_END = 2;
2756 static final int MSG_SWITCH_CONTENT_RESOLVER = 3;
Jae Seo31dc634be2014-04-15 17:40:23 -07002757
Jae Seo8c375fe2015-06-23 20:33:25 -07002758 private ContentResolver mContentResolver;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002759
Jae Seo8c375fe2015-06-23 20:33:25 -07002760 WatchLogHandler(ContentResolver contentResolver, Looper looper) {
Jae Seo31dc634be2014-04-15 17:40:23 -07002761 super(looper);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002762 mContentResolver = contentResolver;
Jae Seo31dc634be2014-04-15 17:40:23 -07002763 }
2764
2765 @Override
2766 public void handleMessage(Message msg) {
2767 switch (msg.what) {
Jae Seo7eb75df2014-08-08 22:20:48 -07002768 case MSG_LOG_WATCH_START: {
Jae Seo31dc634be2014-04-15 17:40:23 -07002769 SomeArgs args = (SomeArgs) msg.obj;
Jae Seo7eb75df2014-08-08 22:20:48 -07002770 String packageName = (String) args.arg1;
2771 long watchStartTime = (long) args.arg2;
2772 long channelId = (long) args.arg3;
2773 Bundle tuneParams = (Bundle) args.arg4;
2774 IBinder sessionToken = (IBinder) args.arg5;
2775
2776 ContentValues values = new ContentValues();
2777 values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
2778 values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
2779 watchStartTime);
2780 values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
2781 if (tuneParams != null) {
2782 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS,
2783 encodeTuneParams(tuneParams));
2784 }
2785 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
2786 sessionToken.toString());
2787
2788 mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
Jae Seo31dc634be2014-04-15 17:40:23 -07002789 args.recycle();
Jae Seo8c375fe2015-06-23 20:33:25 -07002790 break;
Jae Seo31dc634be2014-04-15 17:40:23 -07002791 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002792 case MSG_LOG_WATCH_END: {
Jae Seo31dc634be2014-04-15 17:40:23 -07002793 SomeArgs args = (SomeArgs) msg.obj;
Jae Seo7eb75df2014-08-08 22:20:48 -07002794 IBinder sessionToken = (IBinder) args.arg1;
2795 long watchEndTime = (long) args.arg2;
2796
2797 ContentValues values = new ContentValues();
2798 values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
2799 watchEndTime);
2800 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
2801 sessionToken.toString());
2802
2803 mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
Jae Seo31dc634be2014-04-15 17:40:23 -07002804 args.recycle();
Jae Seo8c375fe2015-06-23 20:33:25 -07002805 break;
2806 }
2807 case MSG_SWITCH_CONTENT_RESOLVER: {
2808 mContentResolver = (ContentResolver) msg.obj;
2809 break;
Jae Seo31dc634be2014-04-15 17:40:23 -07002810 }
2811 default: {
Jae Seo8c375fe2015-06-23 20:33:25 -07002812 Slog.w(TAG, "unhandled message code: " + msg.what);
2813 break;
Jae Seo31dc634be2014-04-15 17:40:23 -07002814 }
2815 }
2816 }
2817
Jae Seo7eb75df2014-08-08 22:20:48 -07002818 private String encodeTuneParams(Bundle tuneParams) {
2819 StringBuilder builder = new StringBuilder();
2820 Set<String> keySet = tuneParams.keySet();
2821 Iterator<String> it = keySet.iterator();
2822 while (it.hasNext()) {
2823 String key = it.next();
2824 Object value = tuneParams.get(key);
2825 if (value == null) {
2826 continue;
Jae Seo579befe2014-08-06 19:18:37 -07002827 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002828 builder.append(replaceEscapeCharacters(key));
2829 builder.append("=");
2830 builder.append(replaceEscapeCharacters(value.toString()));
2831 if (it.hasNext()) {
2832 builder.append(", ");
Jae Seo31dc634be2014-04-15 17:40:23 -07002833 }
2834 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002835 return builder.toString();
Jae Seo31dc634be2014-04-15 17:40:23 -07002836 }
2837
Jae Seo7eb75df2014-08-08 22:20:48 -07002838 private String replaceEscapeCharacters(String src) {
2839 final char ESCAPE_CHARACTER = '%';
2840 final String ENCODING_TARGET_CHARACTERS = "%=,";
2841 StringBuilder builder = new StringBuilder();
2842 for (char ch : src.toCharArray()) {
2843 if (ENCODING_TARGET_CHARACTERS.indexOf(ch) >= 0) {
2844 builder.append(ESCAPE_CHARACTER);
Chulwoo Lee8d4ded02014-07-10 03:56:39 +09002845 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002846 builder.append(ch);
Jae Seo31dc634be2014-04-15 17:40:23 -07002847 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002848 return builder.toString();
Jae Seo579befe2014-08-06 19:18:37 -07002849 }
Jae Seo31dc634be2014-04-15 17:40:23 -07002850 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002851
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002852 private final class HardwareListener implements TvInputHardwareManager.Listener {
Wonsik Kim187423c2014-06-25 14:12:48 +09002853 @Override
2854 public void onStateChanged(String inputId, int state) {
Wonsik Kim969167d2014-06-24 16:33:17 +09002855 synchronized (mLock) {
2856 setStateLocked(inputId, state, mCurrentUserId);
2857 }
2858 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002859
2860 @Override
2861 public void onHardwareDeviceAdded(TvInputHardwareInfo info) {
2862 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002863 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002864 // Broadcast the event to all hardware inputs.
2865 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002866 if (!serviceState.isHardware || serviceState.service == null) continue;
Wonsik Kim187423c2014-06-25 14:12:48 +09002867 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002868 serviceState.service.notifyHardwareAdded(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09002869 } catch (RemoteException e) {
2870 Slog.e(TAG, "error in notifyHardwareAdded", e);
2871 }
2872 }
2873 }
2874 }
2875
2876 @Override
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002877 public void onHardwareDeviceRemoved(TvInputHardwareInfo info) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002878 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002879 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002880 // Broadcast the event to all hardware inputs.
2881 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002882 if (!serviceState.isHardware || serviceState.service == null) continue;
Wonsik Kim187423c2014-06-25 14:12:48 +09002883 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002884 serviceState.service.notifyHardwareRemoved(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09002885 } catch (RemoteException e) {
2886 Slog.e(TAG, "error in notifyHardwareRemoved", e);
2887 }
2888 }
2889 }
2890 }
2891
2892 @Override
Jae Seo546c6352014-08-07 11:57:01 -07002893 public void onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002894 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002895 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002896 // Broadcast the event to all hardware inputs.
2897 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002898 if (!serviceState.isHardware || serviceState.service == null) continue;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002899 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002900 serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002901 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002902 Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002903 }
2904 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002905 }
2906 }
2907
2908 @Override
Jae Seo546c6352014-08-07 11:57:01 -07002909 public void onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002910 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002911 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002912 // Broadcast the event to all hardware inputs.
2913 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002914 if (!serviceState.isHardware || serviceState.service == null) continue;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002915 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002916 serviceState.service.notifyHdmiDeviceRemoved(deviceInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002917 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002918 Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002919 }
2920 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002921 }
2922 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09002923
2924 @Override
Wonsik Kime92f8572014-08-12 18:30:24 +09002925 public void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo deviceInfo) {
2926 synchronized (mLock) {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002927 Integer state;
Wonsik Kime92f8572014-08-12 18:30:24 +09002928 switch (deviceInfo.getDevicePowerStatus()) {
2929 case HdmiControlManager.POWER_STATUS_ON:
2930 state = INPUT_STATE_CONNECTED;
2931 break;
2932 case HdmiControlManager.POWER_STATUS_STANDBY:
2933 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON:
2934 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY:
2935 state = INPUT_STATE_CONNECTED_STANDBY;
2936 break;
2937 case HdmiControlManager.POWER_STATUS_UNKNOWN:
2938 default:
2939 state = null;
2940 break;
2941 }
2942 if (state != null) {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002943 setStateLocked(inputId, state, mCurrentUserId);
Wonsik Kime92f8572014-08-12 18:30:24 +09002944 }
2945 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09002946 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002947 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09002948
2949 private static class SessionNotFoundException extends IllegalArgumentException {
Dongwon Kangfdce9e52014-12-04 18:08:00 +09002950 public SessionNotFoundException(String name) {
2951 super(name);
2952 }
2953 }
Jae Seo39570912014-02-20 18:23:25 -08002954}