blob: 2946bb594634615b4f1f58c89d65088b72f04880 [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
Youngsang Cho19c47d72016-05-05 09:21:24 -0700160 @Override
161 public void onUnlockUser(int userHandle) {
162 if (DEBUG) Slog.d(TAG, "onUnlockUser(userHandle=" + userHandle + ")");
163 synchronized (mLock) {
164 if (mCurrentUserId != userHandle) {
165 return;
166 }
167 buildTvInputListLocked(mCurrentUserId, null);
168 buildTvContentRatingSystemListLocked(mCurrentUserId);
169 }
170 }
171
Jae Seo39570912014-02-20 18:23:25 -0800172 private void registerBroadcastReceivers() {
173 PackageMonitor monitor = new PackageMonitor() {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900174 private void buildTvInputList(String[] packages) {
175 synchronized (mLock) {
Dongwon Kanga897e5f2016-04-19 13:39:47 -0700176 if (mCurrentUserId == getChangingUserId()) {
177 buildTvInputListLocked(mCurrentUserId, packages);
178 buildTvContentRatingSystemListLocked(mCurrentUserId);
179 }
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900180 }
181 }
182
183 @Override
184 public void onPackageUpdateFinished(String packageName, int uid) {
185 if (DEBUG) Slog.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")");
186 // This callback is invoked when the TV input is reinstalled.
187 // In this case, isReplacing() always returns true.
188 buildTvInputList(new String[] { packageName });
189 }
190
191 @Override
192 public void onPackagesAvailable(String[] packages) {
193 if (DEBUG) {
194 Slog.d(TAG, "onPackagesAvailable(packages=" + Arrays.toString(packages) + ")");
195 }
196 // This callback is invoked when the media on which some packages exist become
197 // available.
198 if (isReplacing()) {
199 buildTvInputList(packages);
200 }
201 }
202
203 @Override
204 public void onPackagesUnavailable(String[] packages) {
205 // This callback is invoked when the media on which some packages exist become
206 // unavailable.
207 if (DEBUG) {
208 Slog.d(TAG, "onPackagesUnavailable(packages=" + Arrays.toString(packages)
209 + ")");
210 }
211 if (isReplacing()) {
212 buildTvInputList(packages);
213 }
214 }
215
Jae Seo39570912014-02-20 18:23:25 -0800216 @Override
217 public void onSomePackagesChanged() {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900218 // TODO: Use finer-grained methods(e.g. onPackageAdded, onPackageRemoved) to manage
219 // the TV inputs.
Dongwon Kang426c9a42014-08-26 17:39:21 +0900220 if (DEBUG) Slog.d(TAG, "onSomePackagesChanged()");
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900221 if (isReplacing()) {
222 if (DEBUG) Slog.d(TAG, "Skipped building TV input list due to replacing");
223 // When the package is updated, buildTvInputListLocked is called in other
224 // methods instead.
225 return;
Jae Seo39570912014-02-20 18:23:25 -0800226 }
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900227 buildTvInputList(null);
Jae Seo39570912014-02-20 18:23:25 -0800228 }
Jae Seo5c80ad22014-06-12 19:52:58 -0700229
230 @Override
Dongwon Kang31a8f842015-04-08 18:26:23 -0700231 public boolean onPackageChanged(String packageName, int uid, String[] components) {
232 // The input list needs to be updated in any cases, regardless of whether
233 // it happened to the whole package or a specific component. Returning true so that
234 // the update can be handled in {@link #onSomePackagesChanged}.
235 return true;
236 }
237
238 @Override
Jae Seo5c80ad22014-06-12 19:52:58 -0700239 public void onPackageRemoved(String packageName, int uid) {
240 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700241 UserState userState = getOrCreateUserStateLocked(getChangingUserId());
Wonsik Kim969167d2014-06-24 16:33:17 +0900242 if (!userState.packageSet.contains(packageName)) {
Jae Seo5c80ad22014-06-12 19:52:58 -0700243 // Not a TV input package.
244 return;
245 }
246 }
247
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700248 ArrayList<ContentProviderOperation> operations = new ArrayList<>();
Jae Seo5c80ad22014-06-12 19:52:58 -0700249
250 String selection = TvContract.BaseTvColumns.COLUMN_PACKAGE_NAME + "=?";
251 String[] selectionArgs = { packageName };
252
253 operations.add(ContentProviderOperation.newDelete(TvContract.Channels.CONTENT_URI)
254 .withSelection(selection, selectionArgs).build());
255 operations.add(ContentProviderOperation.newDelete(TvContract.Programs.CONTENT_URI)
256 .withSelection(selection, selectionArgs).build());
257 operations.add(ContentProviderOperation
258 .newDelete(TvContract.WatchedPrograms.CONTENT_URI)
259 .withSelection(selection, selectionArgs).build());
260
261 ContentProviderResult[] results = null;
262 try {
Jae Seo8c375fe2015-06-23 20:33:25 -0700263 ContentResolver cr = getContentResolverForUser(getChangingUserId());
264 results = cr.applyBatch(TvContract.AUTHORITY, operations);
Jae Seo5c80ad22014-06-12 19:52:58 -0700265 } catch (RemoteException | OperationApplicationException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700266 Slog.e(TAG, "error in applyBatch", e);
Jae Seo5c80ad22014-06-12 19:52:58 -0700267 }
268
269 if (DEBUG) {
270 Slog.d(TAG, "onPackageRemoved(packageName=" + packageName + ", uid=" + uid
271 + ")");
272 Slog.d(TAG, "results=" + results);
273 }
274 }
Jae Seo39570912014-02-20 18:23:25 -0800275 };
276 monitor.register(mContext, null, UserHandle.ALL, true);
277
278 IntentFilter intentFilter = new IntentFilter();
279 intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
280 intentFilter.addAction(Intent.ACTION_USER_REMOVED);
281 mContext.registerReceiverAsUser(new BroadcastReceiver() {
282 @Override
283 public void onReceive(Context context, Intent intent) {
284 String action = intent.getAction();
285 if (Intent.ACTION_USER_SWITCHED.equals(action)) {
286 switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
287 } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
288 removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
289 }
290 }
291 }, UserHandle.ALL, intentFilter, null, null);
292 }
293
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900294 private static boolean hasHardwarePermission(PackageManager pm, ComponentName component) {
Wonsik Kim187423c2014-06-25 14:12:48 +0900295 return pm.checkPermission(android.Manifest.permission.TV_INPUT_HARDWARE,
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900296 component.getPackageName()) == PackageManager.PERMISSION_GRANTED;
Wonsik Kim187423c2014-06-25 14:12:48 +0900297 }
298
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900299 private void buildTvInputListLocked(int userId, String[] updatedPackages) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700300 UserState userState = getOrCreateUserStateLocked(userId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900301 userState.packageSet.clear();
Jae Seo39570912014-02-20 18:23:25 -0800302
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900303 if (DEBUG) Slog.d(TAG, "buildTvInputList");
Jae Seo39570912014-02-20 18:23:25 -0800304 PackageManager pm = mContext.getPackageManager();
Jae Seo76976fa2015-05-20 21:51:16 -0700305 List<ResolveInfo> services = pm.queryIntentServicesAsUser(
Chulwoo Leee7bb7d62014-05-27 14:10:37 +0900306 new Intent(TvInputService.SERVICE_INTERFACE),
Jae Seo76976fa2015-05-20 21:51:16 -0700307 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
308 userId);
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700309 List<TvInputInfo> inputList = new ArrayList<>();
Jae Seo39570912014-02-20 18:23:25 -0800310 for (ResolveInfo ri : services) {
311 ServiceInfo si = ri.serviceInfo;
312 if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900313 Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
Jae Seo39570912014-02-20 18:23:25 -0800314 + android.Manifest.permission.BIND_TV_INPUT);
315 continue;
316 }
Jae Seo9cc28e52014-08-12 16:45:58 -0700317
318 ComponentName component = new ComponentName(si.packageName, si.name);
319 if (hasHardwarePermission(pm, component)) {
320 ServiceState serviceState = userState.serviceStateMap.get(component);
321 if (serviceState == null) {
Jae Seoc980f43d2016-02-09 23:46:58 -0800322 // New hardware input found. Create a new ServiceState and connect to the
323 // service to populate the hardware list.
Jae Seo9cc28e52014-08-12 16:45:58 -0700324 serviceState = new ServiceState(component, userId);
325 userState.serviceStateMap.put(component, serviceState);
Wonsik Kimf271eac2014-08-30 12:55:10 +0900326 updateServiceConnectionLocked(component, userId);
Wonsik Kim187423c2014-06-25 14:12:48 +0900327 } else {
Jae Seo1abbbcd2016-01-28 22:20:41 -0800328 inputList.addAll(serviceState.hardwareInputList);
Jae Seo9cc28e52014-08-12 16:45:58 -0700329 }
330 } else {
331 try {
Jae Seoc03671f2016-01-26 15:15:22 -0800332 TvInputInfo info = new TvInputInfo.Builder(mContext, ri).build();
333 inputList.add(info);
Jae Seo9cc28e52014-08-12 16:45:58 -0700334 } catch (XmlPullParserException | IOException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700335 Slog.e(TAG, "failed to load TV input " + si.name, e);
Jae Seo9cc28e52014-08-12 16:45:58 -0700336 continue;
Wonsik Kim969167d2014-06-24 16:33:17 +0900337 }
Chulwoo Leee7bb7d62014-05-27 14:10:37 +0900338 }
Jae Seo9cc28e52014-08-12 16:45:58 -0700339 userState.packageSet.add(si.packageName);
340 }
341
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700342 Map<String, TvInputState> inputMap = new HashMap<>();
Jae Seo9cc28e52014-08-12 16:45:58 -0700343 for (TvInputInfo info : inputList) {
Jae Seofea8dd42014-08-26 13:57:41 -0700344 if (DEBUG) {
345 Slog.d(TAG, "add " + info.getId());
346 }
Jae Seoabda4202016-01-28 19:13:04 -0800347 TvInputState inputState = userState.inputMap.get(info.getId());
348 if (inputState == null) {
349 inputState = new TvInputState();
Jae Seo9cc28e52014-08-12 16:45:58 -0700350 }
Shubang2263fb02016-05-13 16:51:53 -0700351 inputState.info = info;
Jae Seoabda4202016-01-28 19:13:04 -0800352 inputMap.put(info.getId(), inputState);
Jae Seo39570912014-02-20 18:23:25 -0800353 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900354
355 for (String inputId : inputMap.keySet()) {
356 if (!userState.inputMap.containsKey(inputId)) {
357 notifyInputAddedLocked(userState, inputId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900358 } else if (updatedPackages != null) {
359 // Notify the package updates
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +0900360 ComponentName component = inputMap.get(inputId).info.getComponent();
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900361 for (String updatedPackage : updatedPackages) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +0900362 if (component.getPackageName().equals(updatedPackage)) {
363 updateServiceConnectionLocked(component, userId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900364 notifyInputUpdatedLocked(userState, inputId);
365 break;
366 }
367 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900368 }
369 }
370
371 for (String inputId : userState.inputMap.keySet()) {
372 if (!inputMap.containsKey(inputId)) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900373 TvInputInfo info = userState.inputMap.get(inputId).info;
Dongwon Kang426c9a42014-08-26 17:39:21 +0900374 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
375 if (serviceState != null) {
376 abortPendingCreateSessionRequestsLocked(serviceState, inputId, userId);
377 }
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900378 notifyInputRemovedLocked(userState, inputId);
379 }
380 }
381
382 userState.inputMap.clear();
383 userState.inputMap = inputMap;
Jae Seo9c165d62014-08-25 14:39:26 -0700384 }
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900385
Jae Seo9c165d62014-08-25 14:39:26 -0700386 private void buildTvContentRatingSystemListLocked(int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700387 UserState userState = getOrCreateUserStateLocked(userId);
Jae Seo9c165d62014-08-25 14:39:26 -0700388 userState.contentRatingSystemList.clear();
389
390 final PackageManager pm = mContext.getPackageManager();
391 Intent intent = new Intent(TvInputManager.ACTION_QUERY_CONTENT_RATING_SYSTEMS);
392 for (ResolveInfo resolveInfo :
393 pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA)) {
394 ActivityInfo receiver = resolveInfo.activityInfo;
395 Bundle metaData = receiver.metaData;
396 if (metaData == null) {
397 continue;
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900398 }
Jae Seo9c165d62014-08-25 14:39:26 -0700399
400 int xmlResId = metaData.getInt(TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS);
401 if (xmlResId == 0) {
402 Slog.w(TAG, "Missing meta-data '"
403 + TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS + "' on receiver "
404 + receiver.packageName + "/" + receiver.name);
405 continue;
406 }
407 userState.contentRatingSystemList.add(
408 TvContentRatingSystemInfo.createTvContentRatingSystemInfo(xmlResId,
409 receiver.applicationInfo));
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900410 }
Jae Seo39570912014-02-20 18:23:25 -0800411 }
412
413 private void switchUser(int userId) {
414 synchronized (mLock) {
415 if (mCurrentUserId == userId) {
416 return;
417 }
shubang8049f202016-04-25 11:21:42 -0700418 UserState userState = mUserStates.get(mCurrentUserId);
419 List<SessionState> sessionStatesToRelease = new ArrayList<>();
420 for (SessionState sessionState : userState.sessionStateMap.values()) {
421 if (sessionState.session != null && !sessionState.isRecordingSession) {
422 sessionStatesToRelease.add(sessionState);
423 }
424 }
425 for (SessionState sessionState : sessionStatesToRelease) {
426 try {
427 sessionState.session.release();
428 } catch (RemoteException e) {
429 Slog.e(TAG, "error in release", e);
430 }
431 clearSessionAndNotifyClientLocked(sessionState);
432 }
433
434 for (Iterator<ComponentName> it = userState.serviceStateMap.keySet().iterator();
435 it.hasNext(); ) {
436 ComponentName component = it.next();
437 ServiceState serviceState = userState.serviceStateMap.get(component);
438 if (serviceState != null && serviceState.sessionTokens.isEmpty()) {
439 if (serviceState.callback != null) {
440 try {
441 serviceState.service.unregisterCallback(serviceState.callback);
442 } catch (RemoteException e) {
443 Slog.e(TAG, "error in unregisterCallback", e);
444 }
445 }
446 mContext.unbindService(serviceState.connection);
447 it.remove();
448 }
449 }
Jae Seo39570912014-02-20 18:23:25 -0800450
Jae Seo8c375fe2015-06-23 20:33:25 -0700451 mCurrentUserId = userId;
Jae Seo4f1a6d42015-07-20 16:15:01 -0700452 getOrCreateUserStateLocked(userId);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900453 buildTvInputListLocked(userId, null);
Jae Seo9c165d62014-08-25 14:39:26 -0700454 buildTvContentRatingSystemListLocked(userId);
Jae Seo8c375fe2015-06-23 20:33:25 -0700455 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_SWITCH_CONTENT_RESOLVER,
456 getContentResolverForUser(userId)).sendToTarget();
Jae Seo39570912014-02-20 18:23:25 -0800457 }
458 }
459
shubang8049f202016-04-25 11:21:42 -0700460 private void clearSessionAndNotifyClientLocked(SessionState state) {
461 if (state.client != null) {
462 try {
463 state.client.onSessionReleased(state.seq);
464 } catch(RemoteException e) {
465 Slog.e(TAG, "error in onSessionReleased", e);
466 }
467 }
468 // If there are any other sessions based on this session, they should be released.
469 UserState userState = getOrCreateUserStateLocked(state.userId);
470 for (SessionState sessionState : userState.sessionStateMap.values()) {
471 if (state.sessionToken == sessionState.hardwareSessionToken) {
472 releaseSessionLocked(sessionState.sessionToken, Process.SYSTEM_UID, state.userId);
473 try {
474 sessionState.client.onSessionReleased(sessionState.seq);
475 } catch (RemoteException e) {
476 Slog.e(TAG, "error in onSessionReleased", e);
477 }
478 }
479 }
480 removeSessionStateLocked(state.sessionToken, state.userId);
481 }
482
Jae Seo39570912014-02-20 18:23:25 -0800483 private void removeUser(int userId) {
484 synchronized (mLock) {
Jae Seob06cb882014-04-09 12:08:17 -0700485 UserState userState = mUserStates.get(userId);
486 if (userState == null) {
487 return;
488 }
shubang8049f202016-04-25 11:21:42 -0700489 // Release all created sessions.
490 for (SessionState state : userState.sessionStateMap.values()) {
491 if (state.session != null) {
492 try {
493 state.session.release();
494 } catch (RemoteException e) {
495 Slog.e(TAG, "error in release", e);
496 }
497 }
498 }
499 userState.sessionStateMap.clear();
500
501 // Unregister all callbacks and unbind all services.
502 for (ServiceState serviceState : userState.serviceStateMap.values()) {
503 if (serviceState.service != null) {
504 if (serviceState.callback != null) {
505 try {
506 serviceState.service.unregisterCallback(serviceState.callback);
507 } catch (RemoteException e) {
508 Slog.e(TAG, "error in unregisterCallback", e);
509 }
510 }
511 mContext.unbindService(serviceState.connection);
512 }
513 }
514 userState.serviceStateMap.clear();
Jae Seo39570912014-02-20 18:23:25 -0800515
Jae Seofea8dd42014-08-26 13:57:41 -0700516 // Clear everything else.
517 userState.inputMap.clear();
518 userState.packageSet.clear();
Jae Seo9c165d62014-08-25 14:39:26 -0700519 userState.contentRatingSystemList.clear();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900520 userState.clientStateMap.clear();
Jae Seofea8dd42014-08-26 13:57:41 -0700521 userState.callbackSet.clear();
522 userState.mainSessionToken = null;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900523
Jae Seo39570912014-02-20 18:23:25 -0800524 mUserStates.remove(userId);
525 }
526 }
527
Jae Seo8c375fe2015-06-23 20:33:25 -0700528 private ContentResolver getContentResolverForUser(int userId) {
529 UserHandle user = new UserHandle(userId);
530 Context context;
531 try {
532 context = mContext.createPackageContextAsUser("android", 0, user);
533 } catch (NameNotFoundException e) {
Jae Seo2a2b2992016-01-12 23:13:14 -0800534 Slog.e(TAG, "failed to create package context as user " + user);
Jae Seo8c375fe2015-06-23 20:33:25 -0700535 context = mContext;
536 }
537 return context.getContentResolver();
538 }
539
Jae Seo4f1a6d42015-07-20 16:15:01 -0700540 private UserState getOrCreateUserStateLocked(int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800541 UserState userState = mUserStates.get(userId);
542 if (userState == null) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700543 userState = new UserState(mContext, userId);
544 mUserStates.put(userId, userState);
Jae Seo39570912014-02-20 18:23:25 -0800545 }
546 return userState;
547 }
548
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900549 private ServiceState getServiceStateLocked(ComponentName component, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700550 UserState userState = getOrCreateUserStateLocked(userId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900551 ServiceState serviceState = userState.serviceStateMap.get(component);
Jae Seo39570912014-02-20 18:23:25 -0800552 if (serviceState == null) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900553 throw new IllegalStateException("Service state not found for " + component + " (userId="
Sungsoo Lim7de5e232014-04-12 16:51:27 +0900554 + userId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800555 }
556 return serviceState;
557 }
558
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900559 private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700560 UserState userState = getOrCreateUserStateLocked(userId);
Jae Seo39570912014-02-20 18:23:25 -0800561 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
562 if (sessionState == null) {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900563 throw new SessionNotFoundException("Session state not found for token " + sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800564 }
565 // Only the application that requested this session or the system can access it.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900566 if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) {
Jae Seo39570912014-02-20 18:23:25 -0800567 throw new SecurityException("Illegal access to the session with token " + sessionToken
568 + " from uid " + callingUid);
569 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900570 return sessionState;
571 }
572
573 private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900574 return getSessionLocked(getSessionStateLocked(sessionToken, callingUid, userId));
575 }
576
577 private ITvInputSession getSessionLocked(SessionState sessionState) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900578 ITvInputSession session = sessionState.session;
Jae Seo39570912014-02-20 18:23:25 -0800579 if (session == null) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900580 throw new IllegalStateException("Session not yet created for token "
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900581 + sessionState.sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800582 }
583 return session;
584 }
585
586 private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
587 String methodName) {
588 return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
589 false, methodName, null);
590 }
591
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900592 private void updateServiceConnectionLocked(ComponentName component, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700593 UserState userState = getOrCreateUserStateLocked(userId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900594 ServiceState serviceState = userState.serviceStateMap.get(component);
Jae Seo39570912014-02-20 18:23:25 -0800595 if (serviceState == null) {
596 return;
597 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900598 if (serviceState.reconnecting) {
599 if (!serviceState.sessionTokens.isEmpty()) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900600 // wait until all the sessions are removed.
601 return;
602 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900603 serviceState.reconnecting = false;
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900604 }
shubang8049f202016-04-25 11:21:42 -0700605
606 boolean shouldBind;
607 if (userId == mCurrentUserId) {
608 shouldBind = !serviceState.sessionTokens.isEmpty() || serviceState.isHardware;
609 } else {
610 // For a non-current user,
611 // if sessionTokens is not empty, it contains recording sessions only
612 // because other sessions must have been removed while switching user
613 // and non-recording sessions are not created by createSession().
614 shouldBind = !serviceState.sessionTokens.isEmpty();
615 }
616
617 if (serviceState.service == null && shouldBind) {
Jae Seo39570912014-02-20 18:23:25 -0800618 // This means that the service is not yet connected but its state indicates that we
619 // have pending requests. Then, connect the service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900620 if (serviceState.bound) {
Jae Seo39570912014-02-20 18:23:25 -0800621 // We have already bound to the service so we don't try to bind again until after we
622 // unbind later on.
623 return;
624 }
625 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900626 Slog.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800627 }
Sungsoo Limd6672b52014-04-30 10:43:26 +0900628
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900629 Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900630 serviceState.bound = mContext.bindServiceAsUser(
Dianne Hackbornd69e4c12015-04-24 09:54:54 -0700631 i, serviceState.connection,
632 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
633 new UserHandle(userId));
shubang8049f202016-04-25 11:21:42 -0700634 } else if (serviceState.service != null && !shouldBind) {
Jae Seo39570912014-02-20 18:23:25 -0800635 // This means that the service is already connected but its state indicates that we have
636 // nothing to do with it. Then, disconnect the service.
637 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900638 Slog.d(TAG, "unbindService(service=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -0800639 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900640 mContext.unbindService(serviceState.connection);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +0900641 userState.serviceStateMap.remove(component);
Jae Seo39570912014-02-20 18:23:25 -0800642 }
643 }
644
Dongwon Kang426c9a42014-08-26 17:39:21 +0900645 private void abortPendingCreateSessionRequestsLocked(ServiceState serviceState,
646 String inputId, int userId) {
647 // Let clients know the create session requests are failed.
Jae Seo4f1a6d42015-07-20 16:15:01 -0700648 UserState userState = getOrCreateUserStateLocked(userId);
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900649 List<SessionState> sessionsToAbort = new ArrayList<>();
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900650 for (IBinder sessionToken : serviceState.sessionTokens) {
Dongwon Kang426c9a42014-08-26 17:39:21 +0900651 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900652 if (sessionState.session == null && (inputId == null
Jae Seo2cdb05e2016-02-04 22:17:13 +0900653 || sessionState.inputId.equals(inputId))) {
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900654 sessionsToAbort.add(sessionState);
Dongwon Kang426c9a42014-08-26 17:39:21 +0900655 }
656 }
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900657 for (SessionState sessionState : sessionsToAbort) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900658 removeSessionStateLocked(sessionState.sessionToken, sessionState.userId);
659 sendSessionTokenToClientLocked(sessionState.client,
Jae Seo2cdb05e2016-02-04 22:17:13 +0900660 sessionState.inputId, null, null, sessionState.seq);
Dongwon Kangf7f49dd2014-08-27 20:48:22 +0900661 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900662 updateServiceConnectionLocked(serviceState.component, userId);
Dongwon Kang426c9a42014-08-26 17:39:21 +0900663 }
664
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900665 private void createSessionInternalLocked(ITvInputService service, IBinder sessionToken,
666 int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700667 UserState userState = getOrCreateUserStateLocked(userId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900668 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Jae Seo39570912014-02-20 18:23:25 -0800669 if (DEBUG) {
Jae Seo2cdb05e2016-02-04 22:17:13 +0900670 Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.inputId + ")");
Jae Seo39570912014-02-20 18:23:25 -0800671 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900672 InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
Jae Seo6a6059a2014-04-17 21:35:29 -0700673
Jae Seo39570912014-02-20 18:23:25 -0800674 // Set up a callback to send the session token.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900675 ITvInputSessionCallback callback = new SessionCallback(sessionState, channels);
Jae Seo39570912014-02-20 18:23:25 -0800676
677 // Create a session. When failed, send a null token immediately.
678 try {
Jae Seoa826d012016-01-18 13:03:35 -0800679 if (sessionState.isRecordingSession) {
Jae Seo2cdb05e2016-02-04 22:17:13 +0900680 service.createRecordingSession(callback, sessionState.inputId);
Jae Seoa826d012016-01-18 13:03:35 -0800681 } else {
Jae Seo2cdb05e2016-02-04 22:17:13 +0900682 service.createSession(channels[1], callback, sessionState.inputId);
Jae Seoa826d012016-01-18 13:03:35 -0800683 }
Jae Seo39570912014-02-20 18:23:25 -0800684 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900685 Slog.e(TAG, "error in createSession", e);
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900686 removeSessionStateLocked(sessionToken, userId);
Jae Seo2cdb05e2016-02-04 22:17:13 +0900687 sendSessionTokenToClientLocked(sessionState.client, sessionState.inputId, null,
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900688 null, sessionState.seq);
Jae Seo39570912014-02-20 18:23:25 -0800689 }
Jae Seo6a6059a2014-04-17 21:35:29 -0700690 channels[1].dispose();
Jae Seo39570912014-02-20 18:23:25 -0800691 }
692
Sungsoo Limd6672b52014-04-30 10:43:26 +0900693 private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId,
Jae Seo5c80ad22014-06-12 19:52:58 -0700694 IBinder sessionToken, InputChannel channel, int seq) {
Jae Seo39570912014-02-20 18:23:25 -0800695 try {
Sungsoo Limd6672b52014-04-30 10:43:26 +0900696 client.onSessionCreated(inputId, sessionToken, channel, seq);
Jae Seofea8dd42014-08-26 13:57:41 -0700697 } catch (RemoteException e) {
698 Slog.e(TAG, "error in onSessionCreated", e);
Jae Seo39570912014-02-20 18:23:25 -0800699 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900700 }
Jae Seo39570912014-02-20 18:23:25 -0800701
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900702 private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900703 SessionState sessionState = null;
704 try {
705 sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
706 if (sessionState.session != null) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700707 UserState userState = getOrCreateUserStateLocked(userId);
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900708 if (sessionToken == userState.mainSessionToken) {
709 setMainLocked(sessionToken, false, callingUid, userId);
710 }
Jae Seoe3c11e82016-02-08 23:18:49 -0800711 sessionState.session.release();
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900712 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900713 } catch (RemoteException | SessionNotFoundException e) {
714 Slog.e(TAG, "error in releaseSession", e);
715 } finally {
716 if (sessionState != null) {
717 sessionState.session = null;
718 }
Jae Seo39570912014-02-20 18:23:25 -0800719 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900720 removeSessionStateLocked(sessionToken, userId);
Jae Seo39570912014-02-20 18:23:25 -0800721 }
722
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900723 private void removeSessionStateLocked(IBinder sessionToken, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700724 UserState userState = getOrCreateUserStateLocked(userId);
Ji-Hwan Leeabca0ee2014-07-24 17:34:19 +0900725 if (sessionToken == userState.mainSessionToken) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900726 if (DEBUG) {
727 Slog.d(TAG, "mainSessionToken=null");
728 }
Ji-Hwan Leeabca0ee2014-07-24 17:34:19 +0900729 userState.mainSessionToken = null;
730 }
731
732 // Remove the session state from the global session state map of the current user.
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900733 SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
734
Chulwoo Lee8d4ded02014-07-10 03:56:39 +0900735 if (sessionState == null) {
736 return;
737 }
738
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900739 // Also remove the session token from the session token list of the current client and
740 // service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900741 ClientState clientState = userState.clientStateMap.get(sessionState.client.asBinder());
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900742 if (clientState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900743 clientState.sessionTokens.remove(sessionToken);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900744 if (clientState.isEmpty()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900745 userState.clientStateMap.remove(sessionState.client.asBinder());
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +0900746 }
747 }
748
Jae Seo2cdb05e2016-02-04 22:17:13 +0900749 ServiceState serviceState = userState.serviceStateMap.get(sessionState.componentName);
750 if (serviceState != null) {
751 serviceState.sessionTokens.remove(sessionToken);
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900752 }
Jae Seo2cdb05e2016-02-04 22:17:13 +0900753 updateServiceConnectionLocked(sessionState.componentName, userId);
Jae Seo7eb75df2014-08-08 22:20:48 -0700754
755 // Log the end of watch.
756 SomeArgs args = SomeArgs.obtain();
757 args.arg1 = sessionToken;
758 args.arg2 = System.currentTimeMillis();
759 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_END, args).sendToTarget();
Dongwon Kangfd5b72f2014-04-15 17:23:24 +0900760 }
761
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900762 private void setMainLocked(IBinder sessionToken, boolean isMain, int callingUid, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900763 try {
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900764 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
765 if (sessionState.hardwareSessionToken != null) {
766 sessionState = getSessionStateLocked(sessionState.hardwareSessionToken,
767 Process.SYSTEM_UID, userId);
768 }
Jae Seo2cdb05e2016-02-04 22:17:13 +0900769 ServiceState serviceState = getServiceStateLocked(sessionState.componentName, userId);
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900770 if (!serviceState.isHardware) {
771 return;
772 }
773 ITvInputSession session = getSessionLocked(sessionState);
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900774 session.setMain(isMain);
Dongwon Kangfdce9e52014-12-04 18:08:00 +0900775 } catch (RemoteException | SessionNotFoundException e) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900776 Slog.e(TAG, "error in setMain", e);
777 }
778 }
779
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900780 private void notifyInputAddedLocked(UserState userState, String inputId) {
781 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700782 Slog.d(TAG, "notifyInputAddedLocked(inputId=" + inputId + ")");
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900783 }
784 for (ITvInputManagerCallback callback : userState.callbackSet) {
785 try {
786 callback.onInputAdded(inputId);
787 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700788 Slog.e(TAG, "failed to report added input to callback", e);
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900789 }
790 }
791 }
792
793 private void notifyInputRemovedLocked(UserState userState, String inputId) {
794 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700795 Slog.d(TAG, "notifyInputRemovedLocked(inputId=" + inputId + ")");
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900796 }
797 for (ITvInputManagerCallback callback : userState.callbackSet) {
798 try {
799 callback.onInputRemoved(inputId);
800 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700801 Slog.e(TAG, "failed to report removed input to callback", e);
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900802 }
803 }
804 }
805
Chulwoo Lee19ba61a2014-09-03 00:59:35 +0900806 private void notifyInputUpdatedLocked(UserState userState, String inputId) {
807 if (DEBUG) {
808 Slog.d(TAG, "notifyInputUpdatedLocked(inputId=" + inputId + ")");
809 }
810 for (ITvInputManagerCallback callback : userState.callbackSet) {
811 try {
812 callback.onInputUpdated(inputId);
813 } catch (RemoteException e) {
814 Slog.e(TAG, "failed to report updated input to callback", e);
815 }
816 }
817 }
818
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900819 private void notifyInputStateChangedLocked(UserState userState, String inputId,
Wonsik Kim969167d2014-06-24 16:33:17 +0900820 int state, ITvInputManagerCallback targetCallback) {
821 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -0700822 Slog.d(TAG, "notifyInputStateChangedLocked(inputId=" + inputId
823 + ", state=" + state + ")");
Wonsik Kim969167d2014-06-24 16:33:17 +0900824 }
825 if (targetCallback == null) {
826 for (ITvInputManagerCallback callback : userState.callbackSet) {
827 try {
828 callback.onInputStateChanged(inputId, state);
829 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700830 Slog.e(TAG, "failed to report state change to callback", e);
Wonsik Kim969167d2014-06-24 16:33:17 +0900831 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900832 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900833 } else {
834 try {
835 targetCallback.onInputStateChanged(inputId, state);
836 } catch (RemoteException e) {
Jae Seofea8dd42014-08-26 13:57:41 -0700837 Slog.e(TAG, "failed to report state change to callback", e);
Wonsik Kim969167d2014-06-24 16:33:17 +0900838 }
839 }
840 }
841
Jae Seoaa5605f2016-02-13 01:38:08 -0800842 private void updateTvInputInfoLocked(UserState userState, TvInputInfo inputInfo) {
Jae Seoa826d012016-01-18 13:03:35 -0800843 if (DEBUG) {
Jae Seoaa5605f2016-02-13 01:38:08 -0800844 Slog.d(TAG, "updateTvInputInfoLocked(inputInfo=" + inputInfo + ")");
Jae Seoa826d012016-01-18 13:03:35 -0800845 }
Jae Seoabda4202016-01-28 19:13:04 -0800846 String inputId = inputInfo.getId();
847 TvInputState inputState = userState.inputMap.get(inputId);
848 if (inputState == null) {
849 Slog.e(TAG, "failed to set input info - unknown input id " + inputId);
850 return;
851 }
Jae Seoabda4202016-01-28 19:13:04 -0800852 inputState.info = inputInfo;
853
Jae Seoa826d012016-01-18 13:03:35 -0800854 for (ITvInputManagerCallback callback : userState.callbackSet) {
855 try {
Jae Seoaa5605f2016-02-13 01:38:08 -0800856 callback.onTvInputInfoUpdated(inputInfo);
Jae Seoa826d012016-01-18 13:03:35 -0800857 } catch (RemoteException e) {
Jae Seoaa5605f2016-02-13 01:38:08 -0800858 Slog.e(TAG, "failed to report updated input info to callback", e);
Jae Seoa826d012016-01-18 13:03:35 -0800859 }
860 }
861 }
862
Wonsik Kim969167d2014-06-24 16:33:17 +0900863 private void setStateLocked(String inputId, int state, int userId) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700864 UserState userState = getOrCreateUserStateLocked(userId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900865 TvInputState inputState = userState.inputMap.get(inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900866 ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent());
867 int oldState = inputState.state;
868 inputState.state = state;
869 if (serviceState != null && serviceState.service == null
shubang8049f202016-04-25 11:21:42 -0700870 && (!serviceState.sessionTokens.isEmpty() || serviceState.isHardware)) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900871 // We don't notify state change while reconnecting. It should remain disconnected.
872 return;
873 }
874 if (oldState != state) {
Jaewan Kim8e6b51b2014-07-15 13:01:57 +0900875 notifyInputStateChangedLocked(userState, inputId, state, null);
Sungsoo Lim2b35a722014-04-17 17:09:15 +0900876 }
877 }
878
Jae Seo39570912014-02-20 18:23:25 -0800879 private final class BinderService extends ITvInputManager.Stub {
880 @Override
881 public List<TvInputInfo> getTvInputList(int userId) {
882 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
883 Binder.getCallingUid(), userId, "getTvInputList");
884 final long identity = Binder.clearCallingIdentity();
885 try {
886 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700887 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700888 List<TvInputInfo> inputList = new ArrayList<>();
Wonsik Kim969167d2014-06-24 16:33:17 +0900889 for (TvInputState state : userState.inputMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900890 inputList.add(state.info);
Jae Seo39570912014-02-20 18:23:25 -0800891 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900892 return inputList;
Jae Seo39570912014-02-20 18:23:25 -0800893 }
894 } finally {
895 Binder.restoreCallingIdentity(identity);
896 }
Jae Seo39570912014-02-20 18:23:25 -0800897 }
898
899 @Override
Jae Seob3758052014-07-12 19:25:24 -0700900 public TvInputInfo getTvInputInfo(String inputId, int userId) {
901 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
902 Binder.getCallingUid(), userId, "getTvInputInfo");
903 final long identity = Binder.clearCallingIdentity();
904 try {
905 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700906 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seob3758052014-07-12 19:25:24 -0700907 TvInputState state = userState.inputMap.get(inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +0900908 return state == null ? null : state.info;
Jae Seob3758052014-07-12 19:25:24 -0700909 }
910 } finally {
911 Binder.restoreCallingIdentity(identity);
912 }
913 }
914
Jae Seoaa5605f2016-02-13 01:38:08 -0800915 public void updateTvInputInfo(TvInputInfo inputInfo, int userId) {
Jae Seoc2a89512016-01-28 10:38:11 -0800916 String inputInfoPackageName = inputInfo.getServiceInfo().packageName;
917 String callingPackageName = getCallingPackageName();
918 if (!TextUtils.equals(inputInfoPackageName, callingPackageName)) {
919 throw new IllegalArgumentException("calling package " + callingPackageName
920 + " is not allowed to change TvInputInfo for " + inputInfoPackageName);
921 }
922
923 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
Jae Seoaa5605f2016-02-13 01:38:08 -0800924 Binder.getCallingUid(), userId, "updateTvInputInfo");
Jae Seoc2a89512016-01-28 10:38:11 -0800925 final long identity = Binder.clearCallingIdentity();
926 try {
927 synchronized (mLock) {
928 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seoaa5605f2016-02-13 01:38:08 -0800929 updateTvInputInfoLocked(userState, inputInfo);
Jae Seoc2a89512016-01-28 10:38:11 -0800930 }
931 } finally {
932 Binder.restoreCallingIdentity(identity);
933 }
934 }
935
936 private String getCallingPackageName() {
937 final String[] packages = mContext.getPackageManager().getPackagesForUid(
938 Binder.getCallingUid());
939 if (packages != null && packages.length > 0) {
940 return packages[0];
941 }
942 return "unknown";
943 }
944
Jae Seob3758052014-07-12 19:25:24 -0700945 @Override
Dongwon Kang993f81e2014-11-27 19:34:18 +0900946 public int getTvInputState(String inputId, int userId) {
947 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
948 Binder.getCallingUid(), userId, "getTvInputState");
949 final long identity = Binder.clearCallingIdentity();
950 try {
951 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700952 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Dongwon Kang993f81e2014-11-27 19:34:18 +0900953 TvInputState state = userState.inputMap.get(inputId);
Jae Seo82fce642015-04-20 15:37:50 -0700954 return state == null ? INPUT_STATE_CONNECTED : state.state;
Dongwon Kang993f81e2014-11-27 19:34:18 +0900955 }
956 } finally {
957 Binder.restoreCallingIdentity(identity);
958 }
959 }
960
961 @Override
Jae Seo9c165d62014-08-25 14:39:26 -0700962 public List<TvContentRatingSystemInfo> getTvContentRatingSystemList(int userId) {
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900963 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
Jae Seo9c165d62014-08-25 14:39:26 -0700964 Binder.getCallingUid(), userId, "getTvContentRatingSystemList");
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900965 final long identity = Binder.clearCallingIdentity();
966 try {
967 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700968 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo9c165d62014-08-25 14:39:26 -0700969 return userState.contentRatingSystemList;
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +0900970 }
971 } finally {
972 Binder.restoreCallingIdentity(identity);
973 }
974 }
975
976 @Override
Wonsik Kim969167d2014-06-24 16:33:17 +0900977 public void registerCallback(final ITvInputManagerCallback callback, int userId) {
Jae Seo39570912014-02-20 18:23:25 -0800978 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
979 Binder.getCallingUid(), userId, "registerCallback");
980 final long identity = Binder.clearCallingIdentity();
981 try {
982 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -0700983 final UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Wonsik Kim969167d2014-06-24 16:33:17 +0900984 userState.callbackSet.add(callback);
Jae Seofea8dd42014-08-26 13:57:41 -0700985 try {
986 callback.asBinder().linkToDeath(new IBinder.DeathRecipient() {
987 @Override
988 public void binderDied() {
989 synchronized (mLock) {
990 if (userState.callbackSet != null) {
991 userState.callbackSet.remove(callback);
992 }
993 }
994 }
995 }, 0);
996 } catch (RemoteException e) {
997 Slog.e(TAG, "client process has already died", e);
998 }
Jae Seo39570912014-02-20 18:23:25 -0800999 }
1000 } finally {
1001 Binder.restoreCallingIdentity(identity);
1002 }
1003 }
1004
1005 @Override
Wonsik Kim969167d2014-06-24 16:33:17 +09001006 public void unregisterCallback(ITvInputManagerCallback callback, int userId) {
Jae Seo39570912014-02-20 18:23:25 -08001007 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1008 Binder.getCallingUid(), userId, "unregisterCallback");
1009 final long identity = Binder.clearCallingIdentity();
1010 try {
1011 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001012 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Wonsik Kim969167d2014-06-24 16:33:17 +09001013 userState.callbackSet.remove(callback);
Jae Seo39570912014-02-20 18:23:25 -08001014 }
1015 } finally {
1016 Binder.restoreCallingIdentity(identity);
1017 }
1018 }
1019
1020 @Override
Jae Seo783645e2014-07-28 17:30:50 +09001021 public boolean isParentalControlsEnabled(int userId) {
1022 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1023 Binder.getCallingUid(), userId, "isParentalControlsEnabled");
1024 final long identity = Binder.clearCallingIdentity();
1025 try {
1026 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001027 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +09001028 return userState.persistentDataStore.isParentalControlsEnabled();
1029 }
1030 } finally {
1031 Binder.restoreCallingIdentity(identity);
1032 }
1033 }
1034
1035 @Override
1036 public void setParentalControlsEnabled(boolean enabled, int userId) {
1037 ensureParentalControlsPermission();
1038 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1039 Binder.getCallingUid(), userId, "setParentalControlsEnabled");
1040 final long identity = Binder.clearCallingIdentity();
1041 try {
1042 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001043 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +09001044 userState.persistentDataStore.setParentalControlsEnabled(enabled);
1045 }
1046 } finally {
1047 Binder.restoreCallingIdentity(identity);
1048 }
1049 }
1050
1051 @Override
1052 public boolean isRatingBlocked(String rating, int userId) {
1053 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1054 Binder.getCallingUid(), userId, "isRatingBlocked");
1055 final long identity = Binder.clearCallingIdentity();
1056 try {
1057 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001058 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +09001059 return userState.persistentDataStore.isRatingBlocked(
1060 TvContentRating.unflattenFromString(rating));
1061 }
1062 } finally {
1063 Binder.restoreCallingIdentity(identity);
1064 }
1065 }
1066
1067 @Override
1068 public List<String> getBlockedRatings(int userId) {
1069 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1070 Binder.getCallingUid(), userId, "getBlockedRatings");
1071 final long identity = Binder.clearCallingIdentity();
1072 try {
1073 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001074 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001075 List<String> ratings = new ArrayList<>();
Jae Seo783645e2014-07-28 17:30:50 +09001076 for (TvContentRating rating
1077 : userState.persistentDataStore.getBlockedRatings()) {
1078 ratings.add(rating.flattenToString());
1079 }
1080 return ratings;
1081 }
1082 } finally {
1083 Binder.restoreCallingIdentity(identity);
1084 }
1085 }
1086
1087 @Override
1088 public void addBlockedRating(String rating, int userId) {
1089 ensureParentalControlsPermission();
1090 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1091 Binder.getCallingUid(), userId, "addBlockedRating");
1092 final long identity = Binder.clearCallingIdentity();
1093 try {
1094 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001095 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +09001096 userState.persistentDataStore.addBlockedRating(
1097 TvContentRating.unflattenFromString(rating));
1098 }
1099 } finally {
1100 Binder.restoreCallingIdentity(identity);
1101 }
1102 }
1103
1104 @Override
1105 public void removeBlockedRating(String rating, int userId) {
1106 ensureParentalControlsPermission();
1107 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1108 Binder.getCallingUid(), userId, "removeBlockedRating");
1109 final long identity = Binder.clearCallingIdentity();
1110 try {
1111 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001112 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo783645e2014-07-28 17:30:50 +09001113 userState.persistentDataStore.removeBlockedRating(
1114 TvContentRating.unflattenFromString(rating));
1115 }
1116 } finally {
1117 Binder.restoreCallingIdentity(identity);
1118 }
1119 }
1120
1121 private void ensureParentalControlsPermission() {
Jae Seofc836f62014-08-27 00:47:56 +00001122 if (mContext.checkCallingPermission(
1123 android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
1124 != PackageManager.PERMISSION_GRANTED) {
1125 throw new SecurityException(
1126 "The caller does not have parental controls permission");
1127 }
Jae Seo783645e2014-07-28 17:30:50 +09001128 }
1129
1130 @Override
Sungsoo Limd6672b52014-04-30 10:43:26 +09001131 public void createSession(final ITvInputClient client, final String inputId,
Jae Seoa826d012016-01-18 13:03:35 -08001132 boolean isRecordingSession, int seq, int userId) {
Jae Seo39570912014-02-20 18:23:25 -08001133 final int callingUid = Binder.getCallingUid();
1134 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1135 userId, "createSession");
1136 final long identity = Binder.clearCallingIdentity();
1137 try {
1138 synchronized (mLock) {
shubang8049f202016-04-25 11:21:42 -07001139 if (userId != mCurrentUserId && !isRecordingSession) {
1140 // A non-recording session of a backgroud (non-current) user
1141 // should not be created.
1142 // Let the client get onConnectionFailed callback for this case.
1143 sendSessionTokenToClientLocked(client, inputId, null, null, seq);
1144 return;
1145 }
Jae Seo4f1a6d42015-07-20 16:15:01 -07001146 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Dongwon Kang426c9a42014-08-26 17:39:21 +09001147 TvInputState inputState = userState.inputMap.get(inputId);
1148 if (inputState == null) {
1149 Slog.w(TAG, "Failed to find input state for inputId=" + inputId);
1150 sendSessionTokenToClientLocked(client, inputId, null, null, seq);
1151 return;
1152 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001153 TvInputInfo info = inputState.info;
Wonsik Kim187423c2014-06-25 14:12:48 +09001154 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
Jae Seo39570912014-02-20 18:23:25 -08001155 if (serviceState == null) {
Wonsik Kim187423c2014-06-25 14:12:48 +09001156 serviceState = new ServiceState(info.getComponent(), resolvedUserId);
1157 userState.serviceStateMap.put(info.getComponent(), serviceState);
Jae Seo39570912014-02-20 18:23:25 -08001158 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001159 // Send a null token immediately while reconnecting.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001160 if (serviceState.reconnecting) {
Jae Seo5c80ad22014-06-12 19:52:58 -07001161 sendSessionTokenToClientLocked(client, inputId, null, null, seq);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001162 return;
1163 }
1164
1165 // Create a new session token and a session state.
1166 IBinder sessionToken = new Binder();
Jae Seo2cdb05e2016-02-04 22:17:13 +09001167 SessionState sessionState = new SessionState(sessionToken, info.getId(),
1168 info.getComponent(), isRecordingSession, client, seq, callingUid,
1169 resolvedUserId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001170
1171 // Add them to the global session state map of the current user.
1172 userState.sessionStateMap.put(sessionToken, sessionState);
1173
1174 // Also, add them to the session state map of the current service.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001175 serviceState.sessionTokens.add(sessionToken);
Jae Seo39570912014-02-20 18:23:25 -08001176
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001177 if (serviceState.service != null) {
1178 createSessionInternalLocked(serviceState.service, sessionToken,
Sungsoo Lim7de5e232014-04-12 16:51:27 +09001179 resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001180 } else {
Wonsik Kim187423c2014-06-25 14:12:48 +09001181 updateServiceConnectionLocked(info.getComponent(), resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001182 }
1183 }
1184 } finally {
1185 Binder.restoreCallingIdentity(identity);
1186 }
1187 }
1188
1189 @Override
1190 public void releaseSession(IBinder sessionToken, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001191 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -07001192 Slog.d(TAG, "releaseSession(sessionToken=" + sessionToken + ")");
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001193 }
Jae Seo39570912014-02-20 18:23:25 -08001194 final int callingUid = Binder.getCallingUid();
1195 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1196 userId, "releaseSession");
1197 final long identity = Binder.clearCallingIdentity();
1198 try {
1199 synchronized (mLock) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001200 releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
Jae Seo39570912014-02-20 18:23:25 -08001201 }
1202 } finally {
1203 Binder.restoreCallingIdentity(identity);
1204 }
1205 }
1206
1207 @Override
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001208 public void setMainSession(IBinder sessionToken, int userId) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001209 if (DEBUG) {
Jae Seofea8dd42014-08-26 13:57:41 -07001210 Slog.d(TAG, "setMainSession(sessionToken=" + sessionToken + ")");
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001211 }
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001212 final int callingUid = Binder.getCallingUid();
1213 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1214 userId, "setMainSession");
1215 final long identity = Binder.clearCallingIdentity();
1216 try {
1217 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001218 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001219 if (userState.mainSessionToken == sessionToken) {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001220 return;
1221 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001222 if (DEBUG) {
1223 Slog.d(TAG, "mainSessionToken=" + sessionToken);
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001224 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001225 IBinder oldMainSessionToken = userState.mainSessionToken;
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001226 userState.mainSessionToken = sessionToken;
1227
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09001228 // Inform the new main session first.
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001229 // See {@link TvInputService.Session#onSetMain}.
1230 if (sessionToken != null) {
1231 setMainLocked(sessionToken, true, callingUid, userId);
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001232 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001233 if (oldMainSessionToken != null) {
1234 setMainLocked(oldMainSessionToken, false, Process.SYSTEM_UID, userId);
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001235 }
1236 }
1237 } finally {
1238 Binder.restoreCallingIdentity(identity);
1239 }
1240 }
1241
1242 @Override
Jae Seo39570912014-02-20 18:23:25 -08001243 public void setSurface(IBinder sessionToken, Surface surface, int userId) {
1244 final int callingUid = Binder.getCallingUid();
1245 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1246 userId, "setSurface");
1247 final long identity = Binder.clearCallingIdentity();
1248 try {
1249 synchronized (mLock) {
1250 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001251 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1252 resolvedUserId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001253 if (sessionState.hardwareSessionToken == null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001254 getSessionLocked(sessionState).setSurface(surface);
1255 } else {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001256 getSessionLocked(sessionState.hardwareSessionToken,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001257 Process.SYSTEM_UID, resolvedUserId).setSurface(surface);
1258 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001259 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001260 Slog.e(TAG, "error in setSurface", e);
Jae Seo39570912014-02-20 18:23:25 -08001261 }
1262 }
1263 } finally {
Youngsang Chof8362062014-04-30 17:24:20 +09001264 if (surface != null) {
1265 // surface is not used in TvInputManagerService.
1266 surface.release();
1267 }
Jae Seo39570912014-02-20 18:23:25 -08001268 Binder.restoreCallingIdentity(identity);
1269 }
1270 }
1271
1272 @Override
Youngsang Choe821d712014-07-16 14:22:19 -07001273 public void dispatchSurfaceChanged(IBinder sessionToken, int format, int width,
1274 int height, int userId) {
1275 final int callingUid = Binder.getCallingUid();
1276 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1277 userId, "dispatchSurfaceChanged");
1278 final long identity = Binder.clearCallingIdentity();
1279 try {
1280 synchronized (mLock) {
1281 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001282 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1283 resolvedUserId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001284 getSessionLocked(sessionState).dispatchSurfaceChanged(format, width,
1285 height);
1286 if (sessionState.hardwareSessionToken != null) {
1287 getSessionLocked(sessionState.hardwareSessionToken, Process.SYSTEM_UID,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001288 resolvedUserId).dispatchSurfaceChanged(format, width, height);
1289 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001290 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Choe821d712014-07-16 14:22:19 -07001291 Slog.e(TAG, "error in dispatchSurfaceChanged", e);
1292 }
1293 }
1294 } finally {
1295 Binder.restoreCallingIdentity(identity);
1296 }
1297 }
1298
1299 @Override
Jae Seo39570912014-02-20 18:23:25 -08001300 public void setVolume(IBinder sessionToken, float volume, int userId) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001301 final float REMOTE_VOLUME_ON = 1.0f;
1302 final float REMOTE_VOLUME_OFF = 0f;
Jae Seo39570912014-02-20 18:23:25 -08001303 final int callingUid = Binder.getCallingUid();
1304 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1305 userId, "setVolume");
1306 final long identity = Binder.clearCallingIdentity();
1307 try {
1308 synchronized (mLock) {
1309 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001310 SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1311 resolvedUserId);
1312 getSessionLocked(sessionState).setVolume(volume);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001313 if (sessionState.hardwareSessionToken != null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001314 // Here, we let the hardware session know only whether volume is on or
1315 // off to prevent that the volume is controlled in the both side.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001316 getSessionLocked(sessionState.hardwareSessionToken,
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001317 Process.SYSTEM_UID, resolvedUserId).setVolume((volume > 0.0f)
1318 ? REMOTE_VOLUME_ON : REMOTE_VOLUME_OFF);
1319 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001320 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001321 Slog.e(TAG, "error in setVolume", e);
Jae Seo39570912014-02-20 18:23:25 -08001322 }
1323 }
1324 } finally {
1325 Binder.restoreCallingIdentity(identity);
1326 }
1327 }
1328
1329 @Override
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +09001330 public void tune(IBinder sessionToken, final Uri channelUri, Bundle params, int userId) {
Jae Seo39570912014-02-20 18:23:25 -08001331 final int callingUid = Binder.getCallingUid();
1332 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1333 userId, "tune");
1334 final long identity = Binder.clearCallingIdentity();
1335 try {
1336 synchronized (mLock) {
Jae Seo39570912014-02-20 18:23:25 -08001337 try {
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +09001338 getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(
1339 channelUri, params);
Jae Seoc22d0c02014-08-15 13:03:21 -07001340 if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
Youngsang Cho008f6d42014-07-22 21:29:47 -07001341 // Do not log the watch history for passthrough inputs.
1342 return;
1343 }
Jae Seo31dc634be2014-04-15 17:40:23 -07001344
Jae Seo4f1a6d42015-07-20 16:15:01 -07001345 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Jae Seo31dc634be2014-04-15 17:40:23 -07001346 SessionState sessionState = userState.sessionStateMap.get(sessionToken);
Jae Seoe3c11e82016-02-08 23:18:49 -08001347 if (sessionState.isRecordingSession) {
1348 return;
1349 }
Jae Seo31dc634be2014-04-15 17:40:23 -07001350
Jae Seo7eb75df2014-08-08 22:20:48 -07001351 // Log the start of watch.
Jae Seo31dc634be2014-04-15 17:40:23 -07001352 SomeArgs args = SomeArgs.obtain();
Jae Seo2cdb05e2016-02-04 22:17:13 +09001353 args.arg1 = sessionState.componentName.getPackageName();
Jae Seo7eb75df2014-08-08 22:20:48 -07001354 args.arg2 = System.currentTimeMillis();
1355 args.arg3 = ContentUris.parseId(channelUri);
1356 args.arg4 = params;
1357 args.arg5 = sessionToken;
1358 mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_START, args)
1359 .sendToTarget();
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001360 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001361 Slog.e(TAG, "error in tune", e);
Jae Seo39570912014-02-20 18:23:25 -08001362 }
1363 }
1364 } finally {
1365 Binder.restoreCallingIdentity(identity);
1366 }
1367 }
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001368
1369 @Override
Jae Seoa9033832015-03-11 19:29:46 -07001370 public void unblockContent(
Sungsoo Lim9bf671f2014-07-19 12:59:51 +09001371 IBinder sessionToken, String unblockedRating, int userId) {
Dongwon Kange12d8102016-03-04 16:45:39 -08001372 ensureParentalControlsPermission();
Jaewan Kim903d6b72014-07-16 11:28:56 +09001373 final int callingUid = Binder.getCallingUid();
1374 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1375 userId, "unblockContent");
1376 final long identity = Binder.clearCallingIdentity();
1377 try {
1378 synchronized (mLock) {
1379 try {
1380 getSessionLocked(sessionToken, callingUid, resolvedUserId)
Jae Seoa9033832015-03-11 19:29:46 -07001381 .unblockContent(unblockedRating);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001382 } catch (RemoteException | SessionNotFoundException e) {
Jae Seoa9033832015-03-11 19:29:46 -07001383 Slog.e(TAG, "error in unblockContent", e);
Jaewan Kim903d6b72014-07-16 11:28:56 +09001384 }
1385 }
1386 } finally {
1387 Binder.restoreCallingIdentity(identity);
1388 }
1389 }
1390
1391 @Override
Jae Seo2c1c31c2014-07-10 14:57:01 -07001392 public void setCaptionEnabled(IBinder sessionToken, boolean enabled, int userId) {
1393 final int callingUid = Binder.getCallingUid();
1394 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1395 userId, "setCaptionEnabled");
1396 final long identity = Binder.clearCallingIdentity();
1397 try {
1398 synchronized (mLock) {
1399 try {
1400 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1401 .setCaptionEnabled(enabled);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001402 } catch (RemoteException | SessionNotFoundException e) {
Jae Seo2c1c31c2014-07-10 14:57:01 -07001403 Slog.e(TAG, "error in setCaptionEnabled", e);
1404 }
1405 }
1406 } finally {
1407 Binder.restoreCallingIdentity(identity);
1408 }
1409 }
1410
1411 @Override
Jae Seo10d285a2014-07-31 22:46:47 +09001412 public void selectTrack(IBinder sessionToken, int type, String trackId, int userId) {
Dongwon Kang1f213912014-07-02 18:35:08 +09001413 final int callingUid = Binder.getCallingUid();
1414 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1415 userId, "selectTrack");
1416 final long identity = Binder.clearCallingIdentity();
1417 try {
1418 synchronized (mLock) {
1419 try {
1420 getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack(
Jae Seo10d285a2014-07-31 22:46:47 +09001421 type, trackId);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001422 } catch (RemoteException | SessionNotFoundException e) {
Dongwon Kang1f213912014-07-02 18:35:08 +09001423 Slog.e(TAG, "error in selectTrack", e);
1424 }
1425 }
1426 } finally {
1427 Binder.restoreCallingIdentity(identity);
1428 }
1429 }
1430
1431 @Override
Jae Seoa759b112014-07-18 22:16:08 -07001432 public void sendAppPrivateCommand(IBinder sessionToken, String command, Bundle data,
1433 int userId) {
1434 final int callingUid = Binder.getCallingUid();
1435 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1436 userId, "sendAppPrivateCommand");
1437 final long identity = Binder.clearCallingIdentity();
1438 try {
1439 synchronized (mLock) {
1440 try {
1441 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1442 .appPrivateCommand(command, data);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001443 } catch (RemoteException | SessionNotFoundException e) {
Jae Seofea8dd42014-08-26 13:57:41 -07001444 Slog.e(TAG, "error in appPrivateCommand", e);
Jae Seoa759b112014-07-18 22:16:08 -07001445 }
1446 }
1447 } finally {
1448 Binder.restoreCallingIdentity(identity);
1449 }
1450 }
1451
1452 @Override
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001453 public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
1454 int userId) {
1455 final int callingUid = Binder.getCallingUid();
1456 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1457 userId, "createOverlayView");
1458 final long identity = Binder.clearCallingIdentity();
1459 try {
1460 synchronized (mLock) {
1461 try {
1462 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1463 .createOverlayView(windowToken, frame);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001464 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001465 Slog.e(TAG, "error in createOverlayView", e);
1466 }
1467 }
1468 } finally {
1469 Binder.restoreCallingIdentity(identity);
1470 }
1471 }
1472
1473 @Override
1474 public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
1475 final int callingUid = Binder.getCallingUid();
1476 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1477 userId, "relayoutOverlayView");
1478 final long identity = Binder.clearCallingIdentity();
1479 try {
1480 synchronized (mLock) {
1481 try {
1482 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1483 .relayoutOverlayView(frame);
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001484 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001485 Slog.e(TAG, "error in relayoutOverlayView", e);
1486 }
1487 }
1488 } finally {
1489 Binder.restoreCallingIdentity(identity);
1490 }
1491 }
1492
1493 @Override
1494 public void removeOverlayView(IBinder sessionToken, int userId) {
1495 final int callingUid = Binder.getCallingUid();
1496 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1497 userId, "removeOverlayView");
1498 final long identity = Binder.clearCallingIdentity();
1499 try {
1500 synchronized (mLock) {
1501 try {
1502 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1503 .removeOverlayView();
Dongwon Kangfdce9e52014-12-04 18:08:00 +09001504 } catch (RemoteException | SessionNotFoundException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001505 Slog.e(TAG, "error in removeOverlayView", e);
1506 }
1507 }
1508 } finally {
1509 Binder.restoreCallingIdentity(identity);
1510 }
1511 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001512
1513 @Override
Jae Seoa826d012016-01-18 13:03:35 -08001514 public void timeShiftPlay(IBinder sessionToken, final Uri recordedProgramUri, int userId) {
1515 final int callingUid = Binder.getCallingUid();
1516 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1517 userId, "timeShiftPlay");
1518 final long identity = Binder.clearCallingIdentity();
1519 try {
1520 synchronized (mLock) {
1521 try {
1522 getSessionLocked(sessionToken, callingUid, resolvedUserId).timeShiftPlay(
1523 recordedProgramUri);
1524 } catch (RemoteException | SessionNotFoundException e) {
1525 Slog.e(TAG, "error in timeShiftPlay", e);
1526 }
1527 }
1528 } finally {
1529 Binder.restoreCallingIdentity(identity);
1530 }
1531 }
1532
1533 @Override
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001534 public void timeShiftPause(IBinder sessionToken, int userId) {
1535 final int callingUid = Binder.getCallingUid();
1536 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1537 userId, "timeShiftPause");
1538 final long identity = Binder.clearCallingIdentity();
1539 try {
1540 synchronized (mLock) {
1541 try {
Jae Seoa826d012016-01-18 13:03:35 -08001542 getSessionLocked(sessionToken, callingUid, resolvedUserId).timeShiftPause();
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001543 } catch (RemoteException | SessionNotFoundException e) {
1544 Slog.e(TAG, "error in timeShiftPause", e);
1545 }
1546 }
1547 } finally {
1548 Binder.restoreCallingIdentity(identity);
1549 }
1550 }
1551
1552 @Override
1553 public void timeShiftResume(IBinder sessionToken, int userId) {
1554 final int callingUid = Binder.getCallingUid();
1555 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1556 userId, "timeShiftResume");
1557 final long identity = Binder.clearCallingIdentity();
1558 try {
1559 synchronized (mLock) {
1560 try {
1561 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1562 .timeShiftResume();
1563 } catch (RemoteException | SessionNotFoundException e) {
1564 Slog.e(TAG, "error in timeShiftResume", e);
1565 }
1566 }
1567 } finally {
1568 Binder.restoreCallingIdentity(identity);
1569 }
1570 }
1571
1572 @Override
1573 public void timeShiftSeekTo(IBinder sessionToken, long timeMs, int userId) {
1574 final int callingUid = Binder.getCallingUid();
1575 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1576 userId, "timeShiftSeekTo");
1577 final long identity = Binder.clearCallingIdentity();
1578 try {
1579 synchronized (mLock) {
1580 try {
1581 getSessionLocked(sessionToken, callingUid, resolvedUserId)
1582 .timeShiftSeekTo(timeMs);
1583 } catch (RemoteException | SessionNotFoundException e) {
1584 Slog.e(TAG, "error in timeShiftSeekTo", e);
1585 }
1586 }
1587 } finally {
1588 Binder.restoreCallingIdentity(identity);
1589 }
1590 }
1591
1592 @Override
Jae Seo4b34cc72015-05-15 17:29:39 -07001593 public void timeShiftSetPlaybackParams(IBinder sessionToken, PlaybackParams params,
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001594 int userId) {
1595 final int callingUid = Binder.getCallingUid();
1596 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
Jae Seo4b34cc72015-05-15 17:29:39 -07001597 userId, "timeShiftSetPlaybackParams");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001598 final long identity = Binder.clearCallingIdentity();
1599 try {
1600 synchronized (mLock) {
1601 try {
1602 getSessionLocked(sessionToken, callingUid, resolvedUserId)
Jae Seo4b34cc72015-05-15 17:29:39 -07001603 .timeShiftSetPlaybackParams(params);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001604 } catch (RemoteException | SessionNotFoundException e) {
Jae Seo4b34cc72015-05-15 17:29:39 -07001605 Slog.e(TAG, "error in timeShiftSetPlaybackParams", e);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001606 }
1607 }
1608 } finally {
1609 Binder.restoreCallingIdentity(identity);
1610 }
1611 }
1612
1613 @Override
Jae Seo465f0d62015-04-06 18:40:46 -07001614 public void timeShiftEnablePositionTracking(IBinder sessionToken, boolean enable,
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001615 int userId) {
1616 final int callingUid = Binder.getCallingUid();
1617 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
Jae Seo465f0d62015-04-06 18:40:46 -07001618 userId, "timeShiftEnablePositionTracking");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001619 final long identity = Binder.clearCallingIdentity();
1620 try {
1621 synchronized (mLock) {
1622 try {
1623 getSessionLocked(sessionToken, callingUid, resolvedUserId)
Jae Seo465f0d62015-04-06 18:40:46 -07001624 .timeShiftEnablePositionTracking(enable);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001625 } catch (RemoteException | SessionNotFoundException e) {
Jae Seo465f0d62015-04-06 18:40:46 -07001626 Slog.e(TAG, "error in timeShiftEnablePositionTracking", e);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001627 }
1628 }
1629 } finally {
1630 Binder.restoreCallingIdentity(identity);
1631 }
1632 }
1633
1634 @Override
Dongwon Kang0cb52442016-04-22 11:00:03 -07001635 public void startRecording(IBinder sessionToken, @Nullable Uri programUri, int userId) {
Jae Seoa826d012016-01-18 13:03:35 -08001636 final int callingUid = Binder.getCallingUid();
1637 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1638 userId, "startRecording");
1639 final long identity = Binder.clearCallingIdentity();
1640 try {
1641 synchronized (mLock) {
1642 try {
Jae Seo4eee6a72016-02-06 11:11:35 +09001643 getSessionLocked(sessionToken, callingUid, resolvedUserId).startRecording(
Dongwon Kang0cb52442016-04-22 11:00:03 -07001644 programUri);
Jae Seoa826d012016-01-18 13:03:35 -08001645 } catch (RemoteException | SessionNotFoundException e) {
1646 Slog.e(TAG, "error in startRecording", e);
1647 }
1648 }
1649 } finally {
1650 Binder.restoreCallingIdentity(identity);
1651 }
1652 }
1653
1654 @Override
1655 public void stopRecording(IBinder sessionToken, int userId) {
1656 final int callingUid = Binder.getCallingUid();
1657 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1658 userId, "stopRecording");
1659 final long identity = Binder.clearCallingIdentity();
1660 try {
1661 synchronized (mLock) {
1662 try {
1663 getSessionLocked(sessionToken, callingUid, resolvedUserId).stopRecording();
1664 } catch (RemoteException | SessionNotFoundException e) {
1665 Slog.e(TAG, "error in stopRecording", e);
1666 }
1667 }
1668 } finally {
1669 Binder.restoreCallingIdentity(identity);
1670 }
1671 }
1672
1673 @Override
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001674 public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
Wonsik Kim969167d2014-06-24 16:33:17 +09001675 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001676 != PackageManager.PERMISSION_GRANTED) {
1677 return null;
1678 }
1679
1680 final long identity = Binder.clearCallingIdentity();
1681 try {
1682 return mTvInputHardwareManager.getHardwareList();
1683 } finally {
1684 Binder.restoreCallingIdentity(identity);
1685 }
1686 }
1687
1688 @Override
1689 public ITvInputHardware acquireTvInputHardware(int deviceId,
Wonsik Kim969167d2014-06-24 16:33:17 +09001690 ITvInputHardwareCallback callback, TvInputInfo info, int userId)
1691 throws RemoteException {
1692 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001693 != PackageManager.PERMISSION_GRANTED) {
1694 return null;
1695 }
1696
1697 final long identity = Binder.clearCallingIdentity();
1698 final int callingUid = Binder.getCallingUid();
1699 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1700 userId, "acquireTvInputHardware");
1701 try {
1702 return mTvInputHardwareManager.acquireHardware(
Wonsik Kim969167d2014-06-24 16:33:17 +09001703 deviceId, callback, info, callingUid, resolvedUserId);
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001704 } finally {
1705 Binder.restoreCallingIdentity(identity);
1706 }
1707 }
1708
1709 @Override
1710 public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
1711 throws RemoteException {
Wonsik Kim969167d2014-06-24 16:33:17 +09001712 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001713 != PackageManager.PERMISSION_GRANTED) {
1714 return;
1715 }
1716
1717 final long identity = Binder.clearCallingIdentity();
1718 final int callingUid = Binder.getCallingUid();
1719 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1720 userId, "releaseTvInputHardware");
1721 try {
1722 mTvInputHardwareManager.releaseHardware(
1723 deviceId, hardware, callingUid, resolvedUserId);
1724 } finally {
1725 Binder.restoreCallingIdentity(identity);
1726 }
1727 }
Jaewan Kime14c3f42014-06-27 13:47:48 +09001728
1729 @Override
Jaesung Chung58739e72015-04-24 19:39:59 +09001730 public List<DvbDeviceInfo> getDvbDeviceList() throws RemoteException {
1731 if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)
1732 != PackageManager.PERMISSION_GRANTED) {
1733 throw new SecurityException("Requires DVB_DEVICE permission");
1734 }
1735
1736 final long identity = Binder.clearCallingIdentity();
1737 try {
1738 ArrayList<DvbDeviceInfo> deviceInfos = new ArrayList<>();
1739 File devDirectory = new File("/dev");
1740 for (String fileName : devDirectory.list()) {
1741 Matcher matcher = sFrontEndDevicePattern.matcher(fileName);
1742 if (matcher.find()) {
1743 int adapterId = Integer.parseInt(matcher.group(1));
1744 int deviceId = Integer.parseInt(matcher.group(2));
1745 deviceInfos.add(new DvbDeviceInfo(adapterId, deviceId));
1746 }
1747 }
1748 return Collections.unmodifiableList(deviceInfos);
1749 } finally {
1750 Binder.restoreCallingIdentity(identity);
1751 }
1752 }
1753
1754 @Override
1755 public ParcelFileDescriptor openDvbDevice(DvbDeviceInfo info, int device)
1756 throws RemoteException {
1757 if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)
1758 != PackageManager.PERMISSION_GRANTED) {
1759 throw new SecurityException("Requires DVB_DEVICE permission");
1760 }
1761
1762 final long identity = Binder.clearCallingIdentity();
1763 try {
1764 String deviceFileName;
1765 switch (device) {
1766 case TvInputManager.DVB_DEVICE_DEMUX:
1767 deviceFileName = String.format("/dev/dvb%d.demux%d", info.getAdapterId(),
1768 info.getDeviceId());
1769 break;
1770 case TvInputManager.DVB_DEVICE_DVR:
1771 deviceFileName = String.format("/dev/dvb%d.dvr%d", info.getAdapterId(),
1772 info.getDeviceId());
1773 break;
1774 case TvInputManager.DVB_DEVICE_FRONTEND:
1775 deviceFileName = String.format("/dev/dvb%d.frontend%d", info.getAdapterId(),
1776 info.getDeviceId());
1777 break;
1778 default:
1779 throw new IllegalArgumentException("Invalid DVB device: " + device);
1780 }
1781 try {
1782 // The DVB frontend device only needs to be opened in read/write mode, which
1783 // allows performing tuning operations. The DVB demux and DVR device are enough
1784 // to be opened in read only mode.
1785 return ParcelFileDescriptor.open(new File(deviceFileName),
1786 TvInputManager.DVB_DEVICE_FRONTEND == device
1787 ? ParcelFileDescriptor.MODE_READ_WRITE
1788 : ParcelFileDescriptor.MODE_READ_ONLY);
1789 } catch (FileNotFoundException e) {
1790 return null;
1791 }
1792 } finally {
1793 Binder.restoreCallingIdentity(identity);
1794 }
1795 }
1796
1797 @Override
Terry Heoc086a3d2014-06-18 14:26:44 +09001798 public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int userId)
1799 throws RemoteException {
1800 if (mContext.checkCallingPermission(
1801 android.Manifest.permission.CAPTURE_TV_INPUT)
1802 != PackageManager.PERMISSION_GRANTED) {
1803 throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1804 }
1805
1806 final long identity = Binder.clearCallingIdentity();
1807 final int callingUid = Binder.getCallingUid();
1808 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1809 userId, "getAvailableTvStreamConfigList");
1810 try {
1811 return mTvInputHardwareManager.getAvailableTvStreamConfigList(
1812 inputId, callingUid, resolvedUserId);
1813 } finally {
1814 Binder.restoreCallingIdentity(identity);
1815 }
1816 }
1817
1818 @Override
1819 public boolean captureFrame(String inputId, Surface surface, TvStreamConfig config,
1820 int userId)
1821 throws RemoteException {
1822 if (mContext.checkCallingPermission(
1823 android.Manifest.permission.CAPTURE_TV_INPUT)
1824 != PackageManager.PERMISSION_GRANTED) {
1825 throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1826 }
1827
1828 final long identity = Binder.clearCallingIdentity();
1829 final int callingUid = Binder.getCallingUid();
1830 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1831 userId, "captureFrame");
1832 try {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001833 String hardwareInputId = null;
Terry Heo79124a72014-07-21 15:17:17 +09001834 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001835 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001836 if (userState.inputMap.get(inputId) == null) {
Jae Seofea8dd42014-08-26 13:57:41 -07001837 Slog.e(TAG, "input not found for " + inputId);
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001838 return false;
1839 }
1840 for (SessionState sessionState : userState.sessionStateMap.values()) {
Jae Seo2cdb05e2016-02-04 22:17:13 +09001841 if (sessionState.inputId.equals(inputId)
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001842 && sessionState.hardwareSessionToken != null) {
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001843 hardwareInputId = userState.sessionStateMap.get(
Jae Seo2cdb05e2016-02-04 22:17:13 +09001844 sessionState.hardwareSessionToken).inputId;
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001845 break;
1846 }
1847 }
Terry Heo79124a72014-07-21 15:17:17 +09001848 }
Terry Heoc086a3d2014-06-18 14:26:44 +09001849 return mTvInputHardwareManager.captureFrame(
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09001850 (hardwareInputId != null) ? hardwareInputId : inputId,
Terry Heo79124a72014-07-21 15:17:17 +09001851 surface, config, callingUid, resolvedUserId);
Terry Heoc086a3d2014-06-18 14:26:44 +09001852 } finally {
1853 Binder.restoreCallingIdentity(identity);
1854 }
1855 }
1856
1857 @Override
Terry Heodf9f0a32014-08-06 13:53:33 +09001858 public boolean isSingleSessionActive(int userId) throws RemoteException {
1859 final long identity = Binder.clearCallingIdentity();
1860 final int callingUid = Binder.getCallingUid();
1861 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1862 userId, "isSingleSessionActive");
1863 try {
1864 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07001865 UserState userState = getOrCreateUserStateLocked(resolvedUserId);
Terry Heodf9f0a32014-08-06 13:53:33 +09001866 if (userState.sessionStateMap.size() == 1) {
1867 return true;
Jae Seo93ff14b2015-06-21 14:08:54 -07001868 } else if (userState.sessionStateMap.size() == 2) {
Terry Heodf9f0a32014-08-06 13:53:33 +09001869 SessionState[] sessionStates = userState.sessionStateMap.values().toArray(
Jae Seo93ff14b2015-06-21 14:08:54 -07001870 new SessionState[2]);
Terry Heodf9f0a32014-08-06 13:53:33 +09001871 // Check if there is a wrapper input.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001872 if (sessionStates[0].hardwareSessionToken != null
1873 || sessionStates[1].hardwareSessionToken != null) {
Terry Heodf9f0a32014-08-06 13:53:33 +09001874 return true;
1875 }
1876 }
1877 return false;
1878 }
1879 } finally {
1880 Binder.restoreCallingIdentity(identity);
1881 }
1882 }
1883
1884 @Override
Jae Seo0f8fc342014-07-02 10:47:08 -07001885 @SuppressWarnings("resource")
Jaewan Kime14c3f42014-06-27 13:47:48 +09001886 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1887 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
Jae Seo0f8fc342014-07-02 10:47:08 -07001888 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
Jaewan Kime14c3f42014-06-27 13:47:48 +09001889 != PackageManager.PERMISSION_GRANTED) {
Jae Seo0f8fc342014-07-02 10:47:08 -07001890 pw.println("Permission Denial: can't dump TvInputManager from pid="
1891 + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
Jaewan Kime14c3f42014-06-27 13:47:48 +09001892 return;
1893 }
1894
1895 synchronized (mLock) {
1896 pw.println("User Ids (Current user: " + mCurrentUserId + "):");
1897 pw.increaseIndent();
1898 for (int i = 0; i < mUserStates.size(); i++) {
1899 int userId = mUserStates.keyAt(i);
1900 pw.println(Integer.valueOf(userId));
1901 }
1902 pw.decreaseIndent();
1903
1904 for (int i = 0; i < mUserStates.size(); i++) {
1905 int userId = mUserStates.keyAt(i);
Jae Seo4f1a6d42015-07-20 16:15:01 -07001906 UserState userState = getOrCreateUserStateLocked(userId);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001907 pw.println("UserState (" + userId + "):");
1908 pw.increaseIndent();
1909
Wonsik Kim969167d2014-06-24 16:33:17 +09001910 pw.println("inputMap: inputId -> TvInputState");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001911 pw.increaseIndent();
Jaewan Kim8e6b51b2014-07-15 13:01:57 +09001912 for (Map.Entry<String, TvInputState> entry: userState.inputMap.entrySet()) {
1913 pw.println(entry.getKey() + ": " + entry.getValue());
Jaewan Kime14c3f42014-06-27 13:47:48 +09001914 }
1915 pw.decreaseIndent();
1916
Wonsik Kim969167d2014-06-24 16:33:17 +09001917 pw.println("packageSet:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001918 pw.increaseIndent();
Wonsik Kim969167d2014-06-24 16:33:17 +09001919 for (String packageName : userState.packageSet) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09001920 pw.println(packageName);
1921 }
1922 pw.decreaseIndent();
1923
1924 pw.println("clientStateMap: ITvInputClient -> ClientState");
1925 pw.increaseIndent();
1926 for (Map.Entry<IBinder, ClientState> entry :
1927 userState.clientStateMap.entrySet()) {
1928 ClientState client = entry.getValue();
1929 pw.println(entry.getKey() + ": " + client);
1930
1931 pw.increaseIndent();
1932
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001933 pw.println("sessionTokens:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001934 pw.increaseIndent();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001935 for (IBinder token : client.sessionTokens) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09001936 pw.println("" + token);
1937 }
1938 pw.decreaseIndent();
1939
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001940 pw.println("clientTokens: " + client.clientToken);
1941 pw.println("userId: " + client.userId);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001942
1943 pw.decreaseIndent();
1944 }
1945 pw.decreaseIndent();
1946
Wonsik Kim187423c2014-06-25 14:12:48 +09001947 pw.println("serviceStateMap: ComponentName -> ServiceState");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001948 pw.increaseIndent();
Wonsik Kim187423c2014-06-25 14:12:48 +09001949 for (Map.Entry<ComponentName, ServiceState> entry :
Jaewan Kime14c3f42014-06-27 13:47:48 +09001950 userState.serviceStateMap.entrySet()) {
1951 ServiceState service = entry.getValue();
1952 pw.println(entry.getKey() + ": " + service);
1953
1954 pw.increaseIndent();
1955
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001956 pw.println("sessionTokens:");
Jaewan Kime14c3f42014-06-27 13:47:48 +09001957 pw.increaseIndent();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001958 for (IBinder token : service.sessionTokens) {
Jaewan Kime14c3f42014-06-27 13:47:48 +09001959 pw.println("" + token);
1960 }
1961 pw.decreaseIndent();
1962
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001963 pw.println("service: " + service.service);
1964 pw.println("callback: " + service.callback);
1965 pw.println("bound: " + service.bound);
1966 pw.println("reconnecting: " + service.reconnecting);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001967
1968 pw.decreaseIndent();
1969 }
1970 pw.decreaseIndent();
1971
1972 pw.println("sessionStateMap: ITvInputSession -> SessionState");
1973 pw.increaseIndent();
1974 for (Map.Entry<IBinder, SessionState> entry :
1975 userState.sessionStateMap.entrySet()) {
1976 SessionState session = entry.getValue();
1977 pw.println(entry.getKey() + ": " + session);
1978
1979 pw.increaseIndent();
Jae Seo2cdb05e2016-02-04 22:17:13 +09001980 pw.println("inputId: " + session.inputId);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09001981 pw.println("client: " + session.client);
1982 pw.println("seq: " + session.seq);
1983 pw.println("callingUid: " + session.callingUid);
1984 pw.println("userId: " + session.userId);
1985 pw.println("sessionToken: " + session.sessionToken);
1986 pw.println("session: " + session.session);
1987 pw.println("logUri: " + session.logUri);
1988 pw.println("hardwareSessionToken: " + session.hardwareSessionToken);
Jaewan Kime14c3f42014-06-27 13:47:48 +09001989 pw.decreaseIndent();
1990 }
1991 pw.decreaseIndent();
1992
Wonsik Kim969167d2014-06-24 16:33:17 +09001993 pw.println("callbackSet:");
1994 pw.increaseIndent();
1995 for (ITvInputManagerCallback callback : userState.callbackSet) {
1996 pw.println(callback.toString());
1997 }
1998 pw.decreaseIndent();
1999
Ji-Hwan Lee956afc22014-07-26 11:31:39 +09002000 pw.println("mainSessionToken: " + userState.mainSessionToken);
Jaewan Kime14c3f42014-06-27 13:47:48 +09002001 pw.decreaseIndent();
2002 }
2003 }
yangren8d718e12016-02-15 17:02:49 +09002004 mTvInputHardwareManager.dump(fd, writer, args);
Jaewan Kime14c3f42014-06-27 13:47:48 +09002005 }
Jae Seo39570912014-02-20 18:23:25 -08002006 }
2007
Wonsik Kim969167d2014-06-24 16:33:17 +09002008 private static final class UserState {
2009 // A mapping from the TV input id to its TvInputState.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002010 private Map<String, TvInputState> inputMap = new HashMap<>();
Wonsik Kim969167d2014-06-24 16:33:17 +09002011
2012 // A set of all TV input packages.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002013 private final Set<String> packageSet = new HashSet<>();
Jae Seo5c80ad22014-06-12 19:52:58 -07002014
Jae Seo9c165d62014-08-25 14:39:26 -07002015 // A list of all TV content rating systems defined.
2016 private final List<TvContentRatingSystemInfo>
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002017 contentRatingSystemList = new ArrayList<>();
Sungsoo Lim5c5b83f2014-07-29 11:48:36 +09002018
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002019 // A mapping from the token of a client to its state.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002020 private final Map<IBinder, ClientState> clientStateMap = new HashMap<>();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002021
Jae Seo39570912014-02-20 18:23:25 -08002022 // A mapping from the name of a TV input service to its state.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002023 private final Map<ComponentName, ServiceState> serviceStateMap = new HashMap<>();
Jae Seo39570912014-02-20 18:23:25 -08002024
2025 // A mapping from the token of a TV input session to its state.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002026 private final Map<IBinder, SessionState> sessionStateMap = new HashMap<>();
Wonsik Kim969167d2014-06-24 16:33:17 +09002027
2028 // A set of callbacks.
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002029 private final Set<ITvInputManagerCallback> callbackSet = new HashSet<>();
Terry Heo79124a72014-07-21 15:17:17 +09002030
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09002031 // The token of a "main" TV input session.
2032 private IBinder mainSessionToken = null;
Jae Seo783645e2014-07-28 17:30:50 +09002033
2034 // Persistent data store for all internal settings maintained by the TV input manager
2035 // service.
2036 private final PersistentDataStore persistentDataStore;
2037
2038 private UserState(Context context, int userId) {
2039 persistentDataStore = new PersistentDataStore(context, userId);
2040 }
Jae Seo39570912014-02-20 18:23:25 -08002041 }
2042
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002043 private final class ClientState implements IBinder.DeathRecipient {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002044 private final List<IBinder> sessionTokens = new ArrayList<>();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002045
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002046 private IBinder clientToken;
2047 private final int userId;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002048
2049 ClientState(IBinder clientToken, int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002050 this.clientToken = clientToken;
2051 this.userId = userId;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002052 }
2053
2054 public boolean isEmpty() {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002055 return sessionTokens.isEmpty();
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002056 }
2057
2058 @Override
2059 public void binderDied() {
2060 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002061 UserState userState = getOrCreateUserStateLocked(userId);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002062 // DO NOT remove the client state of clientStateMap in this method. It will be
Ji-Hwan Leea65118e2014-07-24 16:30:02 +09002063 // removed in releaseSessionLocked().
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002064 ClientState clientState = userState.clientStateMap.get(clientToken);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002065 if (clientState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002066 while (clientState.sessionTokens.size() > 0) {
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002067 releaseSessionLocked(
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002068 clientState.sessionTokens.get(0), Process.SYSTEM_UID, userId);
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002069 }
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002070 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002071 clientToken = null;
Sungsoo Lim72ad7bf2014-05-14 09:21:08 +09002072 }
2073 }
2074 }
2075
Jae Seo39570912014-02-20 18:23:25 -08002076 private final class ServiceState {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002077 private final List<IBinder> sessionTokens = new ArrayList<>();
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002078 private final ServiceConnection connection;
2079 private final ComponentName component;
2080 private final boolean isHardware;
Jae Seo1abbbcd2016-01-28 22:20:41 -08002081 private final List<TvInputInfo> hardwareInputList = new ArrayList<>();
Jae Seo39570912014-02-20 18:23:25 -08002082
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002083 private ITvInputService service;
2084 private ServiceCallback callback;
2085 private boolean bound;
2086 private boolean reconnecting;
Jae Seo39570912014-02-20 18:23:25 -08002087
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002088 private ServiceState(ComponentName component, int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002089 this.component = component;
2090 this.connection = new InputServiceConnection(component, userId);
2091 this.isHardware = hasHardwarePermission(mContext.getPackageManager(), component);
2092 }
2093 }
2094
2095 private static final class TvInputState {
2096 // A TvInputInfo object which represents the TV input.
2097 private TvInputInfo info;
2098
2099 // The state of TV input. Connected by default.
2100 private int state = INPUT_STATE_CONNECTED;
2101
2102 @Override
2103 public String toString() {
2104 return "info: " + info + "; state: " + state;
Jae Seo39570912014-02-20 18:23:25 -08002105 }
2106 }
2107
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002108 private final class SessionState implements IBinder.DeathRecipient {
Jae Seo2cdb05e2016-02-04 22:17:13 +09002109 private final String inputId;
2110 private final ComponentName componentName;
Jae Seoa826d012016-01-18 13:03:35 -08002111 private final boolean isRecordingSession;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002112 private final ITvInputClient client;
2113 private final int seq;
2114 private final int callingUid;
2115 private final int userId;
2116 private final IBinder sessionToken;
2117 private ITvInputSession session;
2118 private Uri logUri;
Dongwon Kangbd2fa2c2014-07-25 19:52:08 +09002119 // Not null if this session represents an external device connected to a hardware TV input.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002120 private IBinder hardwareSessionToken;
Jae Seo39570912014-02-20 18:23:25 -08002121
Jae Seo2cdb05e2016-02-04 22:17:13 +09002122 private SessionState(IBinder sessionToken, String inputId, ComponentName componentName,
2123 boolean isRecordingSession, ITvInputClient client, int seq, int callingUid,
2124 int userId) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002125 this.sessionToken = sessionToken;
Jae Seo2cdb05e2016-02-04 22:17:13 +09002126 this.inputId = inputId;
2127 this.componentName = componentName;
Jae Seoa826d012016-01-18 13:03:35 -08002128 this.isRecordingSession = isRecordingSession;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002129 this.client = client;
2130 this.seq = seq;
2131 this.callingUid = callingUid;
2132 this.userId = userId;
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002133 }
2134
2135 @Override
2136 public void binderDied() {
2137 synchronized (mLock) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002138 session = null;
shubang8049f202016-04-25 11:21:42 -07002139 clearSessionAndNotifyClientLocked(this);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002140 }
Jae Seo39570912014-02-20 18:23:25 -08002141 }
2142 }
2143
2144 private final class InputServiceConnection implements ServiceConnection {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002145 private final ComponentName mComponent;
Jae Seo39570912014-02-20 18:23:25 -08002146 private final int mUserId;
2147
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002148 private InputServiceConnection(ComponentName component, int userId) {
2149 mComponent = component;
Jae Seo39570912014-02-20 18:23:25 -08002150 mUserId = userId;
2151 }
2152
2153 @Override
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002154 public void onServiceConnected(ComponentName component, IBinder service) {
Jae Seo39570912014-02-20 18:23:25 -08002155 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002156 Slog.d(TAG, "onServiceConnected(component=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -08002157 }
2158 synchronized (mLock) {
Dongwon Kang81e3c3e2015-09-11 15:24:25 -07002159 UserState userState = mUserStates.get(mUserId);
2160 if (userState == null) {
2161 // The user was removed while connecting.
2162 mContext.unbindService(this);
2163 return;
2164 }
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002165 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002166 serviceState.service = ITvInputService.Stub.asInterface(service);
Jae Seo39570912014-02-20 18:23:25 -08002167
2168 // Register a callback, if we need to.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002169 if (serviceState.isHardware && serviceState.callback == null) {
2170 serviceState.callback = new ServiceCallback(mComponent, mUserId);
Jae Seo39570912014-02-20 18:23:25 -08002171 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002172 serviceState.service.registerCallback(serviceState.callback);
Jae Seo39570912014-02-20 18:23:25 -08002173 } catch (RemoteException e) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09002174 Slog.e(TAG, "error in registerCallback", e);
Jae Seo39570912014-02-20 18:23:25 -08002175 }
2176 }
2177
2178 // And create sessions, if any.
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002179 for (IBinder sessionToken : serviceState.sessionTokens) {
2180 createSessionInternalLocked(serviceState.service, sessionToken, mUserId);
Jae Seo39570912014-02-20 18:23:25 -08002181 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002182
Wonsik Kim187423c2014-06-25 14:12:48 +09002183 for (TvInputState inputState : userState.inputMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002184 if (inputState.info.getComponent().equals(component)
Jae Seo82fce642015-04-20 15:37:50 -07002185 && inputState.state != INPUT_STATE_CONNECTED) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002186 notifyInputStateChangedLocked(userState, inputState.info.getId(),
2187 inputState.state, null);
Wonsik Kim187423c2014-06-25 14:12:48 +09002188 }
2189 }
2190
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002191 if (serviceState.isHardware) {
Jae Seoc980f43d2016-02-09 23:46:58 -08002192 serviceState.hardwareInputList.clear();
2193 for (TvInputHardwareInfo hardware : mTvInputHardwareManager.getHardwareList()) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002194 try {
Jae Seoc980f43d2016-02-09 23:46:58 -08002195 serviceState.service.notifyHardwareAdded(hardware);
Wonsik Kim187423c2014-06-25 14:12:48 +09002196 } catch (RemoteException e) {
2197 Slog.e(TAG, "error in notifyHardwareAdded", e);
2198 }
2199 }
Jae Seoc980f43d2016-02-09 23:46:58 -08002200 for (HdmiDeviceInfo device : mTvInputHardwareManager.getHdmiDeviceList()) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002201 try {
Jae Seoc980f43d2016-02-09 23:46:58 -08002202 serviceState.service.notifyHdmiDeviceAdded(device);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002203 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002204 Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002205 }
2206 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002207 }
Jae Seo39570912014-02-20 18:23:25 -08002208 }
2209 }
2210
2211 @Override
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002212 public void onServiceDisconnected(ComponentName component) {
Jae Seo39570912014-02-20 18:23:25 -08002213 if (DEBUG) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002214 Slog.d(TAG, "onServiceDisconnected(component=" + component + ")");
Jae Seo39570912014-02-20 18:23:25 -08002215 }
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002216 if (!mComponent.equals(component)) {
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002217 throw new IllegalArgumentException("Mismatched ComponentName: "
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002218 + mComponent + " (expected), " + component + " (actual).");
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002219 }
2220 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002221 UserState userState = getOrCreateUserStateLocked(mUserId);
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002222 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002223 if (serviceState != null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002224 serviceState.reconnecting = true;
2225 serviceState.bound = false;
2226 serviceState.service = null;
2227 serviceState.callback = null;
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002228
Dongwon Kang426c9a42014-08-26 17:39:21 +09002229 abortPendingCreateSessionRequestsLocked(serviceState, null, mUserId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09002230 }
2231 }
Jae Seo39570912014-02-20 18:23:25 -08002232 }
2233 }
2234
2235 private final class ServiceCallback extends ITvInputServiceCallback.Stub {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002236 private final ComponentName mComponent;
Jae Seo39570912014-02-20 18:23:25 -08002237 private final int mUserId;
2238
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002239 ServiceCallback(ComponentName component, int userId) {
2240 mComponent = component;
Jae Seo39570912014-02-20 18:23:25 -08002241 mUserId = userId;
2242 }
2243
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002244 private void ensureHardwarePermission() {
2245 if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
2246 != PackageManager.PERMISSION_GRANTED) {
2247 throw new SecurityException("The caller does not have hardware permission");
2248 }
2249 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002250
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002251 private void ensureValidInput(TvInputInfo inputInfo) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002252 if (inputInfo.getId() == null || !mComponent.equals(inputInfo.getComponent())) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002253 throw new IllegalArgumentException("Invalid TvInputInfo");
2254 }
2255 }
2256
Jae Seo1abbbcd2016-01-28 22:20:41 -08002257 private void addHardwareInputLocked(TvInputInfo inputInfo) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002258 ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
Jae Seo1abbbcd2016-01-28 22:20:41 -08002259 serviceState.hardwareInputList.add(inputInfo);
Chulwoo Lee19ba61a2014-09-03 00:59:35 +09002260 buildTvInputListLocked(mUserId, null);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002261 }
2262
Jae Seo1abbbcd2016-01-28 22:20:41 -08002263 public void addHardwareInput(int deviceId, TvInputInfo inputInfo) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002264 ensureHardwarePermission();
2265 ensureValidInput(inputInfo);
2266 synchronized (mLock) {
Jae Seo1abbbcd2016-01-28 22:20:41 -08002267 mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo);
2268 addHardwareInputLocked(inputInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002269 }
2270 }
2271
Jae Seo1abbbcd2016-01-28 22:20:41 -08002272 public void addHdmiInput(int id, TvInputInfo inputInfo) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002273 ensureHardwarePermission();
2274 ensureValidInput(inputInfo);
2275 synchronized (mLock) {
Jae Seo1abbbcd2016-01-28 22:20:41 -08002276 mTvInputHardwareManager.addHdmiInput(id, inputInfo);
2277 addHardwareInputLocked(inputInfo);
Wonsik Kim187423c2014-06-25 14:12:48 +09002278 }
2279 }
2280
Jae Seo1abbbcd2016-01-28 22:20:41 -08002281 public void removeHardwareInput(String inputId) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002282 ensureHardwarePermission();
Wonsik Kim187423c2014-06-25 14:12:48 +09002283 synchronized (mLock) {
Ji-Hwan Lee9e8ade22014-07-25 20:20:38 +09002284 ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002285 boolean removed = false;
Jae Seo1abbbcd2016-01-28 22:20:41 -08002286 for (Iterator<TvInputInfo> it = serviceState.hardwareInputList.iterator();
Wonsik Kim187423c2014-06-25 14:12:48 +09002287 it.hasNext(); ) {
2288 if (it.next().getId().equals(inputId)) {
2289 it.remove();
2290 removed = true;
2291 break;
2292 }
2293 }
2294 if (removed) {
Chulwoo Lee19ba61a2014-09-03 00:59:35 +09002295 buildTvInputListLocked(mUserId, null);
Jae Seo1abbbcd2016-01-28 22:20:41 -08002296 mTvInputHardwareManager.removeHardwareInput(inputId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002297 } else {
Jae Seofea8dd42014-08-26 13:57:41 -07002298 Slog.e(TAG, "failed to remove input " + inputId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002299 }
Jae Seo39570912014-02-20 18:23:25 -08002300 }
2301 }
2302 }
Jae Seo31dc634be2014-04-15 17:40:23 -07002303
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002304 private final class SessionCallback extends ITvInputSessionCallback.Stub {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002305 private final SessionState mSessionState;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002306 private final InputChannel[] mChannels;
2307
2308 SessionCallback(SessionState sessionState, InputChannel[] channels) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002309 mSessionState = sessionState;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002310 mChannels = channels;
2311 }
2312
2313 @Override
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002314 public void onSessionCreated(ITvInputSession session, IBinder hardwareSessionToken) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002315 if (DEBUG) {
Jae Seo2cdb05e2016-02-04 22:17:13 +09002316 Slog.d(TAG, "onSessionCreated(inputId=" + mSessionState.inputId + ")");
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002317 }
2318 synchronized (mLock) {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002319 mSessionState.session = session;
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002320 mSessionState.hardwareSessionToken = hardwareSessionToken;
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002321 if (session != null && addSessionTokenToClientStateLocked(session)) {
2322 sendSessionTokenToClientLocked(mSessionState.client,
Jae Seo2cdb05e2016-02-04 22:17:13 +09002323 mSessionState.inputId, mSessionState.sessionToken, mChannels[0],
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002324 mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002325 } else {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002326 removeSessionStateLocked(mSessionState.sessionToken, mSessionState.userId);
2327 sendSessionTokenToClientLocked(mSessionState.client,
Jae Seo2cdb05e2016-02-04 22:17:13 +09002328 mSessionState.inputId, null, null, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002329 }
2330 mChannels[0].dispose();
2331 }
2332 }
2333
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002334 private boolean addSessionTokenToClientStateLocked(ITvInputSession session) {
2335 try {
2336 session.asBinder().linkToDeath(mSessionState, 0);
2337 } catch (RemoteException e) {
2338 Slog.e(TAG, "session process has already died", e);
2339 return false;
2340 }
2341
2342 IBinder clientToken = mSessionState.client.asBinder();
Jae Seo4f1a6d42015-07-20 16:15:01 -07002343 UserState userState = getOrCreateUserStateLocked(mSessionState.userId);
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002344 ClientState clientState = userState.clientStateMap.get(clientToken);
2345 if (clientState == null) {
2346 clientState = new ClientState(clientToken, mSessionState.userId);
2347 try {
2348 clientToken.linkToDeath(clientState, 0);
2349 } catch (RemoteException e) {
2350 Slog.e(TAG, "client process has already died", e);
2351 return false;
2352 }
2353 userState.clientStateMap.put(clientToken, clientState);
2354 }
2355 clientState.sessionTokens.add(mSessionState.sessionToken);
2356 return true;
2357 }
2358
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002359 @Override
2360 public void onChannelRetuned(Uri channelUri) {
2361 synchronized (mLock) {
2362 if (DEBUG) {
2363 Slog.d(TAG, "onChannelRetuned(" + channelUri + ")");
2364 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002365 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002366 return;
2367 }
2368 try {
2369 // TODO: Consider adding this channel change in the watch log. When we do
2370 // that, how we can protect the watch log from malicious tv inputs should
2371 // be addressed. e.g. add a field which represents where the channel change
2372 // originated from.
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002373 mSessionState.client.onChannelRetuned(channelUri, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002374 } catch (RemoteException e) {
2375 Slog.e(TAG, "error in onChannelRetuned", e);
2376 }
2377 }
2378 }
2379
2380 @Override
2381 public void onTracksChanged(List<TvTrackInfo> tracks) {
2382 synchronized (mLock) {
2383 if (DEBUG) {
2384 Slog.d(TAG, "onTracksChanged(" + tracks + ")");
2385 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002386 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002387 return;
2388 }
2389 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002390 mSessionState.client.onTracksChanged(tracks, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002391 } catch (RemoteException e) {
2392 Slog.e(TAG, "error in onTracksChanged", e);
2393 }
2394 }
2395 }
2396
2397 @Override
2398 public void onTrackSelected(int type, String trackId) {
2399 synchronized (mLock) {
2400 if (DEBUG) {
2401 Slog.d(TAG, "onTrackSelected(type=" + type + ", trackId=" + trackId + ")");
2402 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002403 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002404 return;
2405 }
2406 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002407 mSessionState.client.onTrackSelected(type, trackId, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002408 } catch (RemoteException e) {
2409 Slog.e(TAG, "error in onTrackSelected", e);
2410 }
2411 }
2412 }
2413
2414 @Override
2415 public void onVideoAvailable() {
2416 synchronized (mLock) {
2417 if (DEBUG) {
2418 Slog.d(TAG, "onVideoAvailable()");
2419 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002420 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002421 return;
2422 }
2423 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002424 mSessionState.client.onVideoAvailable(mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002425 } catch (RemoteException e) {
2426 Slog.e(TAG, "error in onVideoAvailable", e);
2427 }
2428 }
2429 }
2430
2431 @Override
2432 public void onVideoUnavailable(int reason) {
2433 synchronized (mLock) {
2434 if (DEBUG) {
2435 Slog.d(TAG, "onVideoUnavailable(" + reason + ")");
2436 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002437 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002438 return;
2439 }
2440 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002441 mSessionState.client.onVideoUnavailable(reason, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002442 } catch (RemoteException e) {
2443 Slog.e(TAG, "error in onVideoUnavailable", e);
2444 }
2445 }
2446 }
2447
2448 @Override
2449 public void onContentAllowed() {
2450 synchronized (mLock) {
2451 if (DEBUG) {
2452 Slog.d(TAG, "onContentAllowed()");
2453 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002454 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002455 return;
2456 }
2457 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002458 mSessionState.client.onContentAllowed(mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002459 } catch (RemoteException e) {
2460 Slog.e(TAG, "error in onContentAllowed", e);
2461 }
2462 }
2463 }
2464
2465 @Override
2466 public void onContentBlocked(String rating) {
2467 synchronized (mLock) {
2468 if (DEBUG) {
2469 Slog.d(TAG, "onContentBlocked()");
2470 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002471 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002472 return;
2473 }
2474 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002475 mSessionState.client.onContentBlocked(rating, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002476 } catch (RemoteException e) {
2477 Slog.e(TAG, "error in onContentBlocked", e);
2478 }
2479 }
2480 }
2481
2482 @Override
2483 public void onLayoutSurface(int left, int top, int right, int bottom) {
2484 synchronized (mLock) {
2485 if (DEBUG) {
2486 Slog.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top
2487 + ", right=" + right + ", bottom=" + bottom + ",)");
2488 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002489 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002490 return;
2491 }
2492 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002493 mSessionState.client.onLayoutSurface(left, top, right, bottom,
2494 mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002495 } catch (RemoteException e) {
2496 Slog.e(TAG, "error in onLayoutSurface", e);
2497 }
2498 }
2499 }
2500
2501 @Override
2502 public void onSessionEvent(String eventType, Bundle eventArgs) {
2503 synchronized (mLock) {
2504 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002505 Slog.d(TAG, "onEvent(eventType=" + eventType + ", eventArgs=" + eventArgs
2506 + ")");
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002507 }
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002508 if (mSessionState.session == null || mSessionState.client == null) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002509 return;
2510 }
2511 try {
Ji-Hwan Lee9c6b5b72014-09-17 00:22:39 +09002512 mSessionState.client.onSessionEvent(eventType, eventArgs, mSessionState.seq);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002513 } catch (RemoteException e) {
2514 Slog.e(TAG, "error in onSessionEvent", e);
2515 }
2516 }
2517 }
Dongwon Kang6f0240c2015-03-31 17:56:36 -07002518
2519 @Override
2520 public void onTimeShiftStatusChanged(int status) {
2521 synchronized (mLock) {
2522 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002523 Slog.d(TAG, "onTimeShiftStatusChanged(status=" + status + ")");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07002524 }
2525 if (mSessionState.session == null || mSessionState.client == null) {
2526 return;
2527 }
2528 try {
2529 mSessionState.client.onTimeShiftStatusChanged(status, mSessionState.seq);
2530 } catch (RemoteException e) {
2531 Slog.e(TAG, "error in onTimeShiftStatusChanged", e);
2532 }
2533 }
2534 }
2535
2536 @Override
2537 public void onTimeShiftStartPositionChanged(long timeMs) {
2538 synchronized (mLock) {
2539 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002540 Slog.d(TAG, "onTimeShiftStartPositionChanged(timeMs=" + timeMs + ")");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07002541 }
2542 if (mSessionState.session == null || mSessionState.client == null) {
2543 return;
2544 }
2545 try {
2546 mSessionState.client.onTimeShiftStartPositionChanged(timeMs, mSessionState.seq);
2547 } catch (RemoteException e) {
2548 Slog.e(TAG, "error in onTimeShiftStartPositionChanged", e);
2549 }
2550 }
2551 }
2552
2553 @Override
2554 public void onTimeShiftCurrentPositionChanged(long timeMs) {
2555 synchronized (mLock) {
2556 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002557 Slog.d(TAG, "onTimeShiftCurrentPositionChanged(timeMs=" + timeMs + ")");
Dongwon Kang6f0240c2015-03-31 17:56:36 -07002558 }
2559 if (mSessionState.session == null || mSessionState.client == null) {
2560 return;
2561 }
2562 try {
2563 mSessionState.client.onTimeShiftCurrentPositionChanged(timeMs,
2564 mSessionState.seq);
2565 } catch (RemoteException e) {
2566 Slog.e(TAG, "error in onTimeShiftCurrentPositionChanged", e);
2567 }
2568 }
2569 }
Jae Seoa826d012016-01-18 13:03:35 -08002570
2571 // For the recording session only
2572 @Override
Dongwon Kangb55c7512016-03-01 09:36:07 -08002573 public void onTuned(Uri channelUri) {
Jae Seoa826d012016-01-18 13:03:35 -08002574 synchronized (mLock) {
2575 if (DEBUG) {
Jae Seoe3c11e82016-02-08 23:18:49 -08002576 Slog.d(TAG, "onTuned()");
Jae Seoa826d012016-01-18 13:03:35 -08002577 }
2578 if (mSessionState.session == null || mSessionState.client == null) {
2579 return;
2580 }
2581 try {
Dongwon Kangb55c7512016-03-01 09:36:07 -08002582 mSessionState.client.onTuned(mSessionState.seq, channelUri);
Jae Seoa826d012016-01-18 13:03:35 -08002583 } catch (RemoteException e) {
Jae Seoe3c11e82016-02-08 23:18:49 -08002584 Slog.e(TAG, "error in onTuned", e);
Jae Seoa826d012016-01-18 13:03:35 -08002585 }
2586 }
2587 }
2588
2589 // For the recording session only
2590 @Override
2591 public void onRecordingStopped(Uri recordedProgramUri) {
2592 synchronized (mLock) {
2593 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002594 Slog.d(TAG, "onRecordingStopped(recordedProgramUri=" + recordedProgramUri
2595 + ")");
Jae Seoa826d012016-01-18 13:03:35 -08002596 }
2597 if (mSessionState.session == null || mSessionState.client == null) {
2598 return;
2599 }
2600 try {
2601 mSessionState.client.onRecordingStopped(recordedProgramUri, mSessionState.seq);
2602 } catch (RemoteException e) {
2603 Slog.e(TAG, "error in onRecordingStopped", e);
2604 }
2605 }
2606 }
2607
2608 // For the recording session only
2609 @Override
2610 public void onError(int error) {
2611 synchronized (mLock) {
2612 if (DEBUG) {
Jae Seo4eee6a72016-02-06 11:11:35 +09002613 Slog.d(TAG, "onError(error=" + error + ")");
Jae Seoa826d012016-01-18 13:03:35 -08002614 }
2615 if (mSessionState.session == null || mSessionState.client == null) {
2616 return;
2617 }
2618 try {
2619 mSessionState.client.onError(error, mSessionState.seq);
2620 } catch (RemoteException e) {
2621 Slog.e(TAG, "error in onError", e);
2622 }
2623 }
2624 }
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002625 }
2626
2627 private static final class WatchLogHandler extends Handler {
Jae Seo7eb75df2014-08-08 22:20:48 -07002628 // There are only two kinds of watch events that can happen on the system:
2629 // 1. The current TV input session is tuned to a new channel.
2630 // 2. The session is released for some reason.
2631 // The former indicates the end of the previous log entry, if any, followed by the start of
2632 // a new entry. The latter indicates the end of the most recent entry for the given session.
2633 // Here the system supplies the database the smallest set of information only that is
2634 // sufficient to consolidate the log entries while minimizing database operations in the
2635 // system service.
Jae Seo8c375fe2015-06-23 20:33:25 -07002636 static final int MSG_LOG_WATCH_START = 1;
2637 static final int MSG_LOG_WATCH_END = 2;
2638 static final int MSG_SWITCH_CONTENT_RESOLVER = 3;
Jae Seo31dc634be2014-04-15 17:40:23 -07002639
Jae Seo8c375fe2015-06-23 20:33:25 -07002640 private ContentResolver mContentResolver;
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002641
Jae Seo8c375fe2015-06-23 20:33:25 -07002642 WatchLogHandler(ContentResolver contentResolver, Looper looper) {
Jae Seo31dc634be2014-04-15 17:40:23 -07002643 super(looper);
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002644 mContentResolver = contentResolver;
Jae Seo31dc634be2014-04-15 17:40:23 -07002645 }
2646
2647 @Override
2648 public void handleMessage(Message msg) {
2649 switch (msg.what) {
Jae Seo7eb75df2014-08-08 22:20:48 -07002650 case MSG_LOG_WATCH_START: {
Jae Seo31dc634be2014-04-15 17:40:23 -07002651 SomeArgs args = (SomeArgs) msg.obj;
Jae Seo7eb75df2014-08-08 22:20:48 -07002652 String packageName = (String) args.arg1;
2653 long watchStartTime = (long) args.arg2;
2654 long channelId = (long) args.arg3;
2655 Bundle tuneParams = (Bundle) args.arg4;
2656 IBinder sessionToken = (IBinder) args.arg5;
2657
2658 ContentValues values = new ContentValues();
2659 values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
2660 values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
2661 watchStartTime);
2662 values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
2663 if (tuneParams != null) {
2664 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS,
2665 encodeTuneParams(tuneParams));
2666 }
2667 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
2668 sessionToken.toString());
2669
2670 mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
Jae Seo31dc634be2014-04-15 17:40:23 -07002671 args.recycle();
Jae Seo8c375fe2015-06-23 20:33:25 -07002672 break;
Jae Seo31dc634be2014-04-15 17:40:23 -07002673 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002674 case MSG_LOG_WATCH_END: {
Jae Seo31dc634be2014-04-15 17:40:23 -07002675 SomeArgs args = (SomeArgs) msg.obj;
Jae Seo7eb75df2014-08-08 22:20:48 -07002676 IBinder sessionToken = (IBinder) args.arg1;
2677 long watchEndTime = (long) args.arg2;
2678
2679 ContentValues values = new ContentValues();
2680 values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
2681 watchEndTime);
2682 values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
2683 sessionToken.toString());
2684
2685 mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
Jae Seo31dc634be2014-04-15 17:40:23 -07002686 args.recycle();
Jae Seo8c375fe2015-06-23 20:33:25 -07002687 break;
2688 }
2689 case MSG_SWITCH_CONTENT_RESOLVER: {
2690 mContentResolver = (ContentResolver) msg.obj;
2691 break;
Jae Seo31dc634be2014-04-15 17:40:23 -07002692 }
2693 default: {
Jae Seo8c375fe2015-06-23 20:33:25 -07002694 Slog.w(TAG, "unhandled message code: " + msg.what);
2695 break;
Jae Seo31dc634be2014-04-15 17:40:23 -07002696 }
2697 }
2698 }
2699
Jae Seo7eb75df2014-08-08 22:20:48 -07002700 private String encodeTuneParams(Bundle tuneParams) {
2701 StringBuilder builder = new StringBuilder();
2702 Set<String> keySet = tuneParams.keySet();
2703 Iterator<String> it = keySet.iterator();
2704 while (it.hasNext()) {
2705 String key = it.next();
2706 Object value = tuneParams.get(key);
2707 if (value == null) {
2708 continue;
Jae Seo579befe2014-08-06 19:18:37 -07002709 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002710 builder.append(replaceEscapeCharacters(key));
2711 builder.append("=");
2712 builder.append(replaceEscapeCharacters(value.toString()));
2713 if (it.hasNext()) {
2714 builder.append(", ");
Jae Seo31dc634be2014-04-15 17:40:23 -07002715 }
2716 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002717 return builder.toString();
Jae Seo31dc634be2014-04-15 17:40:23 -07002718 }
2719
Jae Seo7eb75df2014-08-08 22:20:48 -07002720 private String replaceEscapeCharacters(String src) {
2721 final char ESCAPE_CHARACTER = '%';
2722 final String ENCODING_TARGET_CHARACTERS = "%=,";
2723 StringBuilder builder = new StringBuilder();
2724 for (char ch : src.toCharArray()) {
2725 if (ENCODING_TARGET_CHARACTERS.indexOf(ch) >= 0) {
2726 builder.append(ESCAPE_CHARACTER);
Chulwoo Lee8d4ded02014-07-10 03:56:39 +09002727 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002728 builder.append(ch);
Jae Seo31dc634be2014-04-15 17:40:23 -07002729 }
Jae Seo7eb75df2014-08-08 22:20:48 -07002730 return builder.toString();
Jae Seo579befe2014-08-06 19:18:37 -07002731 }
Jae Seo31dc634be2014-04-15 17:40:23 -07002732 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002733
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002734 private final class HardwareListener implements TvInputHardwareManager.Listener {
Wonsik Kim187423c2014-06-25 14:12:48 +09002735 @Override
2736 public void onStateChanged(String inputId, int state) {
Wonsik Kim969167d2014-06-24 16:33:17 +09002737 synchronized (mLock) {
2738 setStateLocked(inputId, state, mCurrentUserId);
2739 }
2740 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002741
2742 @Override
2743 public void onHardwareDeviceAdded(TvInputHardwareInfo info) {
2744 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002745 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002746 // Broadcast the event to all hardware inputs.
2747 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002748 if (!serviceState.isHardware || serviceState.service == null) continue;
Wonsik Kim187423c2014-06-25 14:12:48 +09002749 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002750 serviceState.service.notifyHardwareAdded(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09002751 } catch (RemoteException e) {
2752 Slog.e(TAG, "error in notifyHardwareAdded", e);
2753 }
2754 }
2755 }
2756 }
2757
2758 @Override
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002759 public void onHardwareDeviceRemoved(TvInputHardwareInfo info) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002760 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002761 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Wonsik Kim187423c2014-06-25 14:12:48 +09002762 // Broadcast the event to all hardware inputs.
2763 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002764 if (!serviceState.isHardware || serviceState.service == null) continue;
Wonsik Kim187423c2014-06-25 14:12:48 +09002765 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002766 serviceState.service.notifyHardwareRemoved(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09002767 } catch (RemoteException e) {
2768 Slog.e(TAG, "error in notifyHardwareRemoved", e);
2769 }
2770 }
2771 }
2772 }
2773
2774 @Override
Jae Seo546c6352014-08-07 11:57:01 -07002775 public void onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002776 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002777 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002778 // Broadcast the event to all hardware inputs.
2779 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002780 if (!serviceState.isHardware || serviceState.service == null) continue;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002781 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002782 serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002783 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002784 Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002785 }
2786 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002787 }
2788 }
2789
2790 @Override
Jae Seo546c6352014-08-07 11:57:01 -07002791 public void onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
Wonsik Kim187423c2014-06-25 14:12:48 +09002792 synchronized (mLock) {
Jae Seo4f1a6d42015-07-20 16:15:01 -07002793 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002794 // Broadcast the event to all hardware inputs.
2795 for (ServiceState serviceState : userState.serviceStateMap.values()) {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002796 if (!serviceState.isHardware || serviceState.service == null) continue;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002797 try {
Dongwon Kangfd8aa022014-08-28 14:59:20 +09002798 serviceState.service.notifyHdmiDeviceRemoved(deviceInfo);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002799 } catch (RemoteException e) {
Jae Seo546c6352014-08-07 11:57:01 -07002800 Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09002801 }
2802 }
Wonsik Kim187423c2014-06-25 14:12:48 +09002803 }
2804 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09002805
2806 @Override
Wonsik Kime92f8572014-08-12 18:30:24 +09002807 public void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo deviceInfo) {
2808 synchronized (mLock) {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002809 Integer state;
Wonsik Kime92f8572014-08-12 18:30:24 +09002810 switch (deviceInfo.getDevicePowerStatus()) {
2811 case HdmiControlManager.POWER_STATUS_ON:
2812 state = INPUT_STATE_CONNECTED;
2813 break;
2814 case HdmiControlManager.POWER_STATUS_STANDBY:
2815 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON:
2816 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY:
2817 state = INPUT_STATE_CONNECTED_STANDBY;
2818 break;
2819 case HdmiControlManager.POWER_STATUS_UNKNOWN:
2820 default:
2821 state = null;
2822 break;
2823 }
2824 if (state != null) {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07002825 setStateLocked(inputId, state, mCurrentUserId);
Wonsik Kime92f8572014-08-12 18:30:24 +09002826 }
2827 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09002828 }
Wonsik Kim969167d2014-06-24 16:33:17 +09002829 }
Dongwon Kangfdce9e52014-12-04 18:08:00 +09002830
2831 private static class SessionNotFoundException extends IllegalArgumentException {
Dongwon Kangfdce9e52014-12-04 18:08:00 +09002832 public SessionNotFoundException(String name) {
2833 super(name);
2834 }
2835 }
Jae Seo39570912014-02-20 18:23:25 -08002836}