blob: 49aaa4aa1047112f4a4029bddc084e02cf29bd86 [file] [log] [blame]
Jae Seo39570912014-02-20 18:23:25 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.tv;
18
Wonsik Kim969167d2014-06-24 16:33:17 +090019import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
Wonsik Kime92f8572014-08-12 18:30:24 +090020import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
Wonsik Kim969167d2014-06-24 16:33:17 +090021
Jae Seo4eee6a72016-02-06 11:11:35 +090022import android.annotation.Nullable;
Jae Seo39570912014-02-20 18:23:25 -080023import android.app.ActivityManager;
24import android.content.BroadcastReceiver;
25import android.content.ComponentName;
Jae Seo5c80ad22014-06-12 19:52:58 -070026import android.content.ContentProviderOperation;
27import android.content.ContentProviderResult;
Jae Seo31dc634be2014-04-15 17:40:23 -070028import android.content.ContentResolver;
29import android.content.ContentUris;
30import android.content.ContentValues;
Jae Seo39570912014-02-20 18:23:25 -080031import android.content.Context;
32import android.content.Intent;
33import android.content.IntentFilter;
Jae Seo5c80ad22014-06-12 19:52:58 -070034import android.content.OperationApplicationException;
Jae Seo39570912014-02-20 18:23:25 -080035import android.content.ServiceConnection;
Jae Seo9c165d62014-08-25 14:39:26 -070036import android.content.pm.ActivityInfo;
Jae Seo39570912014-02-20 18:23:25 -080037import android.content.pm.PackageManager;
Jae Seo8c375fe2015-06-23 20:33:25 -070038import android.content.pm.PackageManager.NameNotFoundException;
Jae Seo39570912014-02-20 18:23:25 -080039import android.content.pm.ResolveInfo;
40import android.content.pm.ServiceInfo;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090041import android.graphics.Rect;
Wonsik Kime92f8572014-08-12 18:30:24 +090042import android.hardware.hdmi.HdmiControlManager;
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090043import android.hardware.hdmi.HdmiDeviceInfo;
Jae Seo4b34cc72015-05-15 17:29:39 -070044import android.media.PlaybackParams;
Jaesung Chung58739e72015-04-24 19:39:59 +090045import android.media.tv.DvbDeviceInfo;
Jae Seod5cc4a22014-05-30 16:57:43 -070046import android.media.tv.ITvInputClient;
47import android.media.tv.ITvInputHardware;
48import android.media.tv.ITvInputHardwareCallback;
49import android.media.tv.ITvInputManager;
Wonsik Kim969167d2014-06-24 16:33:17 +090050import android.media.tv.ITvInputManagerCallback;
Jae Seod5cc4a22014-05-30 16:57:43 -070051import android.media.tv.ITvInputService;
52import android.media.tv.ITvInputServiceCallback;
53import android.media.tv.ITvInputSession;
54import android.media.tv.ITvInputSessionCallback;
Jae Seo783645e2014-07-28 17:30:50 +090055import android.media.tv.TvContentRating;
Jae Seo9c165d62014-08-25 14:39:26 -070056import android.media.tv.TvContentRatingSystemInfo;
Jae Seod5cc4a22014-05-30 16:57:43 -070057import android.media.tv.TvContract;
58import android.media.tv.TvInputHardwareInfo;
59import android.media.tv.TvInputInfo;
Jae Seo9c165d62014-08-25 14:39:26 -070060import android.media.tv.TvInputManager;
Jae Seod5cc4a22014-05-30 16:57:43 -070061import android.media.tv.TvInputService;
Terry Heoc086a3d2014-06-18 14:26:44 +090062import android.media.tv.TvStreamConfig;
Dongwon Kang1f213912014-07-02 18:35:08 +090063import android.media.tv.TvTrackInfo;
Jae Seo39570912014-02-20 18:23:25 -080064import android.net.Uri;
65import android.os.Binder;
Youngsang Cho832860f2014-05-21 20:54:03 +090066import android.os.Bundle;
Jae Seo31dc634be2014-04-15 17:40:23 -070067import android.os.Handler;
Jae Seo39570912014-02-20 18:23:25 -080068import android.os.IBinder;
Jae Seo31dc634be2014-04-15 17:40:23 -070069import android.os.Looper;
70import android.os.Message;
Jaesung Chung58739e72015-04-24 19:39:59 +090071import android.os.ParcelFileDescriptor;
Jae Seo39570912014-02-20 18:23:25 -080072import android.os.Process;
73import android.os.RemoteException;
74import android.os.UserHandle;
Jae Seoc2a89512016-01-28 10:38:11 -080075import android.text.TextUtils;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090076import android.util.Slog;
Jae Seo39570912014-02-20 18:23:25 -080077import android.util.SparseArray;
Jae Seo6a6059a2014-04-17 21:35:29 -070078import android.view.InputChannel;
Jae Seo39570912014-02-20 18:23:25 -080079import android.view.Surface;
80
81import com.android.internal.content.PackageMonitor;
Jae Seo31dc634be2014-04-15 17:40:23 -070082import com.android.internal.os.SomeArgs;
Jaewan Kime14c3f42014-06-27 13:47:48 +090083import com.android.internal.util.IndentingPrintWriter;
Jae Seo31dc634be2014-04-15 17:40:23 -070084import com.android.server.IoThread;
Jae Seo39570912014-02-20 18:23:25 -080085import com.android.server.SystemService;
86
Chulwoo Leee7bb7d62014-05-27 14:10:37 +090087import org.xmlpull.v1.XmlPullParserException;
88
Jaesung Chung58739e72015-04-24 19:39:59 +090089import java.io.File;
Jaewan Kime14c3f42014-06-27 13:47:48 +090090import java.io.FileDescriptor;
Jaesung Chung58739e72015-04-24 19:39:59 +090091import java.io.FileNotFoundException;
Chulwoo Leee7bb7d62014-05-27 14:10:37 +090092import java.io.IOException;
Jaewan Kime14c3f42014-06-27 13:47:48 +090093import java.io.PrintWriter;
Jae Seo39570912014-02-20 18:23:25 -080094import java.util.ArrayList;
Chulwoo Lee19ba61a2014-09-03 00:59:35 +090095import java.util.Arrays;
Jaesung Chung58739e72015-04-24 19:39:59 +090096import java.util.Collections;
Jae Seo39570912014-02-20 18:23:25 -080097import java.util.HashMap;
Jae Seo5c80ad22014-06-12 19:52:58 -070098import java.util.HashSet;
Wonsik Kim187423c2014-06-25 14:12:48 +090099import java.util.Iterator;
Jae Seo39570912014-02-20 18:23:25 -0800100import java.util.List;
101import java.util.Map;
Jae Seo5c80ad22014-06-12 19:52:58 -0700102import java.util.Set;
Jaesung Chung58739e72015-04-24 19:39:59 +0900103import java.util.regex.Matcher;
104import java.util.regex.Pattern;
Jae Seo39570912014-02-20 18:23:25 -0800105
106/** This class provides a system service that manages television inputs. */
107public final class TvInputManagerService extends SystemService {
Jae Seoee2ec052014-09-14 10:30:05 -0700108 private static final boolean DEBUG = false;
Jae Seo39570912014-02-20 18:23:25 -0800109 private static final String TAG = "TvInputManagerService";
110
Jaesung Chung58739e72015-04-24 19:39:59 +0900111 // Pattern for selecting the DVB frontend devices from the list of files in the /dev directory.
112 private static final Pattern sFrontEndDevicePattern =
113 Pattern.compile("^dvb([0-9]+)\\.frontend([0-9]+)$");
114
Jae Seo39570912014-02-20 18:23:25 -0800115 private final Context mContext;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000116 private final TvInputHardwareManager mTvInputHardwareManager;
Jae Seo39570912014-02-20 18:23:25 -0800117
118 // A global lock.
119 private final Object mLock = new Object();
120
121 // ID of the current user.
Xiaohui Chen233d94c2015-07-30 15:08:00 -0700122 private int mCurrentUserId = UserHandle.USER_SYSTEM;
Jae Seo39570912014-02-20 18:23:25 -0800123
124 // A map from user id to UserState.
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700125 private final SparseArray<UserState> mUserStates = new SparseArray<>();
Jae Seo39570912014-02-20 18:23:25 -0800126
Jae Seo7eb75df2014-08-08 22:20:48 -0700127 private final WatchLogHandler mWatchLogHandler;
Jae Seo31dc634be2014-04-15 17:40:23 -0700128
Jae Seo39570912014-02-20 18:23:25 -0800129 public TvInputManagerService(Context context) {
130 super(context);
Jae Seo31dc634be2014-04-15 17:40:23 -0700131
Jae Seo39570912014-02-20 18:23:25 -0800132 mContext = context;
Jae Seo8c375fe2015-06-23 20:33:25 -0700133 mWatchLogHandler = new WatchLogHandler(mContext.getContentResolver(),
134 IoThread.get().getLooper());
Wonsik Kim187423c2014-06-25 14:12:48 +0900135 mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
Jae Seo31dc634be2014-04-15 17:40:23 -0700136
Jae Seo39570912014-02-20 18:23:25 -0800137 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700138 getOrCreateUserStateLocked(mCurrentUserId);
Jae Seo39570912014-02-20 18:23:25 -0800139 }
140 }
141
142 @Override
143 public void onStart() {
144 publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());
145 }
146
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900147 @Override
148 public void onBootPhase(int phase) {
149 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
150 registerBroadcastReceivers();
Wonsik Kim187423c2014-06-25 14:12:48 +0900151 } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900152 synchronized (mLock) {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900153 buildTvInputListLocked(mCurrentUserId, null);
Jae Seo9c165d62014-08-25 14:39:26 -0700154 buildTvContentRatingSystemListLocked(mCurrentUserId);
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900155 }
156 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900157 mTvInputHardwareManager.onBootPhase(phase);
Ji-Hwan Lee0ceb7e42014-06-21 04:42:05 +0900158 }
159
Jae Seo39570912014-02-20 18:23:25 -0800160 private void registerBroadcastReceivers() {
161 PackageMonitor monitor = new PackageMonitor() {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900162 private void buildTvInputList(String[] packages) {
163 synchronized (mLock) {
164 buildTvInputListLocked(getChangingUserId(), packages);
165 buildTvContentRatingSystemListLocked(getChangingUserId());
166 }
167 }
168
169 @Override
170 public void onPackageUpdateFinished(String packageName, int uid) {
171 if (DEBUG) Slog.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")");
172 // This callback is invoked when the TV input is reinstalled.
173 // In this case, isReplacing() always returns true.
174 buildTvInputList(new String[] { packageName });
175 }
176
177 @Override
178 public void onPackagesAvailable(String[] packages) {
179 if (DEBUG) {
180 Slog.d(TAG, "onPackagesAvailable(packages=" + Arrays.toString(packages) + ")");
181 }
182 // This callback is invoked when the media on which some packages exist become
183 // available.
184 if (isReplacing()) {
185 buildTvInputList(packages);
186 }
187 }
188
189 @Override
190 public void onPackagesUnavailable(String[] packages) {
191 // This callback is invoked when the media on which some packages exist become
192 // unavailable.
193 if (DEBUG) {
194 Slog.d(TAG, "onPackagesUnavailable(packages=" + Arrays.toString(packages)
195 + ")");
196 }
197 if (isReplacing()) {
198 buildTvInputList(packages);
199 }
200 }
201
Jae Seo39570912014-02-20 18:23:25 -0800202 @Override
203 public void onSomePackagesChanged() {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900204 // TODO: Use finer-grained methods(e.g. onPackageAdded, onPackageRemoved) to manage
205 // the TV inputs.
Dongwon Kang426c9a42014-08-26 17:39:21 +0900206 if (DEBUG) Slog.d(TAG, "onSomePackagesChanged()");
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900207 if (isReplacing()) {
208 if (DEBUG) Slog.d(TAG, "Skipped building TV input list due to replacing");
209 // When the package is updated, buildTvInputListLocked is called in other
210 // methods instead.
211 return;
Jae Seo39570912014-02-20 18:23:25 -0800212 }
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900213 buildTvInputList(null);
Jae Seo39570912014-02-20 18:23:25 -0800214 }
Jae Seo5c80ad22014-06-12 19:52:58 -0700215
216 @Override
Dongwon Kang31a8f842015-04-08 18:26:23 -0700217 public boolean onPackageChanged(String packageName, int uid, String[] components) {
218 // The input list needs to be updated in any cases, regardless of whether
219 // it happened to the whole package or a specific component. Returning true so that
220 // the update can be handled in {@link #onSomePackagesChanged}.
221 return true;
222 }
223
224 @Override
Jae Seo5c80ad22014-06-12 19:52:58 -0700225 public void onPackageRemoved(String packageName, int uid) {
226 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700227 UserState userState = getOrCreateUserStateLocked(getChangingUserId());
Wonsik Kim969167d2014-06-24 16:33:17 +0900228 if (!userState.packageSet.contains(packageName)) {
Jae Seo5c80ad22014-06-12 19:52:58 -0700229 // Not a TV input package.
230 return;
231 }
232 }
233
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700234 ArrayList<ContentProviderOperation> operations = new ArrayList<>();
Jae Seo5c80ad22014-06-12 19:52:58 -0700235
236 String selection = TvContract.BaseTvColumns.COLUMN_PACKAGE_NAME + "=?";
237 String[] selectionArgs = { packageName };
238
239 operations.add(ContentProviderOperation.newDelete(TvContract.Channels.CONTENT_URI)
240 .withSelection(selection, selectionArgs).build());
241 operations.add(ContentProviderOperation.newDelete(TvContract.Programs.CONTENT_URI)
242 .withSelection(selection, selectionArgs).build());
243 operations.add(ContentProviderOperation
244 .newDelete(TvContract.WatchedPrograms.CONTENT_URI)
245 .withSelection(selection, selectionArgs).build());
246
247 ContentProviderResult[] results = null;
248 try {
Jae Seo8c375fe2015-06-23 20:33:25 -0700249 ContentResolver cr = getContentResolverForUser(getChangingUserId());
250 results = cr.applyBatch(TvContract.AUTHORITY, operations);
Jae Seo5c80ad22014-06-12 19:52:58 -0700251 } catch (RemoteException | OperationApplicationException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700252 Slog.e(TAG, "error in applyBatch", e);
Jae Seo5c80ad22014-06-12 19:52:58 -0700253 }
254
255 if (DEBUG) {
256 Slog.d(TAG, "onPackageRemoved(packageName=" + packageName + ", uid=" + uid
257 + ")");
258 Slog.d(TAG, "results=" + results);
259 }
260 }
Jae Seo39570912014-02-20 18:23:25 -0800261 };
262 monitor.register(mContext, null, UserHandle.ALL, true);
263
264 IntentFilter intentFilter = new IntentFilter();
265 intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
266 intentFilter.addAction(Intent.ACTION_USER_REMOVED);
267 mContext.registerReceiverAsUser(new BroadcastReceiver() {
268 @Override
269 public void onReceive(Context context, Intent intent) {
270 String action = intent.getAction();
271 if (Intent.ACTION_USER_SWITCHED.equals(action)) {
272 switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
273 } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
274 removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
275 }
276 }
277 }, UserHandle.ALL, intentFilter, null, null);
278 }
279
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900280 private static boolean hasHardwarePermission(PackageManager pm, ComponentName component) {
Wonsik Kim187423c2014-06-25 14:12:48 +0900281 return pm.checkPermission(android.Manifest.permission.TV_INPUT_HARDWARE,
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900282 component.getPackageName()) == PackageManager.PERMISSION_GRANTED;
Wonsik Kim187423c2014-06-25 14:12:48 +0900283 }
284
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900285 private void buildTvInputListLocked(int userId, String[] updatedPackages) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700286 UserState userState = getOrCreateUserStateLocked(userId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900287 userState.packageSet.clear();
Jae Seo39570912014-02-20 18:23:25 -0800288
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900289 if (DEBUG) Slog.d(TAG, "buildTvInputList");
Jae Seo39570912014-02-20 18:23:25 -0800290 PackageManager pm = mContext.getPackageManager();
Jae Seo76976fa2015-05-20 21:51:16 -0700291 List<ResolveInfo> services = pm.queryIntentServicesAsUser(
Chulwoo Leee7bb7d62014-05-27 14:10:37 +0900292 new Intent(TvInputService.SERVICE_INTERFACE),
Jae Seo76976fa2015-05-20 21:51:16 -0700293 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
294 userId);
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700295 List<TvInputInfo> inputList = new ArrayList<>();
Jae Seo39570912014-02-20 18:23:25 -0800296 for (ResolveInfo ri : services) {
297 ServiceInfo si = ri.serviceInfo;
298 if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900299 Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
Jae Seo39570912014-02-20 18:23:25 -0800300 + android.Manifest.permission.BIND_TV_INPUT);
301 continue;
302 }
Jae Seo9cc28e52014-08-12 16:45:58 -0700303
304 ComponentName component = new ComponentName(si.packageName, si.name);
305 if (hasHardwarePermission(pm, component)) {
306 ServiceState serviceState = userState.serviceStateMap.get(component);
307 if (serviceState == null) {
Jae Seoc980f43d2016-02-09 23:46:58 -0800308 // New hardware input found. Create a new ServiceState and connect to the
309 // service to populate the hardware list.
Jae Seo9cc28e52014-08-12 16:45:58 -0700310 serviceState = new ServiceState(component, userId);
311 userState.serviceStateMap.put(component, serviceState);
Wonsik Kimf271eac2014-08-30 12:55:10 +0900312 updateServiceConnectionLocked(component, userId);
Wonsik Kim187423c2014-06-25 14:12:48 +0900313 } else {
Jae Seo1abbbcd2016-01-28 22:20:41 -0800314 inputList.addAll(serviceState.hardwareInputList);
Jae Seo9cc28e52014-08-12 16:45:58 -0700315 }
316 } else {
317 try {
Jae Seoc03671f2016-01-26 15:15:22 -0800318 TvInputInfo info = new TvInputInfo.Builder(mContext, ri).build();
319 inputList.add(info);
Jae Seo9cc28e52014-08-12 16:45:58 -0700320 } catch (XmlPullParserException | IOException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700321 Slog.e(TAG, "failed to load TV input " + si.name, e);
Jae Seo9cc28e52014-08-12 16:45:58 -0700322 continue;
Wonsik Kim969167d2014-06-24 16:33:17 +0900323 }
Chulwoo Leee7bb7d62014-05-27 14:10:37 +0900324 }
Jae Seo9cc28e52014-08-12 16:45:58 -0700325 userState.packageSet.add(si.packageName);
326 }
327
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700328 Map<String, TvInputState> inputMap = new HashMap<>();
Jae Seo9cc28e52014-08-12 16:45:58 -0700329 for (TvInputInfo info : inputList) {
Jae Seofea8dd42014-08-26 13:57:41 -0700330 if (DEBUG) {
331 Slog.d(TAG, "add " + info.getId());
332 }
Jae Seoabda4202016-01-28 19:13:04 -0800333 TvInputState inputState = userState.inputMap.get(info.getId());
334 if (inputState == null) {
335 inputState = new TvInputState();
336 inputState.info = info;
Jae Seo9cc28e52014-08-12 16:45:58 -0700337 }
Jae Seoabda4202016-01-28 19:13:04 -0800338 inputMap.put(info.getId(), inputState);
Jae Seo39570912014-02-20 18:23:25 -0800339 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900340
341 for (String inputId : inputMap.keySet()) {
342 if (!userState.inputMap.containsKey(inputId)) {
343 notifyInputAddedLocked(userState, inputId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900344 } else if (updatedPackages != null) {
345 // Notify the package updates
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +0900346 ComponentName component = inputMap.get(inputId).info.getComponent();
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900347 for (String updatedPackage : updatedPackages) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +0900348 if (component.getPackageName().equals(updatedPackage)) {
349 updateServiceConnectionLocked(component, userId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900350 notifyInputUpdatedLocked(userState, inputId);
351 break;
352 }
353 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900354 }
355 }
356
357 for (String inputId : userState.inputMap.keySet()) {
358 if (!inputMap.containsKey(inputId)) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900359 TvInputInfo info = userState.inputMap.get(inputId).info;
Dongwon Kang426c9a42014-08-26 17:39:21 +0900360 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
361 if (serviceState != null) {
362 abortPendingCreateSessionRequestsLocked(serviceState, inputId, userId);
363 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900364 notifyInputRemovedLocked(userState, inputId);
365 }
366 }
367
368 userState.inputMap.clear();
369 userState.inputMap = inputMap;
Jae Seo9c165d62014-08-25 14:39:26 -0700370 }
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900371
Jae Seo9c165d62014-08-25 14:39:26 -0700372 private void buildTvContentRatingSystemListLocked(int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700373 UserState userState = getOrCreateUserStateLocked(userId);
Jae Seo9c165d62014-08-25 14:39:26 -0700374 userState.contentRatingSystemList.clear();
375
376 final PackageManager pm = mContext.getPackageManager();
377 Intent intent = new Intent(TvInputManager.ACTION_QUERY_CONTENT_RATING_SYSTEMS);
378 for (ResolveInfo resolveInfo :
379 pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA)) {
380 ActivityInfo receiver = resolveInfo.activityInfo;
381 Bundle metaData = receiver.metaData;
382 if (metaData == null) {
383 continue;
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900384 }
Jae Seo9c165d62014-08-25 14:39:26 -0700385
386 int xmlResId = metaData.getInt(TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS);
387 if (xmlResId == 0) {
388 Slog.w(TAG, "Missing meta-data '"
389 + TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS + "' on receiver "
390 + receiver.packageName + "/" + receiver.name);
391 continue;
392 }
393 userState.contentRatingSystemList.add(
394 TvContentRatingSystemInfo.createTvContentRatingSystemInfo(xmlResId,
395 receiver.applicationInfo));
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900396 }
Jae Seo39570912014-02-20 18:23:25 -0800397 }
398
399 private void switchUser(int userId) {
400 synchronized (mLock) {
401 if (mCurrentUserId == userId) {
402 return;
403 }
Jae Seo8c375fe2015-06-23 20:33:25 -0700404 clearSessionAndServiceStatesLocked(mUserStates.get(mCurrentUserId));
Jae Seo39570912014-02-20 18:23:25 -0800405
Jae Seo8c375fe2015-06-23 20:33:25 -0700406 mCurrentUserId = userId;
Jae Seo4f1a6d42015-07-20 16:15:01 -0700407 getOrCreateUserStateLocked(userId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900408 buildTvInputListLocked(userId, null);
Jae Seo9c165d62014-08-25 14:39:26 -0700409 buildTvContentRatingSystemListLocked(userId);
Jae Seo8c375fe2015-06-23 20:33:25 -0700410 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_SWITCH_CONTENT_RESOLVER,
411 getContentResolverForUser(userId)).sendToTarget();
Jae Seo39570912014-02-20 18:23:25 -0800412 }
413 }
414
415 private void removeUser(int userId) {
416 synchronized (mLock) {
Jae Seob06cb882014-04-09 12:08:17 -0700417 UserState userState = mUserStates.get(userId);
418 if (userState == null) {
419 return;
420 }
Jae Seo8c375fe2015-06-23 20:33:25 -0700421 clearSessionAndServiceStatesLocked(userState);
Jae Seo39570912014-02-20 18:23:25 -0800422
Jae Seofea8dd42014-08-26 13:57:41 -0700423 // Clear everything else.
424 userState.inputMap.clear();
425 userState.packageSet.clear();
Jae Seo9c165d62014-08-25 14:39:26 -0700426 userState.contentRatingSystemList.clear();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900427 userState.clientStateMap.clear();
Jae Seofea8dd42014-08-26 13:57:41 -0700428 userState.callbackSet.clear();
429 userState.mainSessionToken = null;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900430
Jae Seo39570912014-02-20 18:23:25 -0800431 mUserStates.remove(userId);
432 }
433 }
434
Jae Seo8c375fe2015-06-23 20:33:25 -0700435 private void clearSessionAndServiceStatesLocked(UserState userState) {
436 // Release created sessions.
437 for (SessionState state : userState.sessionStateMap.values()) {
438 if (state.session != null) {
439 try {
Jae Seoe3c11e82016-02-08 23:18:49 -0800440 state.session.release();
Jae Seo8c375fe2015-06-23 20:33:25 -0700441 } catch (RemoteException e) {
442 Slog.e(TAG, "error in release", e);
443 }
444 }
445 }
446 userState.sessionStateMap.clear();
447
448 // Unregister all callbacks and unbind all services.
449 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kang81e3c3e2015-09-11 15:24:25 -0700450 if (serviceState.service != null) {
451 if (serviceState.callback != null) {
452 try {
453 serviceState.service.unregisterCallback(serviceState.callback);
454 } catch (RemoteException e) {
455 Slog.e(TAG, "error in unregisterCallback", e);
456 }
Jae Seo8c375fe2015-06-23 20:33:25 -0700457 }
Dongwon Kang81e3c3e2015-09-11 15:24:25 -0700458 mContext.unbindService(serviceState.connection);
Jae Seo8c375fe2015-06-23 20:33:25 -0700459 }
Jae Seo8c375fe2015-06-23 20:33:25 -0700460 }
461 userState.serviceStateMap.clear();
462 }
463
464 private ContentResolver getContentResolverForUser(int userId) {
465 UserHandle user = new UserHandle(userId);
466 Context context;
467 try {
468 context = mContext.createPackageContextAsUser("android", 0, user);
469 } catch (NameNotFoundException e) {
Jae Seo2a2b2992016-01-12 23:13:14 -0800470 Slog.e(TAG, "failed to create package context as user " + user);
Jae Seo8c375fe2015-06-23 20:33:25 -0700471 context = mContext;
472 }
473 return context.getContentResolver();
474 }
475
Jae Seo4f1a6d42015-07-20 16:15:01 -0700476 private UserState getOrCreateUserStateLocked(int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800477 UserState userState = mUserStates.get(userId);
478 if (userState == null) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700479 userState = new UserState(mContext, userId);
480 mUserStates.put(userId, userState);
Jae Seo39570912014-02-20 18:23:25 -0800481 }
482 return userState;
483 }
484
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900485 private ServiceState getServiceStateLocked(ComponentName component, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700486 UserState userState = getOrCreateUserStateLocked(userId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900487 ServiceState serviceState = userState.serviceStateMap.get(component);
Jae Seo39570912014-02-20 18:23:25 -0800488 if (serviceState == null) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900489 throw new IllegalStateException("Service state not found for " + component + " (userId="
Sungsoo Lim7de5e232014-04-12 16:51:27 +0900490 + userId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800491 }
492 return serviceState;
493 }
494
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900495 private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700496 UserState userState = getOrCreateUserStateLocked(userId);
Jae Seo39570912014-02-20 18:23:25 -0800497 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
498 if (sessionState == null) {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900499 throw new SessionNotFoundException("Session state not found for token " + sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800500 }
501 // Only the application that requested this session or the system can access it.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900502 if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) {
Jae Seo39570912014-02-20 18:23:25 -0800503 throw new SecurityException("Illegal access to the session with token " + sessionToken
504 + " from uid " + callingUid);
505 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900506 return sessionState;
507 }
508
509 private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900510 return getSessionLocked(getSessionStateLocked(sessionToken, callingUid, userId));
511 }
512
513 private ITvInputSession getSessionLocked(SessionState sessionState) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900514 ITvInputSession session = sessionState.session;
Jae Seo39570912014-02-20 18:23:25 -0800515 if (session == null) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900516 throw new IllegalStateException("Session not yet created for token "
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900517 + sessionState.sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800518 }
519 return session;
520 }
521
522 private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
523 String methodName) {
524 return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
525 false, methodName, null);
526 }
527
Wonsik Kim187423c2014-06-25 14:12:48 +0900528 private static boolean shouldMaintainConnection(ServiceState serviceState) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900529 return !serviceState.sessionTokens.isEmpty() || serviceState.isHardware;
530 // TODO: Find a way to maintain connection to hardware TV input service only when necessary.
Wonsik Kim187423c2014-06-25 14:12:48 +0900531 }
532
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900533 private void updateServiceConnectionLocked(ComponentName component, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700534 UserState userState = getOrCreateUserStateLocked(userId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900535 ServiceState serviceState = userState.serviceStateMap.get(component);
Jae Seo39570912014-02-20 18:23:25 -0800536 if (serviceState == null) {
537 return;
538 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900539 if (serviceState.reconnecting) {
540 if (!serviceState.sessionTokens.isEmpty()) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900541 // wait until all the sessions are removed.
542 return;
543 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900544 serviceState.reconnecting = false;
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900545 }
Wonsik Kim187423c2014-06-25 14:12:48 +0900546 boolean maintainConnection = shouldMaintainConnection(serviceState);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900547 if (serviceState.service == null && maintainConnection && userId == mCurrentUserId) {
Jae Seo39570912014-02-20 18:23:25 -0800548 // This means that the service is not yet connected but its state indicates that we
549 // have pending requests. Then, connect the service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900550 if (serviceState.bound) {
Jae Seo39570912014-02-20 18:23:25 -0800551 // We have already bound to the service so we don't try to bind again until after we
552 // unbind later on.
553 return;
554 }
555 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900556 Slog.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800557 }
Sungsoo Limd6672b52014-04-30 10:43:26 +0900558
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900559 Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900560 serviceState.bound = mContext.bindServiceAsUser(
Dianne Hackbornd69e4c12015-04-24 09:54:54 -0700561 i, serviceState.connection,
562 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
563 new UserHandle(userId));
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900564 } else if (serviceState.service != null && !maintainConnection) {
Jae Seo39570912014-02-20 18:23:25 -0800565 // This means that the service is already connected but its state indicates that we have
566 // nothing to do with it. Then, disconnect the service.
567 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900568 Slog.d(TAG, "unbindService(service=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -0800569 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900570 mContext.unbindService(serviceState.connection);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900571 userState.serviceStateMap.remove(component);
Jae Seo39570912014-02-20 18:23:25 -0800572 }
573 }
574
Dongwon Kang426c9a42014-08-26 17:39:21 +0900575 private void abortPendingCreateSessionRequestsLocked(ServiceState serviceState,
576 String inputId, int userId) {
577 // Let clients know the create session requests are failed.
Jae Seo4f1a6d42015-07-20 16:15:01 -0700578 UserState userState = getOrCreateUserStateLocked(userId);
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900579 List<SessionState> sessionsToAbort = new ArrayList<>();
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900580 for (IBinder sessionToken : serviceState.sessionTokens) {
Dongwon Kang426c9a42014-08-26 17:39:21 +0900581 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900582 if (sessionState.session == null && (inputId == null
Jae Seo2cdb05e2016-02-04 22:17:13 +0900583 || sessionState.inputId.equals(inputId))) {
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900584 sessionsToAbort.add(sessionState);
Dongwon Kang426c9a42014-08-26 17:39:21 +0900585 }
586 }
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900587 for (SessionState sessionState : sessionsToAbort) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900588 removeSessionStateLocked(sessionState.sessionToken, sessionState.userId);
589 sendSessionTokenToClientLocked(sessionState.client,
Jae Seo2cdb05e2016-02-04 22:17:13 +0900590 sessionState.inputId, null, null, sessionState.seq);
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900591 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900592 updateServiceConnectionLocked(serviceState.component, userId);
Dongwon Kang426c9a42014-08-26 17:39:21 +0900593 }
594
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900595 private void createSessionInternalLocked(ITvInputService service, IBinder sessionToken,
596 int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700597 UserState userState = getOrCreateUserStateLocked(userId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900598 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800599 if (DEBUG) {
Jae Seo2cdb05e2016-02-04 22:17:13 +0900600 Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.inputId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800601 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900602 InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
Jae Seo6a6059a2014-04-17 21:35:29 -0700603
Jae Seo39570912014-02-20 18:23:25 -0800604 // Set up a callback to send the session token.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900605 ITvInputSessionCallback callback = new SessionCallback(sessionState, channels);
Jae Seo39570912014-02-20 18:23:25 -0800606
607 // Create a session. When failed, send a null token immediately.
608 try {
Jae Seoa826d012016-01-18 13:03:35 -0800609 if (sessionState.isRecordingSession) {
Jae Seo2cdb05e2016-02-04 22:17:13 +0900610 service.createRecordingSession(callback, sessionState.inputId);
Jae Seoa826d012016-01-18 13:03:35 -0800611 } else {
Jae Seo2cdb05e2016-02-04 22:17:13 +0900612 service.createSession(channels[1], callback, sessionState.inputId);
Jae Seoa826d012016-01-18 13:03:35 -0800613 }
Jae Seo39570912014-02-20 18:23:25 -0800614 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900615 Slog.e(TAG, "error in createSession", e);
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900616 removeSessionStateLocked(sessionToken, userId);
Jae Seo2cdb05e2016-02-04 22:17:13 +0900617 sendSessionTokenToClientLocked(sessionState.client, sessionState.inputId, null,
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900618 null, sessionState.seq);
Jae Seo39570912014-02-20 18:23:25 -0800619 }
Jae Seo6a6059a2014-04-17 21:35:29 -0700620 channels[1].dispose();
Jae Seo39570912014-02-20 18:23:25 -0800621 }
622
Sungsoo Limd6672b52014-04-30 10:43:26 +0900623 private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId,
Jae Seo5c80ad22014-06-12 19:52:58 -0700624 IBinder sessionToken, InputChannel channel, int seq) {
Jae Seo39570912014-02-20 18:23:25 -0800625 try {
Sungsoo Limd6672b52014-04-30 10:43:26 +0900626 client.onSessionCreated(inputId, sessionToken, channel, seq);
Jae Seofea8dd42014-08-26 13:57:41 -0700627 } catch (RemoteException e) {
628 Slog.e(TAG, "error in onSessionCreated", e);
Jae Seo39570912014-02-20 18:23:25 -0800629 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900630 }
Jae Seo39570912014-02-20 18:23:25 -0800631
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900632 private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900633 SessionState sessionState = null;
634 try {
635 sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
636 if (sessionState.session != null) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700637 UserState userState = getOrCreateUserStateLocked(userId);
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900638 if (sessionToken == userState.mainSessionToken) {
639 setMainLocked(sessionToken, false, callingUid, userId);
640 }
Jae Seoe3c11e82016-02-08 23:18:49 -0800641 sessionState.session.release();
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900642 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900643 } catch (RemoteException | SessionNotFoundException e) {
644 Slog.e(TAG, "error in releaseSession", e);
645 } finally {
646 if (sessionState != null) {
647 sessionState.session = null;
648 }
Jae Seo39570912014-02-20 18:23:25 -0800649 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900650 removeSessionStateLocked(sessionToken, userId);
Jae Seo39570912014-02-20 18:23:25 -0800651 }
652
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900653 private void removeSessionStateLocked(IBinder sessionToken, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700654 UserState userState = getOrCreateUserStateLocked(userId);
Ji-Hwan Leeabca0ee2014-07-24 17:34:19 +0900655 if (sessionToken == userState.mainSessionToken) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900656 if (DEBUG) {
657 Slog.d(TAG, "mainSessionToken=null");
658 }
Ji-Hwan Leeabca0ee2014-07-24 17:34:19 +0900659 userState.mainSessionToken = null;
660 }
661
662 // Remove the session state from the global session state map of the current user.
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900663 SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
664
Chulwoo Lee8d4ded02014-07-10 03:56:39 +0900665 if (sessionState == null) {
666 return;
667 }
668
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900669 // Also remove the session token from the session token list of the current client and
670 // service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900671 ClientState clientState = userState.clientStateMap.get(sessionState.client.asBinder());
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900672 if (clientState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900673 clientState.sessionTokens.remove(sessionToken);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900674 if (clientState.isEmpty()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900675 userState.clientStateMap.remove(sessionState.client.asBinder());
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900676 }
677 }
678
Jae Seo2cdb05e2016-02-04 22:17:13 +0900679 ServiceState serviceState = userState.serviceStateMap.get(sessionState.componentName);
680 if (serviceState != null) {
681 serviceState.sessionTokens.remove(sessionToken);
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900682 }
Jae Seo2cdb05e2016-02-04 22:17:13 +0900683 updateServiceConnectionLocked(sessionState.componentName, userId);
Jae Seo7eb75df2014-08-08 22:20:48 -0700684
685 // Log the end of watch.
686 SomeArgs args = SomeArgs.obtain();
687 args.arg1 = sessionToken;
688 args.arg2 = System.currentTimeMillis();
689 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_END, args).sendToTarget();
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900690 }
691
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900692 private void setMainLocked(IBinder sessionToken, boolean isMain, int callingUid, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900693 try {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900694 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
695 if (sessionState.hardwareSessionToken != null) {
696 sessionState = getSessionStateLocked(sessionState.hardwareSessionToken,
697 Process.SYSTEM_UID, userId);
698 }
Jae Seo2cdb05e2016-02-04 22:17:13 +0900699 ServiceState serviceState = getServiceStateLocked(sessionState.componentName, userId);
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900700 if (!serviceState.isHardware) {
701 return;
702 }
703 ITvInputSession session = getSessionLocked(sessionState);
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900704 session.setMain(isMain);
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900705 } catch (RemoteException | SessionNotFoundException e) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900706 Slog.e(TAG, "error in setMain", e);
707 }
708 }
709
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900710 private void notifyInputAddedLocked(UserState userState, String inputId) {
711 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700712 Slog.d(TAG, "notifyInputAddedLocked(inputId=" + inputId + ")");
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900713 }
714 for (ITvInputManagerCallback callback : userState.callbackSet) {
715 try {
716 callback.onInputAdded(inputId);
717 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700718 Slog.e(TAG, "failed to report added input to callback", e);
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900719 }
720 }
721 }
722
723 private void notifyInputRemovedLocked(UserState userState, String inputId) {
724 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700725 Slog.d(TAG, "notifyInputRemovedLocked(inputId=" + inputId + ")");
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900726 }
727 for (ITvInputManagerCallback callback : userState.callbackSet) {
728 try {
729 callback.onInputRemoved(inputId);
730 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700731 Slog.e(TAG, "failed to report removed input to callback", e);
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900732 }
733 }
734 }
735
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900736 private void notifyInputUpdatedLocked(UserState userState, String inputId) {
737 if (DEBUG) {
738 Slog.d(TAG, "notifyInputUpdatedLocked(inputId=" + inputId + ")");
739 }
740 for (ITvInputManagerCallback callback : userState.callbackSet) {
741 try {
742 callback.onInputUpdated(inputId);
743 } catch (RemoteException e) {
744 Slog.e(TAG, "failed to report updated input to callback", e);
745 }
746 }
747 }
748
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900749 private void notifyInputStateChangedLocked(UserState userState, String inputId,
Wonsik Kim969167d2014-06-24 16:33:17 +0900750 int state, ITvInputManagerCallback targetCallback) {
751 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700752 Slog.d(TAG, "notifyInputStateChangedLocked(inputId=" + inputId
753 + ", state=" + state + ")");
Wonsik Kim969167d2014-06-24 16:33:17 +0900754 }
755 if (targetCallback == null) {
756 for (ITvInputManagerCallback callback : userState.callbackSet) {
757 try {
758 callback.onInputStateChanged(inputId, state);
759 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700760 Slog.e(TAG, "failed to report state change to callback", e);
Wonsik Kim969167d2014-06-24 16:33:17 +0900761 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900762 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900763 } else {
764 try {
765 targetCallback.onInputStateChanged(inputId, state);
766 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700767 Slog.e(TAG, "failed to report state change to callback", e);
Wonsik Kim969167d2014-06-24 16:33:17 +0900768 }
769 }
770 }
771
Jae Seoaa5605f2016-02-13 01:38:08 -0800772 private void updateTvInputInfoLocked(UserState userState, TvInputInfo inputInfo) {
Jae Seoa826d012016-01-18 13:03:35 -0800773 if (DEBUG) {
Jae Seoaa5605f2016-02-13 01:38:08 -0800774 Slog.d(TAG, "updateTvInputInfoLocked(inputInfo=" + inputInfo + ")");
Jae Seoa826d012016-01-18 13:03:35 -0800775 }
Jae Seoabda4202016-01-28 19:13:04 -0800776 String inputId = inputInfo.getId();
777 TvInputState inputState = userState.inputMap.get(inputId);
778 if (inputState == null) {
779 Slog.e(TAG, "failed to set input info - unknown input id " + inputId);
780 return;
781 }
Jae Seoabda4202016-01-28 19:13:04 -0800782 inputState.info = inputInfo;
783
Jae Seoa826d012016-01-18 13:03:35 -0800784 for (ITvInputManagerCallback callback : userState.callbackSet) {
785 try {
Jae Seoaa5605f2016-02-13 01:38:08 -0800786 callback.onTvInputInfoUpdated(inputInfo);
Jae Seoa826d012016-01-18 13:03:35 -0800787 } catch (RemoteException e) {
Jae Seoaa5605f2016-02-13 01:38:08 -0800788 Slog.e(TAG, "failed to report updated input info to callback", e);
Jae Seoa826d012016-01-18 13:03:35 -0800789 }
790 }
791 }
792
Wonsik Kim969167d2014-06-24 16:33:17 +0900793 private void setStateLocked(String inputId, int state, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700794 UserState userState = getOrCreateUserStateLocked(userId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900795 TvInputState inputState = userState.inputMap.get(inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900796 ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent());
797 int oldState = inputState.state;
798 inputState.state = state;
799 if (serviceState != null && serviceState.service == null
Wonsik Kim187423c2014-06-25 14:12:48 +0900800 && shouldMaintainConnection(serviceState)) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900801 // We don't notify state change while reconnecting. It should remain disconnected.
802 return;
803 }
804 if (oldState != state) {
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900805 notifyInputStateChangedLocked(userState, inputId, state, null);
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900806 }
807 }
808
Jae Seo39570912014-02-20 18:23:25 -0800809 private final class BinderService extends ITvInputManager.Stub {
810 @Override
811 public List<TvInputInfo> getTvInputList(int userId) {
812 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
813 Binder.getCallingUid(), userId, "getTvInputList");
814 final long identity = Binder.clearCallingIdentity();
815 try {
816 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700817 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700818 List<TvInputInfo> inputList = new ArrayList<>();
Wonsik Kim969167d2014-06-24 16:33:17 +0900819 for (TvInputState state : userState.inputMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900820 inputList.add(state.info);
Jae Seo39570912014-02-20 18:23:25 -0800821 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900822 return inputList;
Jae Seo39570912014-02-20 18:23:25 -0800823 }
824 } finally {
825 Binder.restoreCallingIdentity(identity);
826 }
Jae Seo39570912014-02-20 18:23:25 -0800827 }
828
829 @Override
Jae Seob3758052014-07-12 19:25:24 -0700830 public TvInputInfo getTvInputInfo(String inputId, int userId) {
831 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
832 Binder.getCallingUid(), userId, "getTvInputInfo");
833 final long identity = Binder.clearCallingIdentity();
834 try {
835 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700836 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seob3758052014-07-12 19:25:24 -0700837 TvInputState state = userState.inputMap.get(inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900838 return state == null ? null : state.info;
Jae Seob3758052014-07-12 19:25:24 -0700839 }
840 } finally {
841 Binder.restoreCallingIdentity(identity);
842 }
843 }
844
Jae Seoaa5605f2016-02-13 01:38:08 -0800845 public void updateTvInputInfo(TvInputInfo inputInfo, int userId) {
Jae Seoc2a89512016-01-28 10:38:11 -0800846 String inputInfoPackageName = inputInfo.getServiceInfo().packageName;
847 String callingPackageName = getCallingPackageName();
848 if (!TextUtils.equals(inputInfoPackageName, callingPackageName)) {
849 throw new IllegalArgumentException("calling package " + callingPackageName
850 + " is not allowed to change TvInputInfo for " + inputInfoPackageName);
851 }
852
853 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
Jae Seoaa5605f2016-02-13 01:38:08 -0800854 Binder.getCallingUid(), userId, "updateTvInputInfo");
Jae Seoc2a89512016-01-28 10:38:11 -0800855 final long identity = Binder.clearCallingIdentity();
856 try {
857 synchronized (mLock) {
858 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seoaa5605f2016-02-13 01:38:08 -0800859 updateTvInputInfoLocked(userState, inputInfo);
Jae Seoc2a89512016-01-28 10:38:11 -0800860 }
861 } finally {
862 Binder.restoreCallingIdentity(identity);
863 }
864 }
865
866 private String getCallingPackageName() {
867 final String[] packages = mContext.getPackageManager().getPackagesForUid(
868 Binder.getCallingUid());
869 if (packages != null && packages.length > 0) {
870 return packages[0];
871 }
872 return "unknown";
873 }
874
Jae Seob3758052014-07-12 19:25:24 -0700875 @Override
Dongwon Kang993f81e2014-11-27 19:34:18 +0900876 public int getTvInputState(String inputId, int userId) {
877 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
878 Binder.getCallingUid(), userId, "getTvInputState");
879 final long identity = Binder.clearCallingIdentity();
880 try {
881 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700882 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Dongwon Kang993f81e2014-11-27 19:34:18 +0900883 TvInputState state = userState.inputMap.get(inputId);
Jae Seo82fce642015-04-20 15:37:50 -0700884 return state == null ? INPUT_STATE_CONNECTED : state.state;
Dongwon Kang993f81e2014-11-27 19:34:18 +0900885 }
886 } finally {
887 Binder.restoreCallingIdentity(identity);
888 }
889 }
890
891 @Override
Jae Seo9c165d62014-08-25 14:39:26 -0700892 public List<TvContentRatingSystemInfo> getTvContentRatingSystemList(int userId) {
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900893 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
Jae Seo9c165d62014-08-25 14:39:26 -0700894 Binder.getCallingUid(), userId, "getTvContentRatingSystemList");
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900895 final long identity = Binder.clearCallingIdentity();
896 try {
897 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700898 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo9c165d62014-08-25 14:39:26 -0700899 return userState.contentRatingSystemList;
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900900 }
901 } finally {
902 Binder.restoreCallingIdentity(identity);
903 }
904 }
905
906 @Override
Wonsik Kim969167d2014-06-24 16:33:17 +0900907 public void registerCallback(final ITvInputManagerCallback callback, int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800908 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
909 Binder.getCallingUid(), userId, "registerCallback");
910 final long identity = Binder.clearCallingIdentity();
911 try {
912 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700913 final UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900914 userState.callbackSet.add(callback);
Jae Seofea8dd42014-08-26 13:57:41 -0700915 try {
916 callback.asBinder().linkToDeath(new IBinder.DeathRecipient() {
917 @Override
918 public void binderDied() {
919 synchronized (mLock) {
920 if (userState.callbackSet != null) {
921 userState.callbackSet.remove(callback);
922 }
923 }
924 }
925 }, 0);
926 } catch (RemoteException e) {
927 Slog.e(TAG, "client process has already died", e);
928 }
Jae Seo39570912014-02-20 18:23:25 -0800929 }
930 } finally {
931 Binder.restoreCallingIdentity(identity);
932 }
933 }
934
935 @Override
Wonsik Kim969167d2014-06-24 16:33:17 +0900936 public void unregisterCallback(ITvInputManagerCallback callback, int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800937 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
938 Binder.getCallingUid(), userId, "unregisterCallback");
939 final long identity = Binder.clearCallingIdentity();
940 try {
941 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700942 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900943 userState.callbackSet.remove(callback);
Jae Seo39570912014-02-20 18:23:25 -0800944 }
945 } finally {
946 Binder.restoreCallingIdentity(identity);
947 }
948 }
949
950 @Override
Jae Seo783645e2014-07-28 17:30:50 +0900951 public boolean isParentalControlsEnabled(int userId) {
952 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
953 Binder.getCallingUid(), userId, "isParentalControlsEnabled");
954 final long identity = Binder.clearCallingIdentity();
955 try {
956 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700957 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +0900958 return userState.persistentDataStore.isParentalControlsEnabled();
959 }
960 } finally {
961 Binder.restoreCallingIdentity(identity);
962 }
963 }
964
965 @Override
966 public void setParentalControlsEnabled(boolean enabled, int userId) {
967 ensureParentalControlsPermission();
968 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
969 Binder.getCallingUid(), userId, "setParentalControlsEnabled");
970 final long identity = Binder.clearCallingIdentity();
971 try {
972 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700973 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +0900974 userState.persistentDataStore.setParentalControlsEnabled(enabled);
975 }
976 } finally {
977 Binder.restoreCallingIdentity(identity);
978 }
979 }
980
981 @Override
982 public boolean isRatingBlocked(String rating, int userId) {
983 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
984 Binder.getCallingUid(), userId, "isRatingBlocked");
985 final long identity = Binder.clearCallingIdentity();
986 try {
987 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700988 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +0900989 return userState.persistentDataStore.isRatingBlocked(
990 TvContentRating.unflattenFromString(rating));
991 }
992 } finally {
993 Binder.restoreCallingIdentity(identity);
994 }
995 }
996
997 @Override
998 public List<String> getBlockedRatings(int userId) {
999 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1000 Binder.getCallingUid(), userId, "getBlockedRatings");
1001 final long identity = Binder.clearCallingIdentity();
1002 try {
1003 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001004 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001005 List<String> ratings = new ArrayList<>();
Jae Seo783645e2014-07-28 17:30:50 +09001006 for (TvContentRating rating
1007 : userState.persistentDataStore.getBlockedRatings()) {
1008 ratings.add(rating.flattenToString());
1009 }
1010 return ratings;
1011 }
1012 } finally {
1013 Binder.restoreCallingIdentity(identity);
1014 }
1015 }
1016
1017 @Override
1018 public void addBlockedRating(String rating, int userId) {
1019 ensureParentalControlsPermission();
1020 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1021 Binder.getCallingUid(), userId, "addBlockedRating");
1022 final long identity = Binder.clearCallingIdentity();
1023 try {
1024 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001025 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +09001026 userState.persistentDataStore.addBlockedRating(
1027 TvContentRating.unflattenFromString(rating));
1028 }
1029 } finally {
1030 Binder.restoreCallingIdentity(identity);
1031 }
1032 }
1033
1034 @Override
1035 public void removeBlockedRating(String rating, int userId) {
1036 ensureParentalControlsPermission();
1037 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1038 Binder.getCallingUid(), userId, "removeBlockedRating");
1039 final long identity = Binder.clearCallingIdentity();
1040 try {
1041 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001042 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +09001043 userState.persistentDataStore.removeBlockedRating(
1044 TvContentRating.unflattenFromString(rating));
1045 }
1046 } finally {
1047 Binder.restoreCallingIdentity(identity);
1048 }
1049 }
1050
1051 private void ensureParentalControlsPermission() {
Jae Seofc836f62014-08-27 00:47:56 +00001052 if (mContext.checkCallingPermission(
1053 android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
1054 != PackageManager.PERMISSION_GRANTED) {
1055 throw new SecurityException(
1056 "The caller does not have parental controls permission");
1057 }
Jae Seo783645e2014-07-28 17:30:50 +09001058 }
1059
1060 @Override
Sungsoo Limd6672b52014-04-30 10:43:26 +09001061 public void createSession(final ITvInputClient client, final String inputId,
Jae Seoa826d012016-01-18 13:03:35 -08001062 boolean isRecordingSession, int seq, int userId) {
Jae Seo39570912014-02-20 18:23:25 -08001063 final int callingUid = Binder.getCallingUid();
1064 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1065 userId, "createSession");
1066 final long identity = Binder.clearCallingIdentity();
1067 try {
1068 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001069 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Dongwon Kang426c9a42014-08-26 17:39:21 +09001070 TvInputState inputState = userState.inputMap.get(inputId);
1071 if (inputState == null) {
1072 Slog.w(TAG, "Failed to find input state for inputId=" + inputId);
1073 sendSessionTokenToClientLocked(client, inputId, null, null, seq);
1074 return;
1075 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001076 TvInputInfo info = inputState.info;
Wonsik Kim187423c2014-06-25 14:12:48 +09001077 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
Jae Seo39570912014-02-20 18:23:25 -08001078 if (serviceState == null) {
Wonsik Kim187423c2014-06-25 14:12:48 +09001079 serviceState = new ServiceState(info.getComponent(), resolvedUserId);
1080 userState.serviceStateMap.put(info.getComponent(), serviceState);
Jae Seo39570912014-02-20 18:23:25 -08001081 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001082 // Send a null token immediately while reconnecting.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001083 if (serviceState.reconnecting) {
Jae Seo5c80ad22014-06-12 19:52:58 -07001084 sendSessionTokenToClientLocked(client, inputId, null, null, seq);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001085 return;
1086 }
1087
1088 // Create a new session token and a session state.
1089 IBinder sessionToken = new Binder();
Jae Seo2cdb05e2016-02-04 22:17:13 +09001090 SessionState sessionState = new SessionState(sessionToken, info.getId(),
1091 info.getComponent(), isRecordingSession, client, seq, callingUid,
1092 resolvedUserId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001093
1094 // Add them to the global session state map of the current user.
1095 userState.sessionStateMap.put(sessionToken, sessionState);
1096
1097 // Also, add them to the session state map of the current service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001098 serviceState.sessionTokens.add(sessionToken);
Jae Seo39570912014-02-20 18:23:25 -08001099
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001100 if (serviceState.service != null) {
1101 createSessionInternalLocked(serviceState.service, sessionToken,
Sungsoo Lim7de5e232014-04-12 16:51:27 +09001102 resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001103 } else {
Wonsik Kim187423c2014-06-25 14:12:48 +09001104 updateServiceConnectionLocked(info.getComponent(), resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001105 }
1106 }
1107 } finally {
1108 Binder.restoreCallingIdentity(identity);
1109 }
1110 }
1111
1112 @Override
1113 public void releaseSession(IBinder sessionToken, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001114 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -07001115 Slog.d(TAG, "releaseSession(sessionToken=" + sessionToken + ")");
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001116 }
Jae Seo39570912014-02-20 18:23:25 -08001117 final int callingUid = Binder.getCallingUid();
1118 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1119 userId, "releaseSession");
1120 final long identity = Binder.clearCallingIdentity();
1121 try {
1122 synchronized (mLock) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001123 releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001124 }
1125 } finally {
1126 Binder.restoreCallingIdentity(identity);
1127 }
1128 }
1129
1130 @Override
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001131 public void setMainSession(IBinder sessionToken, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001132 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -07001133 Slog.d(TAG, "setMainSession(sessionToken=" + sessionToken + ")");
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001134 }
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001135 final int callingUid = Binder.getCallingUid();
1136 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1137 userId, "setMainSession");
1138 final long identity = Binder.clearCallingIdentity();
1139 try {
1140 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001141 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001142 if (userState.mainSessionToken == sessionToken) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001143 return;
1144 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001145 if (DEBUG) {
1146 Slog.d(TAG, "mainSessionToken=" + sessionToken);
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001147 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001148 IBinder oldMainSessionToken = userState.mainSessionToken;
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001149 userState.mainSessionToken = sessionToken;
1150
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001151 // Inform the new main session first.
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001152 // See {@link TvInputService.Session#onSetMain}.
1153 if (sessionToken != null) {
1154 setMainLocked(sessionToken, true, callingUid, userId);
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001155 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001156 if (oldMainSessionToken != null) {
1157 setMainLocked(oldMainSessionToken, false, Process.SYSTEM_UID, userId);
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001158 }
1159 }
1160 } finally {
1161 Binder.restoreCallingIdentity(identity);
1162 }
1163 }
1164
1165 @Override
Jae Seo39570912014-02-20 18:23:25 -08001166 public void setSurface(IBinder sessionToken, Surface surface, int userId) {
1167 final int callingUid = Binder.getCallingUid();
1168 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1169 userId, "setSurface");
1170 final long identity = Binder.clearCallingIdentity();
1171 try {
1172 synchronized (mLock) {
1173 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001174 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1175 resolvedUserId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001176 if (sessionState.hardwareSessionToken == null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001177 getSessionLocked(sessionState).setSurface(surface);
1178 } else {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001179 getSessionLocked(sessionState.hardwareSessionToken,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001180 Process.SYSTEM_UID, resolvedUserId).setSurface(surface);
1181 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001182 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001183 Slog.e(TAG, "error in setSurface", e);
Jae Seo39570912014-02-20 18:23:25 -08001184 }
1185 }
1186 } finally {
Youngsang Chof8362062014-04-30 17:24:20 +09001187 if (surface != null) {
1188 // surface is not used in TvInputManagerService.
1189 surface.release();
1190 }
Jae Seo39570912014-02-20 18:23:25 -08001191 Binder.restoreCallingIdentity(identity);
1192 }
1193 }
1194
1195 @Override
Youngsang Choe821d712014-07-16 14:22:19 -07001196 public void dispatchSurfaceChanged(IBinder sessionToken, int format, int width,
1197 int height, int userId) {
1198 final int callingUid = Binder.getCallingUid();
1199 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1200 userId, "dispatchSurfaceChanged");
1201 final long identity = Binder.clearCallingIdentity();
1202 try {
1203 synchronized (mLock) {
1204 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001205 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1206 resolvedUserId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001207 getSessionLocked(sessionState).dispatchSurfaceChanged(format, width,
1208 height);
1209 if (sessionState.hardwareSessionToken != null) {
1210 getSessionLocked(sessionState.hardwareSessionToken, Process.SYSTEM_UID,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001211 resolvedUserId).dispatchSurfaceChanged(format, width, height);
1212 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001213 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Choe821d712014-07-16 14:22:19 -07001214 Slog.e(TAG, "error in dispatchSurfaceChanged", e);
1215 }
1216 }
1217 } finally {
1218 Binder.restoreCallingIdentity(identity);
1219 }
1220 }
1221
1222 @Override
Jae Seo39570912014-02-20 18:23:25 -08001223 public void setVolume(IBinder sessionToken, float volume, int userId) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001224 final float REMOTE_VOLUME_ON = 1.0f;
1225 final float REMOTE_VOLUME_OFF = 0f;
Jae Seo39570912014-02-20 18:23:25 -08001226 final int callingUid = Binder.getCallingUid();
1227 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1228 userId, "setVolume");
1229 final long identity = Binder.clearCallingIdentity();
1230 try {
1231 synchronized (mLock) {
1232 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001233 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1234 resolvedUserId);
1235 getSessionLocked(sessionState).setVolume(volume);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001236 if (sessionState.hardwareSessionToken != null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001237 // Here, we let the hardware session know only whether volume is on or
1238 // off to prevent that the volume is controlled in the both side.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001239 getSessionLocked(sessionState.hardwareSessionToken,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001240 Process.SYSTEM_UID, resolvedUserId).setVolume((volume > 0.0f)
1241 ? REMOTE_VOLUME_ON : REMOTE_VOLUME_OFF);
1242 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001243 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001244 Slog.e(TAG, "error in setVolume", e);
Jae Seo39570912014-02-20 18:23:25 -08001245 }
1246 }
1247 } finally {
1248 Binder.restoreCallingIdentity(identity);
1249 }
1250 }
1251
1252 @Override
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +09001253 public void tune(IBinder sessionToken, final Uri channelUri, Bundle params, int userId) {
Jae Seo39570912014-02-20 18:23:25 -08001254 final int callingUid = Binder.getCallingUid();
1255 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1256 userId, "tune");
1257 final long identity = Binder.clearCallingIdentity();
1258 try {
1259 synchronized (mLock) {
Jae Seo39570912014-02-20 18:23:25 -08001260 try {
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +09001261 getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(
1262 channelUri, params);
Jae Seoc22d0c02014-08-15 13:03:21 -07001263 if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
Youngsang Cho008f6d42014-07-22 21:29:47 -07001264 // Do not log the watch history for passthrough inputs.
1265 return;
1266 }
Jae Seo31dc634be2014-04-15 17:40:23 -07001267
Jae Seo4f1a6d42015-07-20 16:15:01 -07001268 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo31dc634be2014-04-15 17:40:23 -07001269 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Jae Seoe3c11e82016-02-08 23:18:49 -08001270 if (sessionState.isRecordingSession) {
1271 return;
1272 }
Jae Seo31dc634be2014-04-15 17:40:23 -07001273
Jae Seo7eb75df2014-08-08 22:20:48 -07001274 // Log the start of watch.
Jae Seo31dc634be2014-04-15 17:40:23 -07001275 SomeArgs args = SomeArgs.obtain();
Jae Seo2cdb05e2016-02-04 22:17:13 +09001276 args.arg1 = sessionState.componentName.getPackageName();
Jae Seo7eb75df2014-08-08 22:20:48 -07001277 args.arg2 = System.currentTimeMillis();
1278 args.arg3 = ContentUris.parseId(channelUri);
1279 args.arg4 = params;
1280 args.arg5 = sessionToken;
1281 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_START, args)
1282 .sendToTarget();
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001283 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001284 Slog.e(TAG, "error in tune", e);
Jae Seo39570912014-02-20 18:23:25 -08001285 }
1286 }
1287 } finally {
1288 Binder.restoreCallingIdentity(identity);
1289 }
1290 }
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001291
1292 @Override
Jae Seoa9033832015-03-11 19:29:46 -07001293 public void unblockContent(
Sungsoo Lim9bf671f2014-07-19 12:59:51 +09001294 IBinder sessionToken, String unblockedRating, int userId) {
Jaewan Kim903d6b72014-07-16 11:28:56 +09001295 final int callingUid = Binder.getCallingUid();
1296 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1297 userId, "unblockContent");
1298 final long identity = Binder.clearCallingIdentity();
1299 try {
1300 synchronized (mLock) {
1301 try {
1302 getSessionLocked(sessionToken, callingUid, resolvedUserId)
Jae Seoa9033832015-03-11 19:29:46 -07001303 .unblockContent(unblockedRating);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001304 } catch (RemoteException | SessionNotFoundException e) {
Jae Seoa9033832015-03-11 19:29:46 -07001305 Slog.e(TAG, "error in unblockContent", e);
Jaewan Kim903d6b72014-07-16 11:28:56 +09001306 }
1307 }
1308 } finally {
1309 Binder.restoreCallingIdentity(identity);
1310 }
1311 }
1312
1313 @Override
Jae Seo2c1c31c2014-07-10 14:57:01 -07001314 public void setCaptionEnabled(IBinder sessionToken, boolean enabled, int userId) {
1315 final int callingUid = Binder.getCallingUid();
1316 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1317 userId, "setCaptionEnabled");
1318 final long identity = Binder.clearCallingIdentity();
1319 try {
1320 synchronized (mLock) {
1321 try {
1322 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1323 .setCaptionEnabled(enabled);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001324 } catch (RemoteException | SessionNotFoundException e) {
Jae Seo2c1c31c2014-07-10 14:57:01 -07001325 Slog.e(TAG, "error in setCaptionEnabled", e);
1326 }
1327 }
1328 } finally {
1329 Binder.restoreCallingIdentity(identity);
1330 }
1331 }
1332
1333 @Override
Jae Seo10d285a2014-07-31 22:46:47 +09001334 public void selectTrack(IBinder sessionToken, int type, String trackId, int userId) {
Dongwon Kang1f213912014-07-02 18:35:08 +09001335 final int callingUid = Binder.getCallingUid();
1336 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1337 userId, "selectTrack");
1338 final long identity = Binder.clearCallingIdentity();
1339 try {
1340 synchronized (mLock) {
1341 try {
1342 getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack(
Jae Seo10d285a2014-07-31 22:46:47 +09001343 type, trackId);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001344 } catch (RemoteException | SessionNotFoundException e) {
Dongwon Kang1f213912014-07-02 18:35:08 +09001345 Slog.e(TAG, "error in selectTrack", e);
1346 }
1347 }
1348 } finally {
1349 Binder.restoreCallingIdentity(identity);
1350 }
1351 }
1352
1353 @Override
Jae Seoa759b112014-07-18 22:16:08 -07001354 public void sendAppPrivateCommand(IBinder sessionToken, String command, Bundle data,
1355 int userId) {
1356 final int callingUid = Binder.getCallingUid();
1357 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1358 userId, "sendAppPrivateCommand");
1359 final long identity = Binder.clearCallingIdentity();
1360 try {
1361 synchronized (mLock) {
1362 try {
1363 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1364 .appPrivateCommand(command, data);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001365 } catch (RemoteException | SessionNotFoundException e) {
Jae Seofea8dd42014-08-26 13:57:41 -07001366 Slog.e(TAG, "error in appPrivateCommand", e);
Jae Seoa759b112014-07-18 22:16:08 -07001367 }
1368 }
1369 } finally {
1370 Binder.restoreCallingIdentity(identity);
1371 }
1372 }
1373
1374 @Override
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001375 public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
1376 int userId) {
1377 final int callingUid = Binder.getCallingUid();
1378 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1379 userId, "createOverlayView");
1380 final long identity = Binder.clearCallingIdentity();
1381 try {
1382 synchronized (mLock) {
1383 try {
1384 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1385 .createOverlayView(windowToken, frame);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001386 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001387 Slog.e(TAG, "error in createOverlayView", e);
1388 }
1389 }
1390 } finally {
1391 Binder.restoreCallingIdentity(identity);
1392 }
1393 }
1394
1395 @Override
1396 public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
1397 final int callingUid = Binder.getCallingUid();
1398 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1399 userId, "relayoutOverlayView");
1400 final long identity = Binder.clearCallingIdentity();
1401 try {
1402 synchronized (mLock) {
1403 try {
1404 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1405 .relayoutOverlayView(frame);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001406 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001407 Slog.e(TAG, "error in relayoutOverlayView", e);
1408 }
1409 }
1410 } finally {
1411 Binder.restoreCallingIdentity(identity);
1412 }
1413 }
1414
1415 @Override
1416 public void removeOverlayView(IBinder sessionToken, int userId) {
1417 final int callingUid = Binder.getCallingUid();
1418 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1419 userId, "removeOverlayView");
1420 final long identity = Binder.clearCallingIdentity();
1421 try {
1422 synchronized (mLock) {
1423 try {
1424 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1425 .removeOverlayView();
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001426 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001427 Slog.e(TAG, "error in removeOverlayView", e);
1428 }
1429 }
1430 } finally {
1431 Binder.restoreCallingIdentity(identity);
1432 }
1433 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001434
1435 @Override
Jae Seoa826d012016-01-18 13:03:35 -08001436 public void timeShiftPlay(IBinder sessionToken, final Uri recordedProgramUri, int userId) {
1437 final int callingUid = Binder.getCallingUid();
1438 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1439 userId, "timeShiftPlay");
1440 final long identity = Binder.clearCallingIdentity();
1441 try {
1442 synchronized (mLock) {
1443 try {
1444 getSessionLocked(sessionToken, callingUid, resolvedUserId).timeShiftPlay(
1445 recordedProgramUri);
1446 } catch (RemoteException | SessionNotFoundException e) {
1447 Slog.e(TAG, "error in timeShiftPlay", e);
1448 }
1449 }
1450 } finally {
1451 Binder.restoreCallingIdentity(identity);
1452 }
1453 }
1454
1455 @Override
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001456 public void timeShiftPause(IBinder sessionToken, int userId) {
1457 final int callingUid = Binder.getCallingUid();
1458 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1459 userId, "timeShiftPause");
1460 final long identity = Binder.clearCallingIdentity();
1461 try {
1462 synchronized (mLock) {
1463 try {
Jae Seoa826d012016-01-18 13:03:35 -08001464 getSessionLocked(sessionToken, callingUid, resolvedUserId).timeShiftPause();
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001465 } catch (RemoteException | SessionNotFoundException e) {
1466 Slog.e(TAG, "error in timeShiftPause", e);
1467 }
1468 }
1469 } finally {
1470 Binder.restoreCallingIdentity(identity);
1471 }
1472 }
1473
1474 @Override
1475 public void timeShiftResume(IBinder sessionToken, int userId) {
1476 final int callingUid = Binder.getCallingUid();
1477 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1478 userId, "timeShiftResume");
1479 final long identity = Binder.clearCallingIdentity();
1480 try {
1481 synchronized (mLock) {
1482 try {
1483 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1484 .timeShiftResume();
1485 } catch (RemoteException | SessionNotFoundException e) {
1486 Slog.e(TAG, "error in timeShiftResume", e);
1487 }
1488 }
1489 } finally {
1490 Binder.restoreCallingIdentity(identity);
1491 }
1492 }
1493
1494 @Override
1495 public void timeShiftSeekTo(IBinder sessionToken, long timeMs, int userId) {
1496 final int callingUid = Binder.getCallingUid();
1497 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1498 userId, "timeShiftSeekTo");
1499 final long identity = Binder.clearCallingIdentity();
1500 try {
1501 synchronized (mLock) {
1502 try {
1503 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1504 .timeShiftSeekTo(timeMs);
1505 } catch (RemoteException | SessionNotFoundException e) {
1506 Slog.e(TAG, "error in timeShiftSeekTo", e);
1507 }
1508 }
1509 } finally {
1510 Binder.restoreCallingIdentity(identity);
1511 }
1512 }
1513
1514 @Override
Jae Seo4b34cc72015-05-15 17:29:39 -07001515 public void timeShiftSetPlaybackParams(IBinder sessionToken, PlaybackParams params,
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001516 int userId) {
1517 final int callingUid = Binder.getCallingUid();
1518 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
Jae Seo4b34cc72015-05-15 17:29:39 -07001519 userId, "timeShiftSetPlaybackParams");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001520 final long identity = Binder.clearCallingIdentity();
1521 try {
1522 synchronized (mLock) {
1523 try {
1524 getSessionLocked(sessionToken, callingUid, resolvedUserId)
Jae Seo4b34cc72015-05-15 17:29:39 -07001525 .timeShiftSetPlaybackParams(params);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001526 } catch (RemoteException | SessionNotFoundException e) {
Jae Seo4b34cc72015-05-15 17:29:39 -07001527 Slog.e(TAG, "error in timeShiftSetPlaybackParams", e);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001528 }
1529 }
1530 } finally {
1531 Binder.restoreCallingIdentity(identity);
1532 }
1533 }
1534
1535 @Override
Jae Seo465f0d62015-04-06 18:40:46 -07001536 public void timeShiftEnablePositionTracking(IBinder sessionToken, boolean enable,
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001537 int userId) {
1538 final int callingUid = Binder.getCallingUid();
1539 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
Jae Seo465f0d62015-04-06 18:40:46 -07001540 userId, "timeShiftEnablePositionTracking");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001541 final long identity = Binder.clearCallingIdentity();
1542 try {
1543 synchronized (mLock) {
1544 try {
1545 getSessionLocked(sessionToken, callingUid, resolvedUserId)
Jae Seo465f0d62015-04-06 18:40:46 -07001546 .timeShiftEnablePositionTracking(enable);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001547 } catch (RemoteException | SessionNotFoundException e) {
Jae Seo465f0d62015-04-06 18:40:46 -07001548 Slog.e(TAG, "error in timeShiftEnablePositionTracking", e);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001549 }
1550 }
1551 } finally {
1552 Binder.restoreCallingIdentity(identity);
1553 }
1554 }
1555
1556 @Override
Jae Seo4eee6a72016-02-06 11:11:35 +09001557 public void startRecording(IBinder sessionToken, @Nullable Uri programHint, int userId) {
Jae Seoa826d012016-01-18 13:03:35 -08001558 final int callingUid = Binder.getCallingUid();
1559 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1560 userId, "startRecording");
1561 final long identity = Binder.clearCallingIdentity();
1562 try {
1563 synchronized (mLock) {
1564 try {
Jae Seo4eee6a72016-02-06 11:11:35 +09001565 getSessionLocked(sessionToken, callingUid, resolvedUserId).startRecording(
1566 programHint);
Jae Seoa826d012016-01-18 13:03:35 -08001567 } catch (RemoteException | SessionNotFoundException e) {
1568 Slog.e(TAG, "error in startRecording", e);
1569 }
1570 }
1571 } finally {
1572 Binder.restoreCallingIdentity(identity);
1573 }
1574 }
1575
1576 @Override
1577 public void stopRecording(IBinder sessionToken, int userId) {
1578 final int callingUid = Binder.getCallingUid();
1579 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1580 userId, "stopRecording");
1581 final long identity = Binder.clearCallingIdentity();
1582 try {
1583 synchronized (mLock) {
1584 try {
1585 getSessionLocked(sessionToken, callingUid, resolvedUserId).stopRecording();
1586 } catch (RemoteException | SessionNotFoundException e) {
1587 Slog.e(TAG, "error in stopRecording", e);
1588 }
1589 }
1590 } finally {
1591 Binder.restoreCallingIdentity(identity);
1592 }
1593 }
1594
1595 @Override
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001596 public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
Wonsik Kim969167d2014-06-24 16:33:17 +09001597 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001598 != PackageManager.PERMISSION_GRANTED) {
1599 return null;
1600 }
1601
1602 final long identity = Binder.clearCallingIdentity();
1603 try {
1604 return mTvInputHardwareManager.getHardwareList();
1605 } finally {
1606 Binder.restoreCallingIdentity(identity);
1607 }
1608 }
1609
1610 @Override
1611 public ITvInputHardware acquireTvInputHardware(int deviceId,
Wonsik Kim969167d2014-06-24 16:33:17 +09001612 ITvInputHardwareCallback callback, TvInputInfo info, int userId)
1613 throws RemoteException {
1614 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001615 != PackageManager.PERMISSION_GRANTED) {
1616 return null;
1617 }
1618
1619 final long identity = Binder.clearCallingIdentity();
1620 final int callingUid = Binder.getCallingUid();
1621 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1622 userId, "acquireTvInputHardware");
1623 try {
1624 return mTvInputHardwareManager.acquireHardware(
Wonsik Kim969167d2014-06-24 16:33:17 +09001625 deviceId, callback, info, callingUid, resolvedUserId);
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001626 } finally {
1627 Binder.restoreCallingIdentity(identity);
1628 }
1629 }
1630
1631 @Override
1632 public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
1633 throws RemoteException {
Wonsik Kim969167d2014-06-24 16:33:17 +09001634 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001635 != PackageManager.PERMISSION_GRANTED) {
1636 return;
1637 }
1638
1639 final long identity = Binder.clearCallingIdentity();
1640 final int callingUid = Binder.getCallingUid();
1641 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1642 userId, "releaseTvInputHardware");
1643 try {
1644 mTvInputHardwareManager.releaseHardware(
1645 deviceId, hardware, callingUid, resolvedUserId);
1646 } finally {
1647 Binder.restoreCallingIdentity(identity);
1648 }
1649 }
Jaewan Kime14c3f42014-06-27 13:47:48 +09001650
1651 @Override
Jaesung Chung58739e72015-04-24 19:39:59 +09001652 public List<DvbDeviceInfo> getDvbDeviceList() throws RemoteException {
1653 if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)
1654 != PackageManager.PERMISSION_GRANTED) {
1655 throw new SecurityException("Requires DVB_DEVICE permission");
1656 }
1657
1658 final long identity = Binder.clearCallingIdentity();
1659 try {
1660 ArrayList<DvbDeviceInfo> deviceInfos = new ArrayList<>();
1661 File devDirectory = new File("/dev");
1662 for (String fileName : devDirectory.list()) {
1663 Matcher matcher = sFrontEndDevicePattern.matcher(fileName);
1664 if (matcher.find()) {
1665 int adapterId = Integer.parseInt(matcher.group(1));
1666 int deviceId = Integer.parseInt(matcher.group(2));
1667 deviceInfos.add(new DvbDeviceInfo(adapterId, deviceId));
1668 }
1669 }
1670 return Collections.unmodifiableList(deviceInfos);
1671 } finally {
1672 Binder.restoreCallingIdentity(identity);
1673 }
1674 }
1675
1676 @Override
1677 public ParcelFileDescriptor openDvbDevice(DvbDeviceInfo info, int device)
1678 throws RemoteException {
1679 if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)
1680 != PackageManager.PERMISSION_GRANTED) {
1681 throw new SecurityException("Requires DVB_DEVICE permission");
1682 }
1683
1684 final long identity = Binder.clearCallingIdentity();
1685 try {
1686 String deviceFileName;
1687 switch (device) {
1688 case TvInputManager.DVB_DEVICE_DEMUX:
1689 deviceFileName = String.format("/dev/dvb%d.demux%d", info.getAdapterId(),
1690 info.getDeviceId());
1691 break;
1692 case TvInputManager.DVB_DEVICE_DVR:
1693 deviceFileName = String.format("/dev/dvb%d.dvr%d", info.getAdapterId(),
1694 info.getDeviceId());
1695 break;
1696 case TvInputManager.DVB_DEVICE_FRONTEND:
1697 deviceFileName = String.format("/dev/dvb%d.frontend%d", info.getAdapterId(),
1698 info.getDeviceId());
1699 break;
1700 default:
1701 throw new IllegalArgumentException("Invalid DVB device: " + device);
1702 }
1703 try {
1704 // The DVB frontend device only needs to be opened in read/write mode, which
1705 // allows performing tuning operations. The DVB demux and DVR device are enough
1706 // to be opened in read only mode.
1707 return ParcelFileDescriptor.open(new File(deviceFileName),
1708 TvInputManager.DVB_DEVICE_FRONTEND == device
1709 ? ParcelFileDescriptor.MODE_READ_WRITE
1710 : ParcelFileDescriptor.MODE_READ_ONLY);
1711 } catch (FileNotFoundException e) {
1712 return null;
1713 }
1714 } finally {
1715 Binder.restoreCallingIdentity(identity);
1716 }
1717 }
1718
1719 @Override
Terry Heoc086a3d2014-06-18 14:26:44 +09001720 public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int userId)
1721 throws RemoteException {
1722 if (mContext.checkCallingPermission(
1723 android.Manifest.permission.CAPTURE_TV_INPUT)
1724 != PackageManager.PERMISSION_GRANTED) {
1725 throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1726 }
1727
1728 final long identity = Binder.clearCallingIdentity();
1729 final int callingUid = Binder.getCallingUid();
1730 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1731 userId, "getAvailableTvStreamConfigList");
1732 try {
1733 return mTvInputHardwareManager.getAvailableTvStreamConfigList(
1734 inputId, callingUid, resolvedUserId);
1735 } finally {
1736 Binder.restoreCallingIdentity(identity);
1737 }
1738 }
1739
1740 @Override
1741 public boolean captureFrame(String inputId, Surface surface, TvStreamConfig config,
1742 int userId)
1743 throws RemoteException {
1744 if (mContext.checkCallingPermission(
1745 android.Manifest.permission.CAPTURE_TV_INPUT)
1746 != PackageManager.PERMISSION_GRANTED) {
1747 throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1748 }
1749
1750 final long identity = Binder.clearCallingIdentity();
1751 final int callingUid = Binder.getCallingUid();
1752 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1753 userId, "captureFrame");
1754 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001755 String hardwareInputId = null;
Terry Heo79124a72014-07-21 15:17:17 +09001756 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001757 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001758 if (userState.inputMap.get(inputId) == null) {
Jae Seofea8dd42014-08-26 13:57:41 -07001759 Slog.e(TAG, "input not found for " + inputId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001760 return false;
1761 }
1762 for (SessionState sessionState : userState.sessionStateMap.values()) {
Jae Seo2cdb05e2016-02-04 22:17:13 +09001763 if (sessionState.inputId.equals(inputId)
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001764 && sessionState.hardwareSessionToken != null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001765 hardwareInputId = userState.sessionStateMap.get(
Jae Seo2cdb05e2016-02-04 22:17:13 +09001766 sessionState.hardwareSessionToken).inputId;
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001767 break;
1768 }
1769 }
Terry Heo79124a72014-07-21 15:17:17 +09001770 }
Terry Heoc086a3d2014-06-18 14:26:44 +09001771 return mTvInputHardwareManager.captureFrame(
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001772 (hardwareInputId != null) ? hardwareInputId : inputId,
Terry Heo79124a72014-07-21 15:17:17 +09001773 surface, config, callingUid, resolvedUserId);
Terry Heoc086a3d2014-06-18 14:26:44 +09001774 } finally {
1775 Binder.restoreCallingIdentity(identity);
1776 }
1777 }
1778
1779 @Override
Terry Heodf9f0a32014-08-06 13:53:33 +09001780 public boolean isSingleSessionActive(int userId) throws RemoteException {
1781 final long identity = Binder.clearCallingIdentity();
1782 final int callingUid = Binder.getCallingUid();
1783 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1784 userId, "isSingleSessionActive");
1785 try {
1786 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001787 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Terry Heodf9f0a32014-08-06 13:53:33 +09001788 if (userState.sessionStateMap.size() == 1) {
1789 return true;
Jae Seo93ff14b2015-06-21 14:08:54 -07001790 } else if (userState.sessionStateMap.size() == 2) {
Terry Heodf9f0a32014-08-06 13:53:33 +09001791 SessionState[] sessionStates = userState.sessionStateMap.values().toArray(
Jae Seo93ff14b2015-06-21 14:08:54 -07001792 new SessionState[2]);
Terry Heodf9f0a32014-08-06 13:53:33 +09001793 // Check if there is a wrapper input.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001794 if (sessionStates[0].hardwareSessionToken != null
1795 || sessionStates[1].hardwareSessionToken != null) {
Terry Heodf9f0a32014-08-06 13:53:33 +09001796 return true;
1797 }
1798 }
1799 return false;
1800 }
1801 } finally {
1802 Binder.restoreCallingIdentity(identity);
1803 }
1804 }
1805
1806 @Override
Jae Seo0f8fc342014-07-02 10:47:08 -07001807 @SuppressWarnings("resource")
Jaewan Kime14c3f42014-06-27 13:47:48 +09001808 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1809 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
Jae Seo0f8fc342014-07-02 10:47:08 -07001810 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
Jaewan Kime14c3f42014-06-27 13:47:48 +09001811 != PackageManager.PERMISSION_GRANTED) {
Jae Seo0f8fc342014-07-02 10:47:08 -07001812 pw.println("Permission Denial: can't dump TvInputManager from pid="
1813 + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
Jaewan Kime14c3f42014-06-27 13:47:48 +09001814 return;
1815 }
1816
1817 synchronized (mLock) {
1818 pw.println("User Ids (Current user: " + mCurrentUserId + "):");
1819 pw.increaseIndent();
1820 for (int i = 0; i < mUserStates.size(); i++) {
1821 int userId = mUserStates.keyAt(i);
1822 pw.println(Integer.valueOf(userId));
1823 }
1824 pw.decreaseIndent();
1825
1826 for (int i = 0; i < mUserStates.size(); i++) {
1827 int userId = mUserStates.keyAt(i);
Jae Seo4f1a6d42015-07-20 16:15:01 -07001828 UserState userState = getOrCreateUserStateLocked(userId);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001829 pw.println("UserState (" + userId + "):");
1830 pw.increaseIndent();
1831
Wonsik Kim969167d2014-06-24 16:33:17 +09001832 pw.println("inputMap: inputId -> TvInputState");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001833 pw.increaseIndent();
Jaewan Kim8e6b51b2014-07-15 13:01:57 +09001834 for (Map.Entry<String, TvInputState> entry: userState.inputMap.entrySet()) {
1835 pw.println(entry.getKey() + ": " + entry.getValue());
Jaewan Kime14c3f42014-06-27 13:47:48 +09001836 }
1837 pw.decreaseIndent();
1838
Wonsik Kim969167d2014-06-24 16:33:17 +09001839 pw.println("packageSet:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001840 pw.increaseIndent();
Wonsik Kim969167d2014-06-24 16:33:17 +09001841 for (String packageName : userState.packageSet) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09001842 pw.println(packageName);
1843 }
1844 pw.decreaseIndent();
1845
1846 pw.println("clientStateMap: ITvInputClient -> ClientState");
1847 pw.increaseIndent();
1848 for (Map.Entry<IBinder, ClientState> entry :
1849 userState.clientStateMap.entrySet()) {
1850 ClientState client = entry.getValue();
1851 pw.println(entry.getKey() + ": " + client);
1852
1853 pw.increaseIndent();
1854
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001855 pw.println("sessionTokens:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001856 pw.increaseIndent();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001857 for (IBinder token : client.sessionTokens) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09001858 pw.println("" + token);
1859 }
1860 pw.decreaseIndent();
1861
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001862 pw.println("clientTokens: " + client.clientToken);
1863 pw.println("userId: " + client.userId);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001864
1865 pw.decreaseIndent();
1866 }
1867 pw.decreaseIndent();
1868
Wonsik Kim187423c2014-06-25 14:12:48 +09001869 pw.println("serviceStateMap: ComponentName -> ServiceState");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001870 pw.increaseIndent();
Wonsik Kim187423c2014-06-25 14:12:48 +09001871 for (Map.Entry<ComponentName, ServiceState> entry :
Jaewan Kime14c3f42014-06-27 13:47:48 +09001872 userState.serviceStateMap.entrySet()) {
1873 ServiceState service = entry.getValue();
1874 pw.println(entry.getKey() + ": " + service);
1875
1876 pw.increaseIndent();
1877
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001878 pw.println("sessionTokens:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001879 pw.increaseIndent();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001880 for (IBinder token : service.sessionTokens) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09001881 pw.println("" + token);
1882 }
1883 pw.decreaseIndent();
1884
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001885 pw.println("service: " + service.service);
1886 pw.println("callback: " + service.callback);
1887 pw.println("bound: " + service.bound);
1888 pw.println("reconnecting: " + service.reconnecting);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001889
1890 pw.decreaseIndent();
1891 }
1892 pw.decreaseIndent();
1893
1894 pw.println("sessionStateMap: ITvInputSession -> SessionState");
1895 pw.increaseIndent();
1896 for (Map.Entry<IBinder, SessionState> entry :
1897 userState.sessionStateMap.entrySet()) {
1898 SessionState session = entry.getValue();
1899 pw.println(entry.getKey() + ": " + session);
1900
1901 pw.increaseIndent();
Jae Seo2cdb05e2016-02-04 22:17:13 +09001902 pw.println("inputId: " + session.inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001903 pw.println("client: " + session.client);
1904 pw.println("seq: " + session.seq);
1905 pw.println("callingUid: " + session.callingUid);
1906 pw.println("userId: " + session.userId);
1907 pw.println("sessionToken: " + session.sessionToken);
1908 pw.println("session: " + session.session);
1909 pw.println("logUri: " + session.logUri);
1910 pw.println("hardwareSessionToken: " + session.hardwareSessionToken);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001911 pw.decreaseIndent();
1912 }
1913 pw.decreaseIndent();
1914
Wonsik Kim969167d2014-06-24 16:33:17 +09001915 pw.println("callbackSet:");
1916 pw.increaseIndent();
1917 for (ITvInputManagerCallback callback : userState.callbackSet) {
1918 pw.println(callback.toString());
1919 }
1920 pw.decreaseIndent();
1921
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001922 pw.println("mainSessionToken: " + userState.mainSessionToken);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001923 pw.decreaseIndent();
1924 }
1925 }
yangren8d718e12016-02-15 17:02:49 +09001926 mTvInputHardwareManager.dump(fd, writer, args);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001927 }
Jae Seo39570912014-02-20 18:23:25 -08001928 }
1929
Wonsik Kim969167d2014-06-24 16:33:17 +09001930 private static final class UserState {
1931 // A mapping from the TV input id to its TvInputState.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001932 private Map<String, TvInputState> inputMap = new HashMap<>();
Wonsik Kim969167d2014-06-24 16:33:17 +09001933
1934 // A set of all TV input packages.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001935 private final Set<String> packageSet = new HashSet<>();
Jae Seo5c80ad22014-06-12 19:52:58 -07001936
Jae Seo9c165d62014-08-25 14:39:26 -07001937 // A list of all TV content rating systems defined.
1938 private final List<TvContentRatingSystemInfo>
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001939 contentRatingSystemList = new ArrayList<>();
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +09001940
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001941 // A mapping from the token of a client to its state.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001942 private final Map<IBinder, ClientState> clientStateMap = new HashMap<>();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001943
Jae Seo39570912014-02-20 18:23:25 -08001944 // A mapping from the name of a TV input service to its state.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001945 private final Map<ComponentName, ServiceState> serviceStateMap = new HashMap<>();
Jae Seo39570912014-02-20 18:23:25 -08001946
1947 // A mapping from the token of a TV input session to its state.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001948 private final Map<IBinder, SessionState> sessionStateMap = new HashMap<>();
Wonsik Kim969167d2014-06-24 16:33:17 +09001949
1950 // A set of callbacks.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001951 private final Set<ITvInputManagerCallback> callbackSet = new HashSet<>();
Terry Heo79124a72014-07-21 15:17:17 +09001952
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001953 // The token of a "main" TV input session.
1954 private IBinder mainSessionToken = null;
Jae Seo783645e2014-07-28 17:30:50 +09001955
1956 // Persistent data store for all internal settings maintained by the TV input manager
1957 // service.
1958 private final PersistentDataStore persistentDataStore;
1959
1960 private UserState(Context context, int userId) {
1961 persistentDataStore = new PersistentDataStore(context, userId);
1962 }
Jae Seo39570912014-02-20 18:23:25 -08001963 }
1964
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001965 private final class ClientState implements IBinder.DeathRecipient {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001966 private final List<IBinder> sessionTokens = new ArrayList<>();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001967
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001968 private IBinder clientToken;
1969 private final int userId;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001970
1971 ClientState(IBinder clientToken, int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001972 this.clientToken = clientToken;
1973 this.userId = userId;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001974 }
1975
1976 public boolean isEmpty() {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001977 return sessionTokens.isEmpty();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001978 }
1979
1980 @Override
1981 public void binderDied() {
1982 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001983 UserState userState = getOrCreateUserStateLocked(userId);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001984 // DO NOT remove the client state of clientStateMap in this method. It will be
Ji-Hwan Leea65118e2014-07-24 16:30:02 +09001985 // removed in releaseSessionLocked().
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001986 ClientState clientState = userState.clientStateMap.get(clientToken);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001987 if (clientState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001988 while (clientState.sessionTokens.size() > 0) {
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001989 releaseSessionLocked(
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001990 clientState.sessionTokens.get(0), Process.SYSTEM_UID, userId);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001991 }
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001992 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001993 clientToken = null;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09001994 }
1995 }
1996 }
1997
Jae Seo39570912014-02-20 18:23:25 -08001998 private final class ServiceState {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001999 private final List<IBinder> sessionTokens = new ArrayList<>();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002000 private final ServiceConnection connection;
2001 private final ComponentName component;
2002 private final boolean isHardware;
Jae Seo1abbbcd2016-01-28 22:20:41 -08002003 private final List<TvInputInfo> hardwareInputList = new ArrayList<>();
Jae Seo39570912014-02-20 18:23:25 -08002004
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002005 private ITvInputService service;
2006 private ServiceCallback callback;
2007 private boolean bound;
2008 private boolean reconnecting;
Jae Seo39570912014-02-20 18:23:25 -08002009
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002010 private ServiceState(ComponentName component, int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002011 this.component = component;
2012 this.connection = new InputServiceConnection(component, userId);
2013 this.isHardware = hasHardwarePermission(mContext.getPackageManager(), component);
2014 }
2015 }
2016
2017 private static final class TvInputState {
2018 // A TvInputInfo object which represents the TV input.
2019 private TvInputInfo info;
2020
2021 // The state of TV input. Connected by default.
2022 private int state = INPUT_STATE_CONNECTED;
2023
2024 @Override
2025 public String toString() {
2026 return "info: " + info + "; state: " + state;
Jae Seo39570912014-02-20 18:23:25 -08002027 }
2028 }
2029
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002030 private final class SessionState implements IBinder.DeathRecipient {
Jae Seo2cdb05e2016-02-04 22:17:13 +09002031 private final String inputId;
2032 private final ComponentName componentName;
Jae Seoa826d012016-01-18 13:03:35 -08002033 private final boolean isRecordingSession;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002034 private final ITvInputClient client;
2035 private final int seq;
2036 private final int callingUid;
2037 private final int userId;
2038 private final IBinder sessionToken;
2039 private ITvInputSession session;
2040 private Uri logUri;
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09002041 // Not null if this session represents an external device connected to a hardware TV input.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002042 private IBinder hardwareSessionToken;
Jae Seo39570912014-02-20 18:23:25 -08002043
Jae Seo2cdb05e2016-02-04 22:17:13 +09002044 private SessionState(IBinder sessionToken, String inputId, ComponentName componentName,
2045 boolean isRecordingSession, ITvInputClient client, int seq, int callingUid,
2046 int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002047 this.sessionToken = sessionToken;
Jae Seo2cdb05e2016-02-04 22:17:13 +09002048 this.inputId = inputId;
2049 this.componentName = componentName;
Jae Seoa826d012016-01-18 13:03:35 -08002050 this.isRecordingSession = isRecordingSession;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002051 this.client = client;
2052 this.seq = seq;
2053 this.callingUid = callingUid;
2054 this.userId = userId;
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002055 }
2056
2057 @Override
2058 public void binderDied() {
2059 synchronized (mLock) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002060 session = null;
2061 if (client != null) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002062 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002063 client.onSessionReleased(seq);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002064 } catch(RemoteException e) {
2065 Slog.e(TAG, "error in onSessionReleased", e);
2066 }
2067 }
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09002068 // If there are any other sessions based on this session, they should be released.
Jae Seo4f1a6d42015-07-20 16:15:01 -07002069 UserState userState = getOrCreateUserStateLocked(userId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09002070 for (SessionState sessionState : userState.sessionStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002071 if (sessionToken == sessionState.hardwareSessionToken) {
2072 releaseSessionLocked(sessionState.sessionToken, Process.SYSTEM_UID,
2073 userId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09002074 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002075 sessionState.client.onSessionReleased(sessionState.seq);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09002076 } catch (RemoteException e) {
2077 Slog.e(TAG, "error in onSessionReleased", e);
2078 }
2079 }
2080 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002081 removeSessionStateLocked(sessionToken, userId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002082 }
Jae Seo39570912014-02-20 18:23:25 -08002083 }
2084 }
2085
2086 private final class InputServiceConnection implements ServiceConnection {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002087 private final ComponentName mComponent;
Jae Seo39570912014-02-20 18:23:25 -08002088 private final int mUserId;
2089
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002090 private InputServiceConnection(ComponentName component, int userId) {
2091 mComponent = component;
Jae Seo39570912014-02-20 18:23:25 -08002092 mUserId = userId;
2093 }
2094
2095 @Override
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002096 public void onServiceConnected(ComponentName component, IBinder service) {
Jae Seo39570912014-02-20 18:23:25 -08002097 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002098 Slog.d(TAG, "onServiceConnected(component=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -08002099 }
2100 synchronized (mLock) {
Dongwon Kang81e3c3e2015-09-11 15:24:25 -07002101 UserState userState = mUserStates.get(mUserId);
2102 if (userState == null) {
2103 // The user was removed while connecting.
2104 mContext.unbindService(this);
2105 return;
2106 }
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002107 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002108 serviceState.service = ITvInputService.Stub.asInterface(service);
Jae Seo39570912014-02-20 18:23:25 -08002109
2110 // Register a callback, if we need to.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002111 if (serviceState.isHardware && serviceState.callback == null) {
2112 serviceState.callback = new ServiceCallback(mComponent, mUserId);
Jae Seo39570912014-02-20 18:23:25 -08002113 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002114 serviceState.service.registerCallback(serviceState.callback);
Jae Seo39570912014-02-20 18:23:25 -08002115 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09002116 Slog.e(TAG, "error in registerCallback", e);
Jae Seo39570912014-02-20 18:23:25 -08002117 }
2118 }
2119
2120 // And create sessions, if any.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002121 for (IBinder sessionToken : serviceState.sessionTokens) {
2122 createSessionInternalLocked(serviceState.service, sessionToken, mUserId);
Jae Seo39570912014-02-20 18:23:25 -08002123 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002124
Wonsik Kim187423c2014-06-25 14:12:48 +09002125 for (TvInputState inputState : userState.inputMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002126 if (inputState.info.getComponent().equals(component)
Jae Seo82fce642015-04-20 15:37:50 -07002127 && inputState.state != INPUT_STATE_CONNECTED) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002128 notifyInputStateChangedLocked(userState, inputState.info.getId(),
2129 inputState.state, null);
Wonsik Kim187423c2014-06-25 14:12:48 +09002130 }
2131 }
2132
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002133 if (serviceState.isHardware) {
Jae Seoc980f43d2016-02-09 23:46:58 -08002134 serviceState.hardwareInputList.clear();
2135 for (TvInputHardwareInfo hardware : mTvInputHardwareManager.getHardwareList()) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002136 try {
Jae Seoc980f43d2016-02-09 23:46:58 -08002137 serviceState.service.notifyHardwareAdded(hardware);
Wonsik Kim187423c2014-06-25 14:12:48 +09002138 } catch (RemoteException e) {
2139 Slog.e(TAG, "error in notifyHardwareAdded", e);
2140 }
2141 }
Jae Seoc980f43d2016-02-09 23:46:58 -08002142 for (HdmiDeviceInfo device : mTvInputHardwareManager.getHdmiDeviceList()) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002143 try {
Jae Seoc980f43d2016-02-09 23:46:58 -08002144 serviceState.service.notifyHdmiDeviceAdded(device);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002145 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002146 Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002147 }
2148 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002149 }
Jae Seo39570912014-02-20 18:23:25 -08002150 }
2151 }
2152
2153 @Override
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002154 public void onServiceDisconnected(ComponentName component) {
Jae Seo39570912014-02-20 18:23:25 -08002155 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002156 Slog.d(TAG, "onServiceDisconnected(component=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -08002157 }
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002158 if (!mComponent.equals(component)) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002159 throw new IllegalArgumentException("Mismatched ComponentName: "
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002160 + mComponent + " (expected), " + component + " (actual).");
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002161 }
2162 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002163 UserState userState = getOrCreateUserStateLocked(mUserId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002164 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002165 if (serviceState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002166 serviceState.reconnecting = true;
2167 serviceState.bound = false;
2168 serviceState.service = null;
2169 serviceState.callback = null;
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002170
Dongwon Kang426c9a42014-08-26 17:39:21 +09002171 abortPendingCreateSessionRequestsLocked(serviceState, null, mUserId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002172 }
2173 }
Jae Seo39570912014-02-20 18:23:25 -08002174 }
2175 }
2176
2177 private final class ServiceCallback extends ITvInputServiceCallback.Stub {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002178 private final ComponentName mComponent;
Jae Seo39570912014-02-20 18:23:25 -08002179 private final int mUserId;
2180
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002181 ServiceCallback(ComponentName component, int userId) {
2182 mComponent = component;
Jae Seo39570912014-02-20 18:23:25 -08002183 mUserId = userId;
2184 }
2185
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002186 private void ensureHardwarePermission() {
2187 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
2188 != PackageManager.PERMISSION_GRANTED) {
2189 throw new SecurityException("The caller does not have hardware permission");
2190 }
2191 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002192
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002193 private void ensureValidInput(TvInputInfo inputInfo) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002194 if (inputInfo.getId() == null || !mComponent.equals(inputInfo.getComponent())) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002195 throw new IllegalArgumentException("Invalid TvInputInfo");
2196 }
2197 }
2198
Jae Seo1abbbcd2016-01-28 22:20:41 -08002199 private void addHardwareInputLocked(TvInputInfo inputInfo) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002200 ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
Jae Seo1abbbcd2016-01-28 22:20:41 -08002201 serviceState.hardwareInputList.add(inputInfo);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +09002202 buildTvInputListLocked(mUserId, null);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002203 }
2204
Jae Seo1abbbcd2016-01-28 22:20:41 -08002205 public void addHardwareInput(int deviceId, TvInputInfo inputInfo) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002206 ensureHardwarePermission();
2207 ensureValidInput(inputInfo);
2208 synchronized (mLock) {
Jae Seo1abbbcd2016-01-28 22:20:41 -08002209 mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo);
2210 addHardwareInputLocked(inputInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002211 }
2212 }
2213
Jae Seo1abbbcd2016-01-28 22:20:41 -08002214 public void addHdmiInput(int id, TvInputInfo inputInfo) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002215 ensureHardwarePermission();
2216 ensureValidInput(inputInfo);
2217 synchronized (mLock) {
Jae Seo1abbbcd2016-01-28 22:20:41 -08002218 mTvInputHardwareManager.addHdmiInput(id, inputInfo);
2219 addHardwareInputLocked(inputInfo);
Wonsik Kim187423c2014-06-25 14:12:48 +09002220 }
2221 }
2222
Jae Seo1abbbcd2016-01-28 22:20:41 -08002223 public void removeHardwareInput(String inputId) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002224 ensureHardwarePermission();
Wonsik Kim187423c2014-06-25 14:12:48 +09002225 synchronized (mLock) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002226 ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002227 boolean removed = false;
Jae Seo1abbbcd2016-01-28 22:20:41 -08002228 for (Iterator<TvInputInfo> it = serviceState.hardwareInputList.iterator();
Wonsik Kim187423c2014-06-25 14:12:48 +09002229 it.hasNext(); ) {
2230 if (it.next().getId().equals(inputId)) {
2231 it.remove();
2232 removed = true;
2233 break;
2234 }
2235 }
2236 if (removed) {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +09002237 buildTvInputListLocked(mUserId, null);
Jae Seo1abbbcd2016-01-28 22:20:41 -08002238 mTvInputHardwareManager.removeHardwareInput(inputId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002239 } else {
Jae Seofea8dd42014-08-26 13:57:41 -07002240 Slog.e(TAG, "failed to remove input " + inputId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002241 }
Jae Seo39570912014-02-20 18:23:25 -08002242 }
2243 }
2244 }
Jae Seo31dc634be2014-04-15 17:40:23 -07002245
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002246 private final class SessionCallback extends ITvInputSessionCallback.Stub {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002247 private final SessionState mSessionState;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002248 private final InputChannel[] mChannels;
2249
2250 SessionCallback(SessionState sessionState, InputChannel[] channels) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002251 mSessionState = sessionState;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002252 mChannels = channels;
2253 }
2254
2255 @Override
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002256 public void onSessionCreated(ITvInputSession session, IBinder hardwareSessionToken) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002257 if (DEBUG) {
Jae Seo2cdb05e2016-02-04 22:17:13 +09002258 Slog.d(TAG, "onSessionCreated(inputId=" + mSessionState.inputId + ")");
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002259 }
2260 synchronized (mLock) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002261 mSessionState.session = session;
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002262 mSessionState.hardwareSessionToken = hardwareSessionToken;
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002263 if (session != null && addSessionTokenToClientStateLocked(session)) {
2264 sendSessionTokenToClientLocked(mSessionState.client,
Jae Seo2cdb05e2016-02-04 22:17:13 +09002265 mSessionState.inputId, mSessionState.sessionToken, mChannels[0],
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002266 mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002267 } else {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002268 removeSessionStateLocked(mSessionState.sessionToken, mSessionState.userId);
2269 sendSessionTokenToClientLocked(mSessionState.client,
Jae Seo2cdb05e2016-02-04 22:17:13 +09002270 mSessionState.inputId, null, null, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002271 }
2272 mChannels[0].dispose();
2273 }
2274 }
2275
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002276 private boolean addSessionTokenToClientStateLocked(ITvInputSession session) {
2277 try {
2278 session.asBinder().linkToDeath(mSessionState, 0);
2279 } catch (RemoteException e) {
2280 Slog.e(TAG, "session process has already died", e);
2281 return false;
2282 }
2283
2284 IBinder clientToken = mSessionState.client.asBinder();
Jae Seo4f1a6d42015-07-20 16:15:01 -07002285 UserState userState = getOrCreateUserStateLocked(mSessionState.userId);
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002286 ClientState clientState = userState.clientStateMap.get(clientToken);
2287 if (clientState == null) {
2288 clientState = new ClientState(clientToken, mSessionState.userId);
2289 try {
2290 clientToken.linkToDeath(clientState, 0);
2291 } catch (RemoteException e) {
2292 Slog.e(TAG, "client process has already died", e);
2293 return false;
2294 }
2295 userState.clientStateMap.put(clientToken, clientState);
2296 }
2297 clientState.sessionTokens.add(mSessionState.sessionToken);
2298 return true;
2299 }
2300
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002301 @Override
2302 public void onChannelRetuned(Uri channelUri) {
2303 synchronized (mLock) {
2304 if (DEBUG) {
2305 Slog.d(TAG, "onChannelRetuned(" + channelUri + ")");
2306 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002307 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002308 return;
2309 }
2310 try {
2311 // TODO: Consider adding this channel change in the watch log. When we do
2312 // that, how we can protect the watch log from malicious tv inputs should
2313 // be addressed. e.g. add a field which represents where the channel change
2314 // originated from.
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002315 mSessionState.client.onChannelRetuned(channelUri, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002316 } catch (RemoteException e) {
2317 Slog.e(TAG, "error in onChannelRetuned", e);
2318 }
2319 }
2320 }
2321
2322 @Override
2323 public void onTracksChanged(List<TvTrackInfo> tracks) {
2324 synchronized (mLock) {
2325 if (DEBUG) {
2326 Slog.d(TAG, "onTracksChanged(" + tracks + ")");
2327 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002328 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002329 return;
2330 }
2331 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002332 mSessionState.client.onTracksChanged(tracks, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002333 } catch (RemoteException e) {
2334 Slog.e(TAG, "error in onTracksChanged", e);
2335 }
2336 }
2337 }
2338
2339 @Override
2340 public void onTrackSelected(int type, String trackId) {
2341 synchronized (mLock) {
2342 if (DEBUG) {
2343 Slog.d(TAG, "onTrackSelected(type=" + type + ", trackId=" + trackId + ")");
2344 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002345 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002346 return;
2347 }
2348 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002349 mSessionState.client.onTrackSelected(type, trackId, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002350 } catch (RemoteException e) {
2351 Slog.e(TAG, "error in onTrackSelected", e);
2352 }
2353 }
2354 }
2355
2356 @Override
2357 public void onVideoAvailable() {
2358 synchronized (mLock) {
2359 if (DEBUG) {
2360 Slog.d(TAG, "onVideoAvailable()");
2361 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002362 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002363 return;
2364 }
2365 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002366 mSessionState.client.onVideoAvailable(mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002367 } catch (RemoteException e) {
2368 Slog.e(TAG, "error in onVideoAvailable", e);
2369 }
2370 }
2371 }
2372
2373 @Override
2374 public void onVideoUnavailable(int reason) {
2375 synchronized (mLock) {
2376 if (DEBUG) {
2377 Slog.d(TAG, "onVideoUnavailable(" + reason + ")");
2378 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002379 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002380 return;
2381 }
2382 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002383 mSessionState.client.onVideoUnavailable(reason, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002384 } catch (RemoteException e) {
2385 Slog.e(TAG, "error in onVideoUnavailable", e);
2386 }
2387 }
2388 }
2389
2390 @Override
2391 public void onContentAllowed() {
2392 synchronized (mLock) {
2393 if (DEBUG) {
2394 Slog.d(TAG, "onContentAllowed()");
2395 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002396 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002397 return;
2398 }
2399 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002400 mSessionState.client.onContentAllowed(mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002401 } catch (RemoteException e) {
2402 Slog.e(TAG, "error in onContentAllowed", e);
2403 }
2404 }
2405 }
2406
2407 @Override
2408 public void onContentBlocked(String rating) {
2409 synchronized (mLock) {
2410 if (DEBUG) {
2411 Slog.d(TAG, "onContentBlocked()");
2412 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002413 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002414 return;
2415 }
2416 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002417 mSessionState.client.onContentBlocked(rating, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002418 } catch (RemoteException e) {
2419 Slog.e(TAG, "error in onContentBlocked", e);
2420 }
2421 }
2422 }
2423
2424 @Override
2425 public void onLayoutSurface(int left, int top, int right, int bottom) {
2426 synchronized (mLock) {
2427 if (DEBUG) {
2428 Slog.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top
2429 + ", right=" + right + ", bottom=" + bottom + ",)");
2430 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002431 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002432 return;
2433 }
2434 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002435 mSessionState.client.onLayoutSurface(left, top, right, bottom,
2436 mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002437 } catch (RemoteException e) {
2438 Slog.e(TAG, "error in onLayoutSurface", e);
2439 }
2440 }
2441 }
2442
2443 @Override
2444 public void onSessionEvent(String eventType, Bundle eventArgs) {
2445 synchronized (mLock) {
2446 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002447 Slog.d(TAG, "onEvent(eventType=" + eventType + ", eventArgs=" + eventArgs
2448 + ")");
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002449 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002450 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002451 return;
2452 }
2453 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002454 mSessionState.client.onSessionEvent(eventType, eventArgs, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002455 } catch (RemoteException e) {
2456 Slog.e(TAG, "error in onSessionEvent", e);
2457 }
2458 }
2459 }
Dongwon Kang6f0240c2015-03-31 17:56:36 -07002460
2461 @Override
2462 public void onTimeShiftStatusChanged(int status) {
2463 synchronized (mLock) {
2464 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002465 Slog.d(TAG, "onTimeShiftStatusChanged(status=" + status + ")");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07002466 }
2467 if (mSessionState.session == null || mSessionState.client == null) {
2468 return;
2469 }
2470 try {
2471 mSessionState.client.onTimeShiftStatusChanged(status, mSessionState.seq);
2472 } catch (RemoteException e) {
2473 Slog.e(TAG, "error in onTimeShiftStatusChanged", e);
2474 }
2475 }
2476 }
2477
2478 @Override
2479 public void onTimeShiftStartPositionChanged(long timeMs) {
2480 synchronized (mLock) {
2481 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002482 Slog.d(TAG, "onTimeShiftStartPositionChanged(timeMs=" + timeMs + ")");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07002483 }
2484 if (mSessionState.session == null || mSessionState.client == null) {
2485 return;
2486 }
2487 try {
2488 mSessionState.client.onTimeShiftStartPositionChanged(timeMs, mSessionState.seq);
2489 } catch (RemoteException e) {
2490 Slog.e(TAG, "error in onTimeShiftStartPositionChanged", e);
2491 }
2492 }
2493 }
2494
2495 @Override
2496 public void onTimeShiftCurrentPositionChanged(long timeMs) {
2497 synchronized (mLock) {
2498 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002499 Slog.d(TAG, "onTimeShiftCurrentPositionChanged(timeMs=" + timeMs + ")");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07002500 }
2501 if (mSessionState.session == null || mSessionState.client == null) {
2502 return;
2503 }
2504 try {
2505 mSessionState.client.onTimeShiftCurrentPositionChanged(timeMs,
2506 mSessionState.seq);
2507 } catch (RemoteException e) {
2508 Slog.e(TAG, "error in onTimeShiftCurrentPositionChanged", e);
2509 }
2510 }
2511 }
Jae Seoa826d012016-01-18 13:03:35 -08002512
2513 // For the recording session only
2514 @Override
Dongwon Kangb55c7512016-03-01 09:36:07 -08002515 public void onTuned(Uri channelUri) {
Jae Seoa826d012016-01-18 13:03:35 -08002516 synchronized (mLock) {
2517 if (DEBUG) {
Jae Seoe3c11e82016-02-08 23:18:49 -08002518 Slog.d(TAG, "onTuned()");
Jae Seoa826d012016-01-18 13:03:35 -08002519 }
2520 if (mSessionState.session == null || mSessionState.client == null) {
2521 return;
2522 }
2523 try {
Dongwon Kangb55c7512016-03-01 09:36:07 -08002524 mSessionState.client.onTuned(mSessionState.seq, channelUri);
Jae Seoa826d012016-01-18 13:03:35 -08002525 } catch (RemoteException e) {
Jae Seoe3c11e82016-02-08 23:18:49 -08002526 Slog.e(TAG, "error in onTuned", e);
Jae Seoa826d012016-01-18 13:03:35 -08002527 }
2528 }
2529 }
2530
2531 // For the recording session only
2532 @Override
2533 public void onRecordingStopped(Uri recordedProgramUri) {
2534 synchronized (mLock) {
2535 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002536 Slog.d(TAG, "onRecordingStopped(recordedProgramUri=" + recordedProgramUri
2537 + ")");
Jae Seoa826d012016-01-18 13:03:35 -08002538 }
2539 if (mSessionState.session == null || mSessionState.client == null) {
2540 return;
2541 }
2542 try {
2543 mSessionState.client.onRecordingStopped(recordedProgramUri, mSessionState.seq);
2544 } catch (RemoteException e) {
2545 Slog.e(TAG, "error in onRecordingStopped", e);
2546 }
2547 }
2548 }
2549
2550 // For the recording session only
2551 @Override
2552 public void onError(int error) {
2553 synchronized (mLock) {
2554 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002555 Slog.d(TAG, "onError(error=" + error + ")");
Jae Seoa826d012016-01-18 13:03:35 -08002556 }
2557 if (mSessionState.session == null || mSessionState.client == null) {
2558 return;
2559 }
2560 try {
2561 mSessionState.client.onError(error, mSessionState.seq);
2562 } catch (RemoteException e) {
2563 Slog.e(TAG, "error in onError", e);
2564 }
2565 }
2566 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002567 }
2568
2569 private static final class WatchLogHandler extends Handler {
Jae Seo7eb75df2014-08-08 22:20:48 -07002570 // There are only two kinds of watch events that can happen on the system:
2571 // 1. The current TV input session is tuned to a new channel.
2572 // 2. The session is released for some reason.
2573 // The former indicates the end of the previous log entry, if any, followed by the start of
2574 // a new entry. The latter indicates the end of the most recent entry for the given session.
2575 // Here the system supplies the database the smallest set of information only that is
2576 // sufficient to consolidate the log entries while minimizing database operations in the
2577 // system service.
Jae Seo8c375fe2015-06-23 20:33:25 -07002578 static final int MSG_LOG_WATCH_START = 1;
2579 static final int MSG_LOG_WATCH_END = 2;
2580 static final int MSG_SWITCH_CONTENT_RESOLVER = 3;
Jae Seo31dc634be2014-04-15 17:40:23 -07002581
Jae Seo8c375fe2015-06-23 20:33:25 -07002582 private ContentResolver mContentResolver;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002583
Jae Seo8c375fe2015-06-23 20:33:25 -07002584 WatchLogHandler(ContentResolver contentResolver, Looper looper) {
Jae Seo31dc634be2014-04-15 17:40:23 -07002585 super(looper);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002586 mContentResolver = contentResolver;
Jae Seo31dc634be2014-04-15 17:40:23 -07002587 }
2588
2589 @Override
2590 public void handleMessage(Message msg) {
2591 switch (msg.what) {
Jae Seo7eb75df2014-08-08 22:20:48 -07002592 case MSG_LOG_WATCH_START: {
Jae Seo31dc634be2014-04-15 17:40:23 -07002593 SomeArgs args = (SomeArgs) msg.obj;
Jae Seo7eb75df2014-08-08 22:20:48 -07002594 String packageName = (String) args.arg1;
2595 long watchStartTime = (long) args.arg2;
2596 long channelId = (long) args.arg3;
2597 Bundle tuneParams = (Bundle) args.arg4;
2598 IBinder sessionToken = (IBinder) args.arg5;
2599
2600 ContentValues values = new ContentValues();
2601 values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
2602 values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
2603 watchStartTime);
2604 values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
2605 if (tuneParams != null) {
2606 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS,
2607 encodeTuneParams(tuneParams));
2608 }
2609 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
2610 sessionToken.toString());
2611
2612 mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
Jae Seo31dc634be2014-04-15 17:40:23 -07002613 args.recycle();
Jae Seo8c375fe2015-06-23 20:33:25 -07002614 break;
Jae Seo31dc634be2014-04-15 17:40:23 -07002615 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002616 case MSG_LOG_WATCH_END: {
Jae Seo31dc634be2014-04-15 17:40:23 -07002617 SomeArgs args = (SomeArgs) msg.obj;
Jae Seo7eb75df2014-08-08 22:20:48 -07002618 IBinder sessionToken = (IBinder) args.arg1;
2619 long watchEndTime = (long) args.arg2;
2620
2621 ContentValues values = new ContentValues();
2622 values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
2623 watchEndTime);
2624 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
2625 sessionToken.toString());
2626
2627 mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
Jae Seo31dc634be2014-04-15 17:40:23 -07002628 args.recycle();
Jae Seo8c375fe2015-06-23 20:33:25 -07002629 break;
2630 }
2631 case MSG_SWITCH_CONTENT_RESOLVER: {
2632 mContentResolver = (ContentResolver) msg.obj;
2633 break;
Jae Seo31dc634be2014-04-15 17:40:23 -07002634 }
2635 default: {
Jae Seo8c375fe2015-06-23 20:33:25 -07002636 Slog.w(TAG, "unhandled message code: " + msg.what);
2637 break;
Jae Seo31dc634be2014-04-15 17:40:23 -07002638 }
2639 }
2640 }
2641
Jae Seo7eb75df2014-08-08 22:20:48 -07002642 private String encodeTuneParams(Bundle tuneParams) {
2643 StringBuilder builder = new StringBuilder();
2644 Set<String> keySet = tuneParams.keySet();
2645 Iterator<String> it = keySet.iterator();
2646 while (it.hasNext()) {
2647 String key = it.next();
2648 Object value = tuneParams.get(key);
2649 if (value == null) {
2650 continue;
Jae Seo579befe2014-08-06 19:18:37 -07002651 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002652 builder.append(replaceEscapeCharacters(key));
2653 builder.append("=");
2654 builder.append(replaceEscapeCharacters(value.toString()));
2655 if (it.hasNext()) {
2656 builder.append(", ");
Jae Seo31dc634be2014-04-15 17:40:23 -07002657 }
2658 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002659 return builder.toString();
Jae Seo31dc634be2014-04-15 17:40:23 -07002660 }
2661
Jae Seo7eb75df2014-08-08 22:20:48 -07002662 private String replaceEscapeCharacters(String src) {
2663 final char ESCAPE_CHARACTER = '%';
2664 final String ENCODING_TARGET_CHARACTERS = "%=,";
2665 StringBuilder builder = new StringBuilder();
2666 for (char ch : src.toCharArray()) {
2667 if (ENCODING_TARGET_CHARACTERS.indexOf(ch) >= 0) {
2668 builder.append(ESCAPE_CHARACTER);
Chulwoo Lee8d4ded02014-07-10 03:56:39 +09002669 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002670 builder.append(ch);
Jae Seo31dc634be2014-04-15 17:40:23 -07002671 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002672 return builder.toString();
Jae Seo579befe2014-08-06 19:18:37 -07002673 }
Jae Seo31dc634be2014-04-15 17:40:23 -07002674 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002675
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002676 private final class HardwareListener implements TvInputHardwareManager.Listener {
Wonsik Kim187423c2014-06-25 14:12:48 +09002677 @Override
2678 public void onStateChanged(String inputId, int state) {
Wonsik Kim969167d2014-06-24 16:33:17 +09002679 synchronized (mLock) {
2680 setStateLocked(inputId, state, mCurrentUserId);
2681 }
2682 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002683
2684 @Override
2685 public void onHardwareDeviceAdded(TvInputHardwareInfo info) {
2686 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002687 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002688 // Broadcast the event to all hardware inputs.
2689 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002690 if (!serviceState.isHardware || serviceState.service == null) continue;
Wonsik Kim187423c2014-06-25 14:12:48 +09002691 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002692 serviceState.service.notifyHardwareAdded(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09002693 } catch (RemoteException e) {
2694 Slog.e(TAG, "error in notifyHardwareAdded", e);
2695 }
2696 }
2697 }
2698 }
2699
2700 @Override
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002701 public void onHardwareDeviceRemoved(TvInputHardwareInfo info) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002702 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002703 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002704 // Broadcast the event to all hardware inputs.
2705 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002706 if (!serviceState.isHardware || serviceState.service == null) continue;
Wonsik Kim187423c2014-06-25 14:12:48 +09002707 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002708 serviceState.service.notifyHardwareRemoved(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09002709 } catch (RemoteException e) {
2710 Slog.e(TAG, "error in notifyHardwareRemoved", e);
2711 }
2712 }
2713 }
2714 }
2715
2716 @Override
Jae Seo546c6352014-08-07 11:57:01 -07002717 public void onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002718 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002719 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002720 // Broadcast the event to all hardware inputs.
2721 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002722 if (!serviceState.isHardware || serviceState.service == null) continue;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002723 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002724 serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002725 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002726 Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002727 }
2728 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002729 }
2730 }
2731
2732 @Override
Jae Seo546c6352014-08-07 11:57:01 -07002733 public void onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002734 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002735 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002736 // Broadcast the event to all hardware inputs.
2737 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002738 if (!serviceState.isHardware || serviceState.service == null) continue;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002739 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002740 serviceState.service.notifyHdmiDeviceRemoved(deviceInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002741 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002742 Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002743 }
2744 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002745 }
2746 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09002747
2748 @Override
Wonsik Kime92f8572014-08-12 18:30:24 +09002749 public void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo deviceInfo) {
2750 synchronized (mLock) {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002751 Integer state;
Wonsik Kime92f8572014-08-12 18:30:24 +09002752 switch (deviceInfo.getDevicePowerStatus()) {
2753 case HdmiControlManager.POWER_STATUS_ON:
2754 state = INPUT_STATE_CONNECTED;
2755 break;
2756 case HdmiControlManager.POWER_STATUS_STANDBY:
2757 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON:
2758 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY:
2759 state = INPUT_STATE_CONNECTED_STANDBY;
2760 break;
2761 case HdmiControlManager.POWER_STATUS_UNKNOWN:
2762 default:
2763 state = null;
2764 break;
2765 }
2766 if (state != null) {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002767 setStateLocked(inputId, state, mCurrentUserId);
Wonsik Kime92f8572014-08-12 18:30:24 +09002768 }
2769 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09002770 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002771 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09002772
2773 private static class SessionNotFoundException extends IllegalArgumentException {
Dongwon Kangfdce9e52014-12-04 18:08:00 +09002774 public SessionNotFoundException(String name) {
2775 super(name);
2776 }
2777 }
Jae Seo39570912014-02-20 18:23:25 -08002778}