TvInputManagerService.java revision abda420b9d2959776093230cd2e157a6080f2f64
1/*
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
19import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
20import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
21
22import android.app.ActivityManager;
23import android.content.BroadcastReceiver;
24import android.content.ComponentName;
25import android.content.ContentProviderOperation;
26import android.content.ContentProviderResult;
27import android.content.ContentResolver;
28import android.content.ContentUris;
29import android.content.ContentValues;
30import android.content.Context;
31import android.content.Intent;
32import android.content.IntentFilter;
33import android.content.OperationApplicationException;
34import android.content.ServiceConnection;
35import android.content.pm.ActivityInfo;
36import android.content.pm.PackageManager;
37import android.content.pm.PackageManager.NameNotFoundException;
38import android.content.pm.ResolveInfo;
39import android.content.pm.ServiceInfo;
40import android.graphics.Rect;
41import android.hardware.hdmi.HdmiControlManager;
42import android.hardware.hdmi.HdmiDeviceInfo;
43import android.media.PlaybackParams;
44import android.media.tv.DvbDeviceInfo;
45import android.media.tv.ITvInputClient;
46import android.media.tv.ITvInputHardware;
47import android.media.tv.ITvInputHardwareCallback;
48import android.media.tv.ITvInputManager;
49import android.media.tv.ITvInputManagerCallback;
50import android.media.tv.ITvInputService;
51import android.media.tv.ITvInputServiceCallback;
52import android.media.tv.ITvInputSession;
53import android.media.tv.ITvInputSessionCallback;
54import android.media.tv.TvContentRating;
55import android.media.tv.TvContentRatingSystemInfo;
56import android.media.tv.TvContract;
57import android.media.tv.TvInputHardwareInfo;
58import android.media.tv.TvInputInfo;
59import android.media.tv.TvInputManager;
60import android.media.tv.TvInputService;
61import android.media.tv.TvStreamConfig;
62import android.media.tv.TvTrackInfo;
63import android.net.Uri;
64import android.os.Binder;
65import android.os.Bundle;
66import android.os.Handler;
67import android.os.IBinder;
68import android.os.Looper;
69import android.os.Message;
70import android.os.ParcelFileDescriptor;
71import android.os.Process;
72import android.os.RemoteException;
73import android.os.UserHandle;
74import android.text.TextUtils;
75import android.util.Slog;
76import android.util.SparseArray;
77import android.view.InputChannel;
78import android.view.Surface;
79
80import com.android.internal.content.PackageMonitor;
81import com.android.internal.os.SomeArgs;
82import com.android.internal.util.IndentingPrintWriter;
83import com.android.server.IoThread;
84import com.android.server.SystemService;
85
86import org.xmlpull.v1.XmlPullParserException;
87
88import java.io.File;
89import java.io.FileDescriptor;
90import java.io.FileNotFoundException;
91import java.io.IOException;
92import java.io.PrintWriter;
93import java.util.ArrayList;
94import java.util.Arrays;
95import java.util.Collections;
96import java.util.HashMap;
97import java.util.HashSet;
98import java.util.Iterator;
99import java.util.List;
100import java.util.Map;
101import java.util.Set;
102import java.util.regex.Matcher;
103import java.util.regex.Pattern;
104
105/** This class provides a system service that manages television inputs. */
106public final class TvInputManagerService extends SystemService {
107    private static final boolean DEBUG = false;
108    private static final String TAG = "TvInputManagerService";
109
110    // Pattern for selecting the DVB frontend devices from the list of files in the /dev directory.
111    private static final Pattern sFrontEndDevicePattern =
112            Pattern.compile("^dvb([0-9]+)\\.frontend([0-9]+)$");
113
114    private final Context mContext;
115    private final TvInputHardwareManager mTvInputHardwareManager;
116
117    // A global lock.
118    private final Object mLock = new Object();
119
120    // ID of the current user.
121    private int mCurrentUserId = UserHandle.USER_SYSTEM;
122
123    // A map from user id to UserState.
124    private final SparseArray<UserState> mUserStates = new SparseArray<>();
125
126    private final WatchLogHandler mWatchLogHandler;
127
128    public TvInputManagerService(Context context) {
129        super(context);
130
131        mContext = context;
132        mWatchLogHandler = new WatchLogHandler(mContext.getContentResolver(),
133                IoThread.get().getLooper());
134        mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
135
136        synchronized (mLock) {
137            getOrCreateUserStateLocked(mCurrentUserId);
138        }
139    }
140
141    @Override
142    public void onStart() {
143        publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());
144    }
145
146    @Override
147    public void onBootPhase(int phase) {
148        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
149            registerBroadcastReceivers();
150        } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
151            synchronized (mLock) {
152                buildTvInputListLocked(mCurrentUserId, null);
153                buildTvContentRatingSystemListLocked(mCurrentUserId);
154            }
155        }
156        mTvInputHardwareManager.onBootPhase(phase);
157    }
158
159    private void registerBroadcastReceivers() {
160        PackageMonitor monitor = new PackageMonitor() {
161            private void buildTvInputList(String[] packages) {
162                synchronized (mLock) {
163                    buildTvInputListLocked(getChangingUserId(), packages);
164                    buildTvContentRatingSystemListLocked(getChangingUserId());
165                }
166            }
167
168            @Override
169            public void onPackageUpdateFinished(String packageName, int uid) {
170                if (DEBUG) Slog.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")");
171                // This callback is invoked when the TV input is reinstalled.
172                // In this case, isReplacing() always returns true.
173                buildTvInputList(new String[] { packageName });
174            }
175
176            @Override
177            public void onPackagesAvailable(String[] packages) {
178                if (DEBUG) {
179                    Slog.d(TAG, "onPackagesAvailable(packages=" + Arrays.toString(packages) + ")");
180                }
181                // This callback is invoked when the media on which some packages exist become
182                // available.
183                if (isReplacing()) {
184                    buildTvInputList(packages);
185                }
186            }
187
188            @Override
189            public void onPackagesUnavailable(String[] packages) {
190                // This callback is invoked when the media on which some packages exist become
191                // unavailable.
192                if (DEBUG)  {
193                    Slog.d(TAG, "onPackagesUnavailable(packages=" + Arrays.toString(packages)
194                            + ")");
195                }
196                if (isReplacing()) {
197                    buildTvInputList(packages);
198                }
199            }
200
201            @Override
202            public void onSomePackagesChanged() {
203                // TODO: Use finer-grained methods(e.g. onPackageAdded, onPackageRemoved) to manage
204                // the TV inputs.
205                if (DEBUG) Slog.d(TAG, "onSomePackagesChanged()");
206                if (isReplacing()) {
207                    if (DEBUG) Slog.d(TAG, "Skipped building TV input list due to replacing");
208                    // When the package is updated, buildTvInputListLocked is called in other
209                    // methods instead.
210                    return;
211                }
212                buildTvInputList(null);
213            }
214
215            @Override
216            public boolean onPackageChanged(String packageName, int uid, String[] components) {
217                // The input list needs to be updated in any cases, regardless of whether
218                // it happened to the whole package or a specific component. Returning true so that
219                // the update can be handled in {@link #onSomePackagesChanged}.
220                return true;
221            }
222
223            @Override
224            public void onPackageRemoved(String packageName, int uid) {
225                synchronized (mLock) {
226                    UserState userState = getOrCreateUserStateLocked(getChangingUserId());
227                    if (!userState.packageSet.contains(packageName)) {
228                        // Not a TV input package.
229                        return;
230                    }
231                }
232
233                ArrayList<ContentProviderOperation> operations = new ArrayList<>();
234
235                String selection = TvContract.BaseTvColumns.COLUMN_PACKAGE_NAME + "=?";
236                String[] selectionArgs = { packageName };
237
238                operations.add(ContentProviderOperation.newDelete(TvContract.Channels.CONTENT_URI)
239                        .withSelection(selection, selectionArgs).build());
240                operations.add(ContentProviderOperation.newDelete(TvContract.Programs.CONTENT_URI)
241                        .withSelection(selection, selectionArgs).build());
242                operations.add(ContentProviderOperation
243                        .newDelete(TvContract.WatchedPrograms.CONTENT_URI)
244                        .withSelection(selection, selectionArgs).build());
245
246                ContentProviderResult[] results = null;
247                try {
248                    ContentResolver cr = getContentResolverForUser(getChangingUserId());
249                    results = cr.applyBatch(TvContract.AUTHORITY, operations);
250                } catch (RemoteException | OperationApplicationException e) {
251                    Slog.e(TAG, "error in applyBatch", e);
252                }
253
254                if (DEBUG) {
255                    Slog.d(TAG, "onPackageRemoved(packageName=" + packageName + ", uid=" + uid
256                            + ")");
257                    Slog.d(TAG, "results=" + results);
258                }
259            }
260        };
261        monitor.register(mContext, null, UserHandle.ALL, true);
262
263        IntentFilter intentFilter = new IntentFilter();
264        intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
265        intentFilter.addAction(Intent.ACTION_USER_REMOVED);
266        mContext.registerReceiverAsUser(new BroadcastReceiver() {
267            @Override
268            public void onReceive(Context context, Intent intent) {
269                String action = intent.getAction();
270                if (Intent.ACTION_USER_SWITCHED.equals(action)) {
271                    switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
272                } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
273                    removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
274                }
275            }
276        }, UserHandle.ALL, intentFilter, null, null);
277    }
278
279    private static boolean hasHardwarePermission(PackageManager pm, ComponentName component) {
280        return pm.checkPermission(android.Manifest.permission.TV_INPUT_HARDWARE,
281                component.getPackageName()) == PackageManager.PERMISSION_GRANTED;
282    }
283
284    private void buildTvInputListLocked(int userId, String[] updatedPackages) {
285        UserState userState = getOrCreateUserStateLocked(userId);
286        userState.packageSet.clear();
287
288        if (DEBUG) Slog.d(TAG, "buildTvInputList");
289        PackageManager pm = mContext.getPackageManager();
290        List<ResolveInfo> services = pm.queryIntentServicesAsUser(
291                new Intent(TvInputService.SERVICE_INTERFACE),
292                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
293                userId);
294        List<TvInputInfo> inputList = new ArrayList<>();
295        for (ResolveInfo ri : services) {
296            ServiceInfo si = ri.serviceInfo;
297            if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
298                Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
299                        + android.Manifest.permission.BIND_TV_INPUT);
300                continue;
301            }
302
303            ComponentName component = new ComponentName(si.packageName, si.name);
304            if (hasHardwarePermission(pm, component)) {
305                ServiceState serviceState = userState.serviceStateMap.get(component);
306                if (serviceState == null) {
307                    // We see this hardware TV input service for the first time; we need to
308                    // prepare the ServiceState object so that we can connect to the service and
309                    // let it add TvInputInfo objects to mInputList if there's any.
310                    serviceState = new ServiceState(component, userId);
311                    userState.serviceStateMap.put(component, serviceState);
312                    updateServiceConnectionLocked(component, userId);
313                } else {
314                    inputList.addAll(serviceState.hardwareInputList);
315                }
316            } else {
317                try {
318                    TvInputInfo info = new TvInputInfo.Builder(mContext, ri).build();
319                    inputList.add(info);
320                } catch (XmlPullParserException | IOException e) {
321                    Slog.e(TAG, "failed to load TV input " + si.name, e);
322                    continue;
323                }
324            }
325            userState.packageSet.add(si.packageName);
326        }
327
328        Map<String, TvInputState> inputMap = new HashMap<>();
329        for (TvInputInfo info : inputList) {
330            if (DEBUG) {
331                Slog.d(TAG, "add " + info.getId());
332            }
333            TvInputState inputState = userState.inputMap.get(info.getId());
334            if (inputState == null) {
335                inputState = new TvInputState();
336                inputState.info = info;
337            }
338            inputMap.put(info.getId(), inputState);
339        }
340
341        for (String inputId : inputMap.keySet()) {
342            if (!userState.inputMap.containsKey(inputId)) {
343                notifyInputAddedLocked(userState, inputId);
344            } else if (updatedPackages != null) {
345                // Notify the package updates
346                ComponentName component = inputMap.get(inputId).info.getComponent();
347                for (String updatedPackage : updatedPackages) {
348                    if (component.getPackageName().equals(updatedPackage)) {
349                        updateServiceConnectionLocked(component, userId);
350                        notifyInputUpdatedLocked(userState, inputId);
351                        break;
352                    }
353                }
354            }
355        }
356
357        for (String inputId : userState.inputMap.keySet()) {
358            if (!inputMap.containsKey(inputId)) {
359                TvInputInfo info = userState.inputMap.get(inputId).info;
360                ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
361                if (serviceState != null) {
362                    abortPendingCreateSessionRequestsLocked(serviceState, inputId, userId);
363                }
364                notifyInputRemovedLocked(userState, inputId);
365            }
366        }
367
368        userState.inputMap.clear();
369        userState.inputMap = inputMap;
370    }
371
372    private void buildTvContentRatingSystemListLocked(int userId) {
373        UserState userState = getOrCreateUserStateLocked(userId);
374        userState.contentRatingSystemList.clear();
375
376        final PackageManager pm = mContext.getPackageManager();
377        Intent intent = new Intent(TvInputManager.ACTION_QUERY_CONTENT_RATING_SYSTEMS);
378        for (ResolveInfo resolveInfo :
379                pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA)) {
380            ActivityInfo receiver = resolveInfo.activityInfo;
381            Bundle metaData = receiver.metaData;
382            if (metaData == null) {
383                continue;
384            }
385
386            int xmlResId = metaData.getInt(TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS);
387            if (xmlResId == 0) {
388                Slog.w(TAG, "Missing meta-data '"
389                        + TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS + "' on receiver "
390                        + receiver.packageName + "/" + receiver.name);
391                continue;
392            }
393            userState.contentRatingSystemList.add(
394                    TvContentRatingSystemInfo.createTvContentRatingSystemInfo(xmlResId,
395                            receiver.applicationInfo));
396        }
397    }
398
399    private void switchUser(int userId) {
400        synchronized (mLock) {
401            if (mCurrentUserId == userId) {
402                return;
403            }
404            clearSessionAndServiceStatesLocked(mUserStates.get(mCurrentUserId));
405
406            mCurrentUserId = userId;
407            getOrCreateUserStateLocked(userId);
408            buildTvInputListLocked(userId, null);
409            buildTvContentRatingSystemListLocked(userId);
410            mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_SWITCH_CONTENT_RESOLVER,
411                    getContentResolverForUser(userId)).sendToTarget();
412        }
413    }
414
415    private void removeUser(int userId) {
416        synchronized (mLock) {
417            UserState userState = mUserStates.get(userId);
418            if (userState == null) {
419                return;
420            }
421            clearSessionAndServiceStatesLocked(userState);
422
423            // Clear everything else.
424            userState.inputMap.clear();
425            userState.packageSet.clear();
426            userState.contentRatingSystemList.clear();
427            userState.clientStateMap.clear();
428            userState.callbackSet.clear();
429            userState.mainSessionToken = null;
430
431            mUserStates.remove(userId);
432        }
433    }
434
435    private void clearSessionAndServiceStatesLocked(UserState userState) {
436        // Release created sessions.
437        for (SessionState state : userState.sessionStateMap.values()) {
438            if (state.session != null) {
439                try {
440                    if (state.isRecordingSession) {
441                        state.session.disconnect();
442                    } else {
443                        state.session.release();
444                    }
445                } catch (RemoteException e) {
446                    Slog.e(TAG, "error in release", e);
447                }
448            }
449        }
450        userState.sessionStateMap.clear();
451
452        // Unregister all callbacks and unbind all services.
453        for (ServiceState serviceState : userState.serviceStateMap.values()) {
454            if (serviceState.service != null) {
455                if (serviceState.callback != null) {
456                    try {
457                        serviceState.service.unregisterCallback(serviceState.callback);
458                    } catch (RemoteException e) {
459                        Slog.e(TAG, "error in unregisterCallback", e);
460                    }
461                }
462                mContext.unbindService(serviceState.connection);
463            }
464        }
465        userState.serviceStateMap.clear();
466    }
467
468    private ContentResolver getContentResolverForUser(int userId) {
469        UserHandle user = new UserHandle(userId);
470        Context context;
471        try {
472            context = mContext.createPackageContextAsUser("android", 0, user);
473        } catch (NameNotFoundException e) {
474            Slog.e(TAG, "failed to create package context as user " + user);
475            context = mContext;
476        }
477        return context.getContentResolver();
478    }
479
480    private UserState getOrCreateUserStateLocked(int userId) {
481        UserState userState = mUserStates.get(userId);
482        if (userState == null) {
483            userState = new UserState(mContext, userId);
484            mUserStates.put(userId, userState);
485        }
486        return userState;
487    }
488
489    private ServiceState getServiceStateLocked(ComponentName component, int userId) {
490        UserState userState = getOrCreateUserStateLocked(userId);
491        ServiceState serviceState = userState.serviceStateMap.get(component);
492        if (serviceState == null) {
493            throw new IllegalStateException("Service state not found for " + component + " (userId="
494                    + userId + ")");
495        }
496        return serviceState;
497    }
498
499    private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid, int userId) {
500        UserState userState = getOrCreateUserStateLocked(userId);
501        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
502        if (sessionState == null) {
503            throw new SessionNotFoundException("Session state not found for token " + sessionToken);
504        }
505        // Only the application that requested this session or the system can access it.
506        if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) {
507            throw new SecurityException("Illegal access to the session with token " + sessionToken
508                    + " from uid " + callingUid);
509        }
510        return sessionState;
511    }
512
513    private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
514        return getSessionLocked(getSessionStateLocked(sessionToken, callingUid, userId));
515    }
516
517    private ITvInputSession getSessionLocked(SessionState sessionState) {
518        ITvInputSession session = sessionState.session;
519        if (session == null) {
520            throw new IllegalStateException("Session not yet created for token "
521                    + sessionState.sessionToken);
522        }
523        return session;
524    }
525
526    private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
527            String methodName) {
528        return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
529                false, methodName, null);
530    }
531
532    private static boolean shouldMaintainConnection(ServiceState serviceState) {
533        return !serviceState.sessionTokens.isEmpty() || serviceState.isHardware;
534        // TODO: Find a way to maintain connection to hardware TV input service only when necessary.
535    }
536
537    private void updateServiceConnectionLocked(ComponentName component, int userId) {
538        UserState userState = getOrCreateUserStateLocked(userId);
539        ServiceState serviceState = userState.serviceStateMap.get(component);
540        if (serviceState == null) {
541            return;
542        }
543        if (serviceState.reconnecting) {
544            if (!serviceState.sessionTokens.isEmpty()) {
545                // wait until all the sessions are removed.
546                return;
547            }
548            serviceState.reconnecting = false;
549        }
550        boolean maintainConnection = shouldMaintainConnection(serviceState);
551        if (serviceState.service == null && maintainConnection && userId == mCurrentUserId) {
552            // This means that the service is not yet connected but its state indicates that we
553            // have pending requests. Then, connect the service.
554            if (serviceState.bound) {
555                // We have already bound to the service so we don't try to bind again until after we
556                // unbind later on.
557                return;
558            }
559            if (DEBUG) {
560                Slog.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
561            }
562
563            Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
564            serviceState.bound = mContext.bindServiceAsUser(
565                    i, serviceState.connection,
566                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
567                    new UserHandle(userId));
568        } else if (serviceState.service != null && !maintainConnection) {
569            // This means that the service is already connected but its state indicates that we have
570            // nothing to do with it. Then, disconnect the service.
571            if (DEBUG) {
572                Slog.d(TAG, "unbindService(service=" + component + ")");
573            }
574            mContext.unbindService(serviceState.connection);
575            userState.serviceStateMap.remove(component);
576        }
577    }
578
579    private void abortPendingCreateSessionRequestsLocked(ServiceState serviceState,
580            String inputId, int userId) {
581        // Let clients know the create session requests are failed.
582        UserState userState = getOrCreateUserStateLocked(userId);
583        List<SessionState> sessionsToAbort = new ArrayList<>();
584        for (IBinder sessionToken : serviceState.sessionTokens) {
585            SessionState sessionState = userState.sessionStateMap.get(sessionToken);
586            if (sessionState.session == null && (inputId == null
587                    || sessionState.inputId.equals(inputId))) {
588                sessionsToAbort.add(sessionState);
589            }
590        }
591        for (SessionState sessionState : sessionsToAbort) {
592            removeSessionStateLocked(sessionState.sessionToken, sessionState.userId);
593            sendSessionTokenToClientLocked(sessionState.client,
594                    sessionState.inputId, null, null, sessionState.seq);
595        }
596        updateServiceConnectionLocked(serviceState.component, userId);
597    }
598
599    private void createSessionInternalLocked(ITvInputService service, IBinder sessionToken,
600            int userId) {
601        UserState userState = getOrCreateUserStateLocked(userId);
602        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
603        if (DEBUG) {
604            Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.inputId + ")");
605        }
606        InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
607
608        // Set up a callback to send the session token.
609        ITvInputSessionCallback callback = new SessionCallback(sessionState, channels);
610
611        // Create a session. When failed, send a null token immediately.
612        try {
613            if (sessionState.isRecordingSession) {
614                service.createRecordingSession(callback, sessionState.inputId);
615            } else {
616                service.createSession(channels[1], callback, sessionState.inputId);
617            }
618        } catch (RemoteException e) {
619            Slog.e(TAG, "error in createSession", e);
620            removeSessionStateLocked(sessionToken, userId);
621            sendSessionTokenToClientLocked(sessionState.client, sessionState.inputId, null,
622                    null, sessionState.seq);
623        }
624        channels[1].dispose();
625    }
626
627    private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId,
628            IBinder sessionToken, InputChannel channel, int seq) {
629        try {
630            client.onSessionCreated(inputId, sessionToken, channel, seq);
631        } catch (RemoteException e) {
632            Slog.e(TAG, "error in onSessionCreated", e);
633        }
634    }
635
636    private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
637        SessionState sessionState = null;
638        try {
639            sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
640            if (sessionState.session != null) {
641                UserState userState = getOrCreateUserStateLocked(userId);
642                if (sessionToken == userState.mainSessionToken) {
643                    setMainLocked(sessionToken, false, callingUid, userId);
644                }
645                if (sessionState.isRecordingSession) {
646                    sessionState.session.disconnect();
647                } else {
648                    sessionState.session.release();
649                }
650            }
651        } catch (RemoteException | SessionNotFoundException e) {
652            Slog.e(TAG, "error in releaseSession", e);
653        } finally {
654            if (sessionState != null) {
655                sessionState.session = null;
656            }
657        }
658        removeSessionStateLocked(sessionToken, userId);
659    }
660
661    private void removeSessionStateLocked(IBinder sessionToken, int userId) {
662        UserState userState = getOrCreateUserStateLocked(userId);
663        if (sessionToken == userState.mainSessionToken) {
664            if (DEBUG) {
665                Slog.d(TAG, "mainSessionToken=null");
666            }
667            userState.mainSessionToken = null;
668        }
669
670        // Remove the session state from the global session state map of the current user.
671        SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
672
673        if (sessionState == null) {
674            return;
675        }
676
677        // Also remove the session token from the session token list of the current client and
678        // service.
679        ClientState clientState = userState.clientStateMap.get(sessionState.client.asBinder());
680        if (clientState != null) {
681            clientState.sessionTokens.remove(sessionToken);
682            if (clientState.isEmpty()) {
683                userState.clientStateMap.remove(sessionState.client.asBinder());
684            }
685        }
686
687        ServiceState serviceState = userState.serviceStateMap.get(sessionState.componentName);
688        if (serviceState != null) {
689            serviceState.sessionTokens.remove(sessionToken);
690        }
691        updateServiceConnectionLocked(sessionState.componentName, userId);
692
693        // Log the end of watch.
694        SomeArgs args = SomeArgs.obtain();
695        args.arg1 = sessionToken;
696        args.arg2 = System.currentTimeMillis();
697        mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_END, args).sendToTarget();
698    }
699
700    private void setMainLocked(IBinder sessionToken, boolean isMain, int callingUid, int userId) {
701        try {
702            SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
703            if (sessionState.hardwareSessionToken != null) {
704                sessionState = getSessionStateLocked(sessionState.hardwareSessionToken,
705                        Process.SYSTEM_UID, userId);
706            }
707            ServiceState serviceState = getServiceStateLocked(sessionState.componentName, userId);
708            if (!serviceState.isHardware) {
709                return;
710            }
711            ITvInputSession session = getSessionLocked(sessionState);
712            session.setMain(isMain);
713        } catch (RemoteException | SessionNotFoundException e) {
714            Slog.e(TAG, "error in setMain", e);
715        }
716    }
717
718    private void notifyInputAddedLocked(UserState userState, String inputId) {
719        if (DEBUG) {
720            Slog.d(TAG, "notifyInputAddedLocked(inputId=" + inputId + ")");
721        }
722        for (ITvInputManagerCallback callback : userState.callbackSet) {
723            try {
724                callback.onInputAdded(inputId);
725            } catch (RemoteException e) {
726                Slog.e(TAG, "failed to report added input to callback", e);
727            }
728        }
729    }
730
731    private void notifyInputRemovedLocked(UserState userState, String inputId) {
732        if (DEBUG) {
733            Slog.d(TAG, "notifyInputRemovedLocked(inputId=" + inputId + ")");
734        }
735        for (ITvInputManagerCallback callback : userState.callbackSet) {
736            try {
737                callback.onInputRemoved(inputId);
738            } catch (RemoteException e) {
739                Slog.e(TAG, "failed to report removed input to callback", e);
740            }
741        }
742    }
743
744    private void notifyInputUpdatedLocked(UserState userState, String inputId) {
745        if (DEBUG) {
746            Slog.d(TAG, "notifyInputUpdatedLocked(inputId=" + inputId + ")");
747        }
748        for (ITvInputManagerCallback callback : userState.callbackSet) {
749            try {
750                callback.onInputUpdated(inputId);
751            } catch (RemoteException e) {
752                Slog.e(TAG, "failed to report updated input to callback", e);
753            }
754        }
755    }
756
757    private void notifyInputStateChangedLocked(UserState userState, String inputId,
758            int state, ITvInputManagerCallback targetCallback) {
759        if (DEBUG) {
760            Slog.d(TAG, "notifyInputStateChangedLocked(inputId=" + inputId
761                    + ", state=" + state + ")");
762        }
763        if (targetCallback == null) {
764            for (ITvInputManagerCallback callback : userState.callbackSet) {
765                try {
766                    callback.onInputStateChanged(inputId, state);
767                } catch (RemoteException e) {
768                    Slog.e(TAG, "failed to report state change to callback", e);
769                }
770            }
771        } else {
772            try {
773                targetCallback.onInputStateChanged(inputId, state);
774            } catch (RemoteException e) {
775                Slog.e(TAG, "failed to report state change to callback", e);
776            }
777        }
778    }
779
780    private void setTvInputInfoLocked(UserState userState, TvInputInfo inputInfo) {
781        if (DEBUG) {
782            Slog.d(TAG, "setTvInputInfoLocked(inputInfo=" + inputInfo + ")");
783        }
784        String inputId = inputInfo.getId();
785        TvInputState inputState = userState.inputMap.get(inputId);
786        if (inputState == null) {
787            Slog.e(TAG, "failed to set input info - unknown input id " + inputId);
788            return;
789        }
790        if (inputState.info.equals(inputInfo)) {
791            return;
792        }
793        inputState.info = inputInfo;
794
795        for (ITvInputManagerCallback callback : userState.callbackSet) {
796            try {
797                callback.onTvInputInfoChanged(inputInfo);
798            } catch (RemoteException e) {
799                Slog.e(TAG, "failed to report changed input info to callback", e);
800            }
801        }
802    }
803
804    private void setStateLocked(String inputId, int state, int userId) {
805        UserState userState = getOrCreateUserStateLocked(userId);
806        TvInputState inputState = userState.inputMap.get(inputId);
807        ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent());
808        int oldState = inputState.state;
809        inputState.state = state;
810        if (serviceState != null && serviceState.service == null
811                && shouldMaintainConnection(serviceState)) {
812            // We don't notify state change while reconnecting. It should remain disconnected.
813            return;
814        }
815        if (oldState != state) {
816            notifyInputStateChangedLocked(userState, inputId, state, null);
817        }
818    }
819
820    private final class BinderService extends ITvInputManager.Stub {
821        @Override
822        public List<TvInputInfo> getTvInputList(int userId) {
823            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
824                    Binder.getCallingUid(), userId, "getTvInputList");
825            final long identity = Binder.clearCallingIdentity();
826            try {
827                synchronized (mLock) {
828                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
829                    List<TvInputInfo> inputList = new ArrayList<>();
830                    for (TvInputState state : userState.inputMap.values()) {
831                        inputList.add(state.info);
832                    }
833                    return inputList;
834                }
835            } finally {
836                Binder.restoreCallingIdentity(identity);
837            }
838        }
839
840        @Override
841        public TvInputInfo getTvInputInfo(String inputId, int userId) {
842            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
843                    Binder.getCallingUid(), userId, "getTvInputInfo");
844            final long identity = Binder.clearCallingIdentity();
845            try {
846                synchronized (mLock) {
847                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
848                    TvInputState state = userState.inputMap.get(inputId);
849                    return state == null ? null : state.info;
850                }
851            } finally {
852                Binder.restoreCallingIdentity(identity);
853            }
854        }
855
856        public void setTvInputInfo(TvInputInfo inputInfo, int userId) {
857            String inputInfoPackageName = inputInfo.getServiceInfo().packageName;
858            String callingPackageName = getCallingPackageName();
859            if (!TextUtils.equals(inputInfoPackageName, callingPackageName)) {
860                throw new IllegalArgumentException("calling package " + callingPackageName
861                        + " is not allowed to change TvInputInfo for " + inputInfoPackageName);
862            }
863
864            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
865                    Binder.getCallingUid(), userId, "setTvInputInfo");
866            final long identity = Binder.clearCallingIdentity();
867            try {
868                synchronized (mLock) {
869                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
870                    setTvInputInfoLocked(userState, inputInfo);
871                }
872            } finally {
873                Binder.restoreCallingIdentity(identity);
874            }
875        }
876
877        private String getCallingPackageName() {
878            final String[] packages = mContext.getPackageManager().getPackagesForUid(
879                    Binder.getCallingUid());
880            if (packages != null && packages.length > 0) {
881                return packages[0];
882            }
883            return "unknown";
884        }
885
886        @Override
887        public int getTvInputState(String inputId, int userId) {
888            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
889                    Binder.getCallingUid(), userId, "getTvInputState");
890            final long identity = Binder.clearCallingIdentity();
891            try {
892                synchronized (mLock) {
893                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
894                    TvInputState state = userState.inputMap.get(inputId);
895                    return state == null ? INPUT_STATE_CONNECTED : state.state;
896                }
897            } finally {
898                Binder.restoreCallingIdentity(identity);
899            }
900        }
901
902        @Override
903        public List<TvContentRatingSystemInfo> getTvContentRatingSystemList(int userId) {
904            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
905                    Binder.getCallingUid(), userId, "getTvContentRatingSystemList");
906            final long identity = Binder.clearCallingIdentity();
907            try {
908                synchronized (mLock) {
909                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
910                    return userState.contentRatingSystemList;
911                }
912            } finally {
913                Binder.restoreCallingIdentity(identity);
914            }
915        }
916
917        @Override
918        public void registerCallback(final ITvInputManagerCallback callback, int userId) {
919            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
920                    Binder.getCallingUid(), userId, "registerCallback");
921            final long identity = Binder.clearCallingIdentity();
922            try {
923                synchronized (mLock) {
924                    final UserState userState = getOrCreateUserStateLocked(resolvedUserId);
925                    userState.callbackSet.add(callback);
926                    try {
927                        callback.asBinder().linkToDeath(new IBinder.DeathRecipient() {
928                            @Override
929                            public void binderDied() {
930                                synchronized (mLock) {
931                                    if (userState.callbackSet != null) {
932                                        userState.callbackSet.remove(callback);
933                                    }
934                                }
935                            }
936                        }, 0);
937                    } catch (RemoteException e) {
938                        Slog.e(TAG, "client process has already died", e);
939                    }
940                }
941            } finally {
942                Binder.restoreCallingIdentity(identity);
943            }
944        }
945
946        @Override
947        public void unregisterCallback(ITvInputManagerCallback callback, int userId) {
948            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
949                    Binder.getCallingUid(), userId, "unregisterCallback");
950            final long identity = Binder.clearCallingIdentity();
951            try {
952                synchronized (mLock) {
953                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
954                    userState.callbackSet.remove(callback);
955                }
956            } finally {
957                Binder.restoreCallingIdentity(identity);
958            }
959        }
960
961        @Override
962        public boolean isParentalControlsEnabled(int userId) {
963            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
964                    Binder.getCallingUid(), userId, "isParentalControlsEnabled");
965            final long identity = Binder.clearCallingIdentity();
966            try {
967                synchronized (mLock) {
968                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
969                    return userState.persistentDataStore.isParentalControlsEnabled();
970                }
971            } finally {
972                Binder.restoreCallingIdentity(identity);
973            }
974        }
975
976        @Override
977        public void setParentalControlsEnabled(boolean enabled, int userId) {
978            ensureParentalControlsPermission();
979            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
980                    Binder.getCallingUid(), userId, "setParentalControlsEnabled");
981            final long identity = Binder.clearCallingIdentity();
982            try {
983                synchronized (mLock) {
984                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
985                    userState.persistentDataStore.setParentalControlsEnabled(enabled);
986                }
987            } finally {
988                Binder.restoreCallingIdentity(identity);
989            }
990        }
991
992        @Override
993        public boolean isRatingBlocked(String rating, int userId) {
994            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
995                    Binder.getCallingUid(), userId, "isRatingBlocked");
996            final long identity = Binder.clearCallingIdentity();
997            try {
998                synchronized (mLock) {
999                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
1000                    return userState.persistentDataStore.isRatingBlocked(
1001                            TvContentRating.unflattenFromString(rating));
1002                }
1003            } finally {
1004                Binder.restoreCallingIdentity(identity);
1005            }
1006        }
1007
1008        @Override
1009        public List<String> getBlockedRatings(int userId) {
1010            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1011                    Binder.getCallingUid(), userId, "getBlockedRatings");
1012            final long identity = Binder.clearCallingIdentity();
1013            try {
1014                synchronized (mLock) {
1015                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
1016                    List<String> ratings = new ArrayList<>();
1017                    for (TvContentRating rating
1018                            : userState.persistentDataStore.getBlockedRatings()) {
1019                        ratings.add(rating.flattenToString());
1020                    }
1021                    return ratings;
1022                }
1023            } finally {
1024                Binder.restoreCallingIdentity(identity);
1025            }
1026        }
1027
1028        @Override
1029        public void addBlockedRating(String rating, int userId) {
1030            ensureParentalControlsPermission();
1031            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1032                    Binder.getCallingUid(), userId, "addBlockedRating");
1033            final long identity = Binder.clearCallingIdentity();
1034            try {
1035                synchronized (mLock) {
1036                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
1037                    userState.persistentDataStore.addBlockedRating(
1038                            TvContentRating.unflattenFromString(rating));
1039                }
1040            } finally {
1041                Binder.restoreCallingIdentity(identity);
1042            }
1043        }
1044
1045        @Override
1046        public void removeBlockedRating(String rating, int userId) {
1047            ensureParentalControlsPermission();
1048            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1049                    Binder.getCallingUid(), userId, "removeBlockedRating");
1050            final long identity = Binder.clearCallingIdentity();
1051            try {
1052                synchronized (mLock) {
1053                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
1054                    userState.persistentDataStore.removeBlockedRating(
1055                            TvContentRating.unflattenFromString(rating));
1056                }
1057            } finally {
1058                Binder.restoreCallingIdentity(identity);
1059            }
1060        }
1061
1062        private void ensureParentalControlsPermission() {
1063            if (mContext.checkCallingPermission(
1064                    android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
1065                    != PackageManager.PERMISSION_GRANTED) {
1066                throw new SecurityException(
1067                        "The caller does not have parental controls permission");
1068            }
1069        }
1070
1071        @Override
1072        public void createSession(final ITvInputClient client, final String inputId,
1073                boolean isRecordingSession, int seq, int userId) {
1074            final int callingUid = Binder.getCallingUid();
1075            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1076                    userId, "createSession");
1077            final long identity = Binder.clearCallingIdentity();
1078            try {
1079                synchronized (mLock) {
1080                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
1081                    TvInputState inputState = userState.inputMap.get(inputId);
1082                    if (inputState == null) {
1083                        Slog.w(TAG, "Failed to find input state for inputId=" + inputId);
1084                        sendSessionTokenToClientLocked(client, inputId, null, null, seq);
1085                        return;
1086                    }
1087                    TvInputInfo info = inputState.info;
1088                    ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
1089                    if (serviceState == null) {
1090                        serviceState = new ServiceState(info.getComponent(), resolvedUserId);
1091                        userState.serviceStateMap.put(info.getComponent(), serviceState);
1092                    }
1093                    // Send a null token immediately while reconnecting.
1094                    if (serviceState.reconnecting) {
1095                        sendSessionTokenToClientLocked(client, inputId, null, null, seq);
1096                        return;
1097                    }
1098
1099                    // Create a new session token and a session state.
1100                    IBinder sessionToken = new Binder();
1101                    SessionState sessionState = new SessionState(sessionToken, info.getId(),
1102                            info.getComponent(), isRecordingSession, client, seq, callingUid,
1103                            resolvedUserId);
1104
1105                    // Add them to the global session state map of the current user.
1106                    userState.sessionStateMap.put(sessionToken, sessionState);
1107
1108                    // Also, add them to the session state map of the current service.
1109                    serviceState.sessionTokens.add(sessionToken);
1110
1111                    if (serviceState.service != null) {
1112                        createSessionInternalLocked(serviceState.service, sessionToken,
1113                                resolvedUserId);
1114                    } else {
1115                        updateServiceConnectionLocked(info.getComponent(), resolvedUserId);
1116                    }
1117                }
1118            } finally {
1119                Binder.restoreCallingIdentity(identity);
1120            }
1121        }
1122
1123        @Override
1124        public void releaseSession(IBinder sessionToken, int userId) {
1125            if (DEBUG) {
1126                Slog.d(TAG, "releaseSession(sessionToken=" + sessionToken + ")");
1127            }
1128            final int callingUid = Binder.getCallingUid();
1129            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1130                    userId, "releaseSession");
1131            final long identity = Binder.clearCallingIdentity();
1132            try {
1133                synchronized (mLock) {
1134                    releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
1135                }
1136            } finally {
1137                Binder.restoreCallingIdentity(identity);
1138            }
1139        }
1140
1141        @Override
1142        public void setMainSession(IBinder sessionToken, int userId) {
1143            if (DEBUG) {
1144                Slog.d(TAG, "setMainSession(sessionToken=" + sessionToken + ")");
1145            }
1146            final int callingUid = Binder.getCallingUid();
1147            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1148                    userId, "setMainSession");
1149            final long identity = Binder.clearCallingIdentity();
1150            try {
1151                synchronized (mLock) {
1152                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
1153                    if (userState.mainSessionToken == sessionToken) {
1154                        return;
1155                    }
1156                    if (DEBUG) {
1157                        Slog.d(TAG, "mainSessionToken=" + sessionToken);
1158                    }
1159                    IBinder oldMainSessionToken = userState.mainSessionToken;
1160                    userState.mainSessionToken = sessionToken;
1161
1162                    // Inform the new main session first.
1163                    // See {@link TvInputService.Session#onSetMain}.
1164                    if (sessionToken != null) {
1165                        setMainLocked(sessionToken, true, callingUid, userId);
1166                    }
1167                    if (oldMainSessionToken != null) {
1168                        setMainLocked(oldMainSessionToken, false, Process.SYSTEM_UID, userId);
1169                    }
1170                }
1171            } finally {
1172                Binder.restoreCallingIdentity(identity);
1173            }
1174        }
1175
1176        @Override
1177        public void setSurface(IBinder sessionToken, Surface surface, int userId) {
1178            final int callingUid = Binder.getCallingUid();
1179            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1180                    userId, "setSurface");
1181            final long identity = Binder.clearCallingIdentity();
1182            try {
1183                synchronized (mLock) {
1184                    try {
1185                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1186                                resolvedUserId);
1187                        if (sessionState.hardwareSessionToken == null) {
1188                            getSessionLocked(sessionState).setSurface(surface);
1189                        } else {
1190                            getSessionLocked(sessionState.hardwareSessionToken,
1191                                    Process.SYSTEM_UID, resolvedUserId).setSurface(surface);
1192                        }
1193                    } catch (RemoteException | SessionNotFoundException e) {
1194                        Slog.e(TAG, "error in setSurface", e);
1195                    }
1196                }
1197            } finally {
1198                if (surface != null) {
1199                    // surface is not used in TvInputManagerService.
1200                    surface.release();
1201                }
1202                Binder.restoreCallingIdentity(identity);
1203            }
1204        }
1205
1206        @Override
1207        public void dispatchSurfaceChanged(IBinder sessionToken, int format, int width,
1208                int height, int userId) {
1209            final int callingUid = Binder.getCallingUid();
1210            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1211                    userId, "dispatchSurfaceChanged");
1212            final long identity = Binder.clearCallingIdentity();
1213            try {
1214                synchronized (mLock) {
1215                    try {
1216                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1217                                resolvedUserId);
1218                        getSessionLocked(sessionState).dispatchSurfaceChanged(format, width,
1219                                height);
1220                        if (sessionState.hardwareSessionToken != null) {
1221                            getSessionLocked(sessionState.hardwareSessionToken, Process.SYSTEM_UID,
1222                                    resolvedUserId).dispatchSurfaceChanged(format, width, height);
1223                        }
1224                    } catch (RemoteException | SessionNotFoundException e) {
1225                        Slog.e(TAG, "error in dispatchSurfaceChanged", e);
1226                    }
1227                }
1228            } finally {
1229                Binder.restoreCallingIdentity(identity);
1230            }
1231        }
1232
1233        @Override
1234        public void setVolume(IBinder sessionToken, float volume, int userId) {
1235            final float REMOTE_VOLUME_ON = 1.0f;
1236            final float REMOTE_VOLUME_OFF = 0f;
1237            final int callingUid = Binder.getCallingUid();
1238            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1239                    userId, "setVolume");
1240            final long identity = Binder.clearCallingIdentity();
1241            try {
1242                synchronized (mLock) {
1243                    try {
1244                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1245                                resolvedUserId);
1246                        getSessionLocked(sessionState).setVolume(volume);
1247                        if (sessionState.hardwareSessionToken != null) {
1248                            // Here, we let the hardware session know only whether volume is on or
1249                            // off to prevent that the volume is controlled in the both side.
1250                            getSessionLocked(sessionState.hardwareSessionToken,
1251                                    Process.SYSTEM_UID, resolvedUserId).setVolume((volume > 0.0f)
1252                                            ? REMOTE_VOLUME_ON : REMOTE_VOLUME_OFF);
1253                        }
1254                    } catch (RemoteException | SessionNotFoundException e) {
1255                        Slog.e(TAG, "error in setVolume", e);
1256                    }
1257                }
1258            } finally {
1259                Binder.restoreCallingIdentity(identity);
1260            }
1261        }
1262
1263        @Override
1264        public void tune(IBinder sessionToken, final Uri channelUri, Bundle params, int userId) {
1265            final int callingUid = Binder.getCallingUid();
1266            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1267                    userId, "tune");
1268            final long identity = Binder.clearCallingIdentity();
1269            try {
1270                synchronized (mLock) {
1271                    try {
1272                        getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(
1273                                channelUri, params);
1274                        if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
1275                            // Do not log the watch history for passthrough inputs.
1276                            return;
1277                        }
1278
1279                        UserState userState = getOrCreateUserStateLocked(resolvedUserId);
1280                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
1281
1282                        // Log the start of watch.
1283                        SomeArgs args = SomeArgs.obtain();
1284                        args.arg1 = sessionState.componentName.getPackageName();
1285                        args.arg2 = System.currentTimeMillis();
1286                        args.arg3 = ContentUris.parseId(channelUri);
1287                        args.arg4 = params;
1288                        args.arg5 = sessionToken;
1289                        mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_START, args)
1290                                .sendToTarget();
1291                    } catch (RemoteException | SessionNotFoundException e) {
1292                        Slog.e(TAG, "error in tune", e);
1293                    }
1294                }
1295            } finally {
1296                Binder.restoreCallingIdentity(identity);
1297            }
1298        }
1299
1300        @Override
1301        public void unblockContent(
1302                IBinder sessionToken, String unblockedRating, int userId) {
1303            final int callingUid = Binder.getCallingUid();
1304            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1305                    userId, "unblockContent");
1306            final long identity = Binder.clearCallingIdentity();
1307            try {
1308                synchronized (mLock) {
1309                    try {
1310                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1311                                .unblockContent(unblockedRating);
1312                    } catch (RemoteException | SessionNotFoundException e) {
1313                        Slog.e(TAG, "error in unblockContent", e);
1314                    }
1315                }
1316            } finally {
1317                Binder.restoreCallingIdentity(identity);
1318            }
1319        }
1320
1321        @Override
1322        public void setCaptionEnabled(IBinder sessionToken, boolean enabled, int userId) {
1323            final int callingUid = Binder.getCallingUid();
1324            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1325                    userId, "setCaptionEnabled");
1326            final long identity = Binder.clearCallingIdentity();
1327            try {
1328                synchronized (mLock) {
1329                    try {
1330                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1331                                .setCaptionEnabled(enabled);
1332                    } catch (RemoteException | SessionNotFoundException e) {
1333                        Slog.e(TAG, "error in setCaptionEnabled", e);
1334                    }
1335                }
1336            } finally {
1337                Binder.restoreCallingIdentity(identity);
1338            }
1339        }
1340
1341        @Override
1342        public void selectTrack(IBinder sessionToken, int type, String trackId, int userId) {
1343            final int callingUid = Binder.getCallingUid();
1344            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1345                    userId, "selectTrack");
1346            final long identity = Binder.clearCallingIdentity();
1347            try {
1348                synchronized (mLock) {
1349                    try {
1350                        getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack(
1351                                type, trackId);
1352                    } catch (RemoteException | SessionNotFoundException e) {
1353                        Slog.e(TAG, "error in selectTrack", e);
1354                    }
1355                }
1356            } finally {
1357                Binder.restoreCallingIdentity(identity);
1358            }
1359        }
1360
1361        @Override
1362        public void sendAppPrivateCommand(IBinder sessionToken, String command, Bundle data,
1363                int userId) {
1364            final int callingUid = Binder.getCallingUid();
1365            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1366                    userId, "sendAppPrivateCommand");
1367            final long identity = Binder.clearCallingIdentity();
1368            try {
1369                synchronized (mLock) {
1370                    try {
1371                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1372                                .appPrivateCommand(command, data);
1373                    } catch (RemoteException | SessionNotFoundException e) {
1374                        Slog.e(TAG, "error in appPrivateCommand", e);
1375                    }
1376                }
1377            } finally {
1378                Binder.restoreCallingIdentity(identity);
1379            }
1380        }
1381
1382        @Override
1383        public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
1384                int userId) {
1385            final int callingUid = Binder.getCallingUid();
1386            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1387                    userId, "createOverlayView");
1388            final long identity = Binder.clearCallingIdentity();
1389            try {
1390                synchronized (mLock) {
1391                    try {
1392                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1393                                .createOverlayView(windowToken, frame);
1394                    } catch (RemoteException | SessionNotFoundException e) {
1395                        Slog.e(TAG, "error in createOverlayView", e);
1396                    }
1397                }
1398            } finally {
1399                Binder.restoreCallingIdentity(identity);
1400            }
1401        }
1402
1403        @Override
1404        public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
1405            final int callingUid = Binder.getCallingUid();
1406            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1407                    userId, "relayoutOverlayView");
1408            final long identity = Binder.clearCallingIdentity();
1409            try {
1410                synchronized (mLock) {
1411                    try {
1412                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1413                                .relayoutOverlayView(frame);
1414                    } catch (RemoteException | SessionNotFoundException e) {
1415                        Slog.e(TAG, "error in relayoutOverlayView", e);
1416                    }
1417                }
1418            } finally {
1419                Binder.restoreCallingIdentity(identity);
1420            }
1421        }
1422
1423        @Override
1424        public void removeOverlayView(IBinder sessionToken, int userId) {
1425            final int callingUid = Binder.getCallingUid();
1426            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1427                    userId, "removeOverlayView");
1428            final long identity = Binder.clearCallingIdentity();
1429            try {
1430                synchronized (mLock) {
1431                    try {
1432                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1433                                .removeOverlayView();
1434                    } catch (RemoteException | SessionNotFoundException e) {
1435                        Slog.e(TAG, "error in removeOverlayView", e);
1436                    }
1437                }
1438            } finally {
1439                Binder.restoreCallingIdentity(identity);
1440            }
1441        }
1442
1443        @Override
1444        public void timeShiftPlay(IBinder sessionToken, final Uri recordedProgramUri, int userId) {
1445            final int callingUid = Binder.getCallingUid();
1446            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1447                    userId, "timeShiftPlay");
1448            final long identity = Binder.clearCallingIdentity();
1449            try {
1450                synchronized (mLock) {
1451                    try {
1452                        getSessionLocked(sessionToken, callingUid, resolvedUserId).timeShiftPlay(
1453                                recordedProgramUri);
1454                    } catch (RemoteException | SessionNotFoundException e) {
1455                        Slog.e(TAG, "error in timeShiftPlay", e);
1456                    }
1457                }
1458            } finally {
1459                Binder.restoreCallingIdentity(identity);
1460            }
1461        }
1462
1463        @Override
1464        public void timeShiftPause(IBinder sessionToken, int userId) {
1465            final int callingUid = Binder.getCallingUid();
1466            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1467                    userId, "timeShiftPause");
1468            final long identity = Binder.clearCallingIdentity();
1469            try {
1470                synchronized (mLock) {
1471                    try {
1472                        getSessionLocked(sessionToken, callingUid, resolvedUserId).timeShiftPause();
1473                    } catch (RemoteException | SessionNotFoundException e) {
1474                        Slog.e(TAG, "error in timeShiftPause", e);
1475                    }
1476                }
1477            } finally {
1478                Binder.restoreCallingIdentity(identity);
1479            }
1480        }
1481
1482        @Override
1483        public void timeShiftResume(IBinder sessionToken, int userId) {
1484            final int callingUid = Binder.getCallingUid();
1485            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1486                    userId, "timeShiftResume");
1487            final long identity = Binder.clearCallingIdentity();
1488            try {
1489                synchronized (mLock) {
1490                    try {
1491                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1492                                .timeShiftResume();
1493                    } catch (RemoteException | SessionNotFoundException e) {
1494                        Slog.e(TAG, "error in timeShiftResume", e);
1495                    }
1496                }
1497            } finally {
1498                Binder.restoreCallingIdentity(identity);
1499            }
1500        }
1501
1502        @Override
1503        public void timeShiftSeekTo(IBinder sessionToken, long timeMs, int userId) {
1504            final int callingUid = Binder.getCallingUid();
1505            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1506                    userId, "timeShiftSeekTo");
1507            final long identity = Binder.clearCallingIdentity();
1508            try {
1509                synchronized (mLock) {
1510                    try {
1511                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1512                                .timeShiftSeekTo(timeMs);
1513                    } catch (RemoteException | SessionNotFoundException e) {
1514                        Slog.e(TAG, "error in timeShiftSeekTo", e);
1515                    }
1516                }
1517            } finally {
1518                Binder.restoreCallingIdentity(identity);
1519            }
1520        }
1521
1522        @Override
1523        public void timeShiftSetPlaybackParams(IBinder sessionToken, PlaybackParams params,
1524                int userId) {
1525            final int callingUid = Binder.getCallingUid();
1526            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1527                    userId, "timeShiftSetPlaybackParams");
1528            final long identity = Binder.clearCallingIdentity();
1529            try {
1530                synchronized (mLock) {
1531                    try {
1532                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1533                                .timeShiftSetPlaybackParams(params);
1534                    } catch (RemoteException | SessionNotFoundException e) {
1535                        Slog.e(TAG, "error in timeShiftSetPlaybackParams", e);
1536                    }
1537                }
1538            } finally {
1539                Binder.restoreCallingIdentity(identity);
1540            }
1541        }
1542
1543        @Override
1544        public void timeShiftEnablePositionTracking(IBinder sessionToken, boolean enable,
1545                int userId) {
1546            final int callingUid = Binder.getCallingUid();
1547            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1548                    userId, "timeShiftEnablePositionTracking");
1549            final long identity = Binder.clearCallingIdentity();
1550            try {
1551                synchronized (mLock) {
1552                    try {
1553                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1554                                .timeShiftEnablePositionTracking(enable);
1555                    } catch (RemoteException | SessionNotFoundException e) {
1556                        Slog.e(TAG, "error in timeShiftEnablePositionTracking", e);
1557                    }
1558                }
1559            } finally {
1560                Binder.restoreCallingIdentity(identity);
1561            }
1562        }
1563
1564        @Override
1565        public void connect(IBinder sessionToken, final Uri channelUri, Bundle params, int userId) {
1566            final int callingUid = Binder.getCallingUid();
1567            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1568                    userId, "connect");
1569            final long identity = Binder.clearCallingIdentity();
1570            try {
1571                synchronized (mLock) {
1572                    try {
1573                        getSessionLocked(sessionToken, callingUid, resolvedUserId).connect(
1574                                channelUri, params);
1575                    } catch (RemoteException | SessionNotFoundException e) {
1576                        Slog.e(TAG, "error in connect", e);
1577                    }
1578                }
1579            } finally {
1580                Binder.restoreCallingIdentity(identity);
1581            }
1582        }
1583
1584        @Override
1585        public void startRecording(IBinder sessionToken, int userId) {
1586            final int callingUid = Binder.getCallingUid();
1587            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1588                    userId, "startRecording");
1589            final long identity = Binder.clearCallingIdentity();
1590            try {
1591                synchronized (mLock) {
1592                    try {
1593                        getSessionLocked(sessionToken, callingUid, resolvedUserId).startRecording();
1594                    } catch (RemoteException | SessionNotFoundException e) {
1595                        Slog.e(TAG, "error in startRecording", e);
1596                    }
1597                }
1598            } finally {
1599                Binder.restoreCallingIdentity(identity);
1600            }
1601        }
1602
1603        @Override
1604        public void stopRecording(IBinder sessionToken, int userId) {
1605            final int callingUid = Binder.getCallingUid();
1606            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1607                    userId, "stopRecording");
1608            final long identity = Binder.clearCallingIdentity();
1609            try {
1610                synchronized (mLock) {
1611                    try {
1612                        getSessionLocked(sessionToken, callingUid, resolvedUserId).stopRecording();
1613                    } catch (RemoteException | SessionNotFoundException e) {
1614                        Slog.e(TAG, "error in stopRecording", e);
1615                    }
1616                }
1617            } finally {
1618                Binder.restoreCallingIdentity(identity);
1619            }
1620        }
1621
1622        @Override
1623        public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
1624            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1625                    != PackageManager.PERMISSION_GRANTED) {
1626                return null;
1627            }
1628
1629            final long identity = Binder.clearCallingIdentity();
1630            try {
1631                return mTvInputHardwareManager.getHardwareList();
1632            } finally {
1633                Binder.restoreCallingIdentity(identity);
1634            }
1635        }
1636
1637        @Override
1638        public ITvInputHardware acquireTvInputHardware(int deviceId,
1639                ITvInputHardwareCallback callback, TvInputInfo info, int userId)
1640                throws RemoteException {
1641            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1642                    != PackageManager.PERMISSION_GRANTED) {
1643                return null;
1644            }
1645
1646            final long identity = Binder.clearCallingIdentity();
1647            final int callingUid = Binder.getCallingUid();
1648            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1649                    userId, "acquireTvInputHardware");
1650            try {
1651                return mTvInputHardwareManager.acquireHardware(
1652                        deviceId, callback, info, callingUid, resolvedUserId);
1653            } finally {
1654                Binder.restoreCallingIdentity(identity);
1655            }
1656        }
1657
1658        @Override
1659        public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
1660                throws RemoteException {
1661            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1662                    != PackageManager.PERMISSION_GRANTED) {
1663                return;
1664            }
1665
1666            final long identity = Binder.clearCallingIdentity();
1667            final int callingUid = Binder.getCallingUid();
1668            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1669                    userId, "releaseTvInputHardware");
1670            try {
1671                mTvInputHardwareManager.releaseHardware(
1672                        deviceId, hardware, callingUid, resolvedUserId);
1673            } finally {
1674                Binder.restoreCallingIdentity(identity);
1675            }
1676        }
1677
1678        @Override
1679        public List<DvbDeviceInfo> getDvbDeviceList() throws RemoteException {
1680            if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)
1681                    != PackageManager.PERMISSION_GRANTED) {
1682                throw new SecurityException("Requires DVB_DEVICE permission");
1683            }
1684
1685            final long identity = Binder.clearCallingIdentity();
1686            try {
1687                ArrayList<DvbDeviceInfo> deviceInfos = new ArrayList<>();
1688                File devDirectory = new File("/dev");
1689                for (String fileName : devDirectory.list()) {
1690                    Matcher matcher = sFrontEndDevicePattern.matcher(fileName);
1691                    if (matcher.find()) {
1692                        int adapterId = Integer.parseInt(matcher.group(1));
1693                        int deviceId = Integer.parseInt(matcher.group(2));
1694                        deviceInfos.add(new DvbDeviceInfo(adapterId, deviceId));
1695                    }
1696                }
1697                return Collections.unmodifiableList(deviceInfos);
1698            } finally {
1699                Binder.restoreCallingIdentity(identity);
1700            }
1701        }
1702
1703        @Override
1704        public ParcelFileDescriptor openDvbDevice(DvbDeviceInfo info, int device)
1705                throws RemoteException {
1706            if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)
1707                    != PackageManager.PERMISSION_GRANTED) {
1708                throw new SecurityException("Requires DVB_DEVICE permission");
1709            }
1710
1711            final long identity = Binder.clearCallingIdentity();
1712            try {
1713                String deviceFileName;
1714                switch (device) {
1715                    case TvInputManager.DVB_DEVICE_DEMUX:
1716                        deviceFileName = String.format("/dev/dvb%d.demux%d", info.getAdapterId(),
1717                                info.getDeviceId());
1718                        break;
1719                    case TvInputManager.DVB_DEVICE_DVR:
1720                        deviceFileName = String.format("/dev/dvb%d.dvr%d", info.getAdapterId(),
1721                                info.getDeviceId());
1722                        break;
1723                    case TvInputManager.DVB_DEVICE_FRONTEND:
1724                        deviceFileName = String.format("/dev/dvb%d.frontend%d", info.getAdapterId(),
1725                                info.getDeviceId());
1726                        break;
1727                    default:
1728                        throw new IllegalArgumentException("Invalid DVB device: " + device);
1729                }
1730                try {
1731                    // The DVB frontend device only needs to be opened in read/write mode, which
1732                    // allows performing tuning operations. The DVB demux and DVR device are enough
1733                    // to be opened in read only mode.
1734                    return ParcelFileDescriptor.open(new File(deviceFileName),
1735                            TvInputManager.DVB_DEVICE_FRONTEND == device
1736                                    ? ParcelFileDescriptor.MODE_READ_WRITE
1737                                    : ParcelFileDescriptor.MODE_READ_ONLY);
1738                } catch (FileNotFoundException e) {
1739                    return null;
1740                }
1741            } finally {
1742                Binder.restoreCallingIdentity(identity);
1743            }
1744        }
1745
1746        @Override
1747        public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int userId)
1748                throws RemoteException {
1749            if (mContext.checkCallingPermission(
1750                    android.Manifest.permission.CAPTURE_TV_INPUT)
1751                    != PackageManager.PERMISSION_GRANTED) {
1752                throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1753            }
1754
1755            final long identity = Binder.clearCallingIdentity();
1756            final int callingUid = Binder.getCallingUid();
1757            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1758                    userId, "getAvailableTvStreamConfigList");
1759            try {
1760                return mTvInputHardwareManager.getAvailableTvStreamConfigList(
1761                        inputId, callingUid, resolvedUserId);
1762            } finally {
1763                Binder.restoreCallingIdentity(identity);
1764            }
1765        }
1766
1767        @Override
1768        public boolean captureFrame(String inputId, Surface surface, TvStreamConfig config,
1769                int userId)
1770                throws RemoteException {
1771            if (mContext.checkCallingPermission(
1772                    android.Manifest.permission.CAPTURE_TV_INPUT)
1773                    != PackageManager.PERMISSION_GRANTED) {
1774                throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1775            }
1776
1777            final long identity = Binder.clearCallingIdentity();
1778            final int callingUid = Binder.getCallingUid();
1779            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1780                    userId, "captureFrame");
1781            try {
1782                String hardwareInputId = null;
1783                synchronized (mLock) {
1784                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
1785                    if (userState.inputMap.get(inputId) == null) {
1786                        Slog.e(TAG, "input not found for " + inputId);
1787                        return false;
1788                    }
1789                    for (SessionState sessionState : userState.sessionStateMap.values()) {
1790                        if (sessionState.inputId.equals(inputId)
1791                                && sessionState.hardwareSessionToken != null) {
1792                            hardwareInputId = userState.sessionStateMap.get(
1793                                    sessionState.hardwareSessionToken).inputId;
1794                            break;
1795                        }
1796                    }
1797                }
1798                return mTvInputHardwareManager.captureFrame(
1799                        (hardwareInputId != null) ? hardwareInputId : inputId,
1800                        surface, config, callingUid, resolvedUserId);
1801            } finally {
1802                Binder.restoreCallingIdentity(identity);
1803            }
1804        }
1805
1806        @Override
1807        public boolean isSingleSessionActive(int userId) throws RemoteException {
1808            final long identity = Binder.clearCallingIdentity();
1809            final int callingUid = Binder.getCallingUid();
1810            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1811                    userId, "isSingleSessionActive");
1812            try {
1813                synchronized (mLock) {
1814                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
1815                    if (userState.sessionStateMap.size() == 1) {
1816                        return true;
1817                    } else if (userState.sessionStateMap.size() == 2) {
1818                        SessionState[] sessionStates = userState.sessionStateMap.values().toArray(
1819                                new SessionState[2]);
1820                        // Check if there is a wrapper input.
1821                        if (sessionStates[0].hardwareSessionToken != null
1822                                || sessionStates[1].hardwareSessionToken != null) {
1823                            return true;
1824                        }
1825                    }
1826                    return false;
1827                }
1828            } finally {
1829                Binder.restoreCallingIdentity(identity);
1830            }
1831        }
1832
1833        @Override
1834        @SuppressWarnings("resource")
1835        protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1836            final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
1837            if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
1838                    != PackageManager.PERMISSION_GRANTED) {
1839                pw.println("Permission Denial: can't dump TvInputManager from pid="
1840                        + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
1841                return;
1842            }
1843
1844            synchronized (mLock) {
1845                pw.println("User Ids (Current user: " + mCurrentUserId + "):");
1846                pw.increaseIndent();
1847                for (int i = 0; i < mUserStates.size(); i++) {
1848                    int userId = mUserStates.keyAt(i);
1849                    pw.println(Integer.valueOf(userId));
1850                }
1851                pw.decreaseIndent();
1852
1853                for (int i = 0; i < mUserStates.size(); i++) {
1854                    int userId = mUserStates.keyAt(i);
1855                    UserState userState = getOrCreateUserStateLocked(userId);
1856                    pw.println("UserState (" + userId + "):");
1857                    pw.increaseIndent();
1858
1859                    pw.println("inputMap: inputId -> TvInputState");
1860                    pw.increaseIndent();
1861                    for (Map.Entry<String, TvInputState> entry: userState.inputMap.entrySet()) {
1862                        pw.println(entry.getKey() + ": " + entry.getValue());
1863                    }
1864                    pw.decreaseIndent();
1865
1866                    pw.println("packageSet:");
1867                    pw.increaseIndent();
1868                    for (String packageName : userState.packageSet) {
1869                        pw.println(packageName);
1870                    }
1871                    pw.decreaseIndent();
1872
1873                    pw.println("clientStateMap: ITvInputClient -> ClientState");
1874                    pw.increaseIndent();
1875                    for (Map.Entry<IBinder, ClientState> entry :
1876                            userState.clientStateMap.entrySet()) {
1877                        ClientState client = entry.getValue();
1878                        pw.println(entry.getKey() + ": " + client);
1879
1880                        pw.increaseIndent();
1881
1882                        pw.println("sessionTokens:");
1883                        pw.increaseIndent();
1884                        for (IBinder token : client.sessionTokens) {
1885                            pw.println("" + token);
1886                        }
1887                        pw.decreaseIndent();
1888
1889                        pw.println("clientTokens: " + client.clientToken);
1890                        pw.println("userId: " + client.userId);
1891
1892                        pw.decreaseIndent();
1893                    }
1894                    pw.decreaseIndent();
1895
1896                    pw.println("serviceStateMap: ComponentName -> ServiceState");
1897                    pw.increaseIndent();
1898                    for (Map.Entry<ComponentName, ServiceState> entry :
1899                            userState.serviceStateMap.entrySet()) {
1900                        ServiceState service = entry.getValue();
1901                        pw.println(entry.getKey() + ": " + service);
1902
1903                        pw.increaseIndent();
1904
1905                        pw.println("sessionTokens:");
1906                        pw.increaseIndent();
1907                        for (IBinder token : service.sessionTokens) {
1908                            pw.println("" + token);
1909                        }
1910                        pw.decreaseIndent();
1911
1912                        pw.println("service: " + service.service);
1913                        pw.println("callback: " + service.callback);
1914                        pw.println("bound: " + service.bound);
1915                        pw.println("reconnecting: " + service.reconnecting);
1916
1917                        pw.decreaseIndent();
1918                    }
1919                    pw.decreaseIndent();
1920
1921                    pw.println("sessionStateMap: ITvInputSession -> SessionState");
1922                    pw.increaseIndent();
1923                    for (Map.Entry<IBinder, SessionState> entry :
1924                            userState.sessionStateMap.entrySet()) {
1925                        SessionState session = entry.getValue();
1926                        pw.println(entry.getKey() + ": " + session);
1927
1928                        pw.increaseIndent();
1929                        pw.println("inputId: " + session.inputId);
1930                        pw.println("client: " + session.client);
1931                        pw.println("seq: " + session.seq);
1932                        pw.println("callingUid: " + session.callingUid);
1933                        pw.println("userId: " + session.userId);
1934                        pw.println("sessionToken: " + session.sessionToken);
1935                        pw.println("session: " + session.session);
1936                        pw.println("logUri: " + session.logUri);
1937                        pw.println("hardwareSessionToken: " + session.hardwareSessionToken);
1938                        pw.decreaseIndent();
1939                    }
1940                    pw.decreaseIndent();
1941
1942                    pw.println("callbackSet:");
1943                    pw.increaseIndent();
1944                    for (ITvInputManagerCallback callback : userState.callbackSet) {
1945                        pw.println(callback.toString());
1946                    }
1947                    pw.decreaseIndent();
1948
1949                    pw.println("mainSessionToken: " + userState.mainSessionToken);
1950                    pw.decreaseIndent();
1951                }
1952            }
1953        }
1954    }
1955
1956    private static final class UserState {
1957        // A mapping from the TV input id to its TvInputState.
1958        private Map<String, TvInputState> inputMap = new HashMap<>();
1959
1960        // A set of all TV input packages.
1961        private final Set<String> packageSet = new HashSet<>();
1962
1963        // A list of all TV content rating systems defined.
1964        private final List<TvContentRatingSystemInfo>
1965                contentRatingSystemList = new ArrayList<>();
1966
1967        // A mapping from the token of a client to its state.
1968        private final Map<IBinder, ClientState> clientStateMap = new HashMap<>();
1969
1970        // A mapping from the name of a TV input service to its state.
1971        private final Map<ComponentName, ServiceState> serviceStateMap = new HashMap<>();
1972
1973        // A mapping from the token of a TV input session to its state.
1974        private final Map<IBinder, SessionState> sessionStateMap = new HashMap<>();
1975
1976        // A set of callbacks.
1977        private final Set<ITvInputManagerCallback> callbackSet = new HashSet<>();
1978
1979        // The token of a "main" TV input session.
1980        private IBinder mainSessionToken = null;
1981
1982        // Persistent data store for all internal settings maintained by the TV input manager
1983        // service.
1984        private final PersistentDataStore persistentDataStore;
1985
1986        private UserState(Context context, int userId) {
1987            persistentDataStore = new PersistentDataStore(context, userId);
1988        }
1989    }
1990
1991    private final class ClientState implements IBinder.DeathRecipient {
1992        private final List<IBinder> sessionTokens = new ArrayList<>();
1993
1994        private IBinder clientToken;
1995        private final int userId;
1996
1997        ClientState(IBinder clientToken, int userId) {
1998            this.clientToken = clientToken;
1999            this.userId = userId;
2000        }
2001
2002        public boolean isEmpty() {
2003            return sessionTokens.isEmpty();
2004        }
2005
2006        @Override
2007        public void binderDied() {
2008            synchronized (mLock) {
2009                UserState userState = getOrCreateUserStateLocked(userId);
2010                // DO NOT remove the client state of clientStateMap in this method. It will be
2011                // removed in releaseSessionLocked().
2012                ClientState clientState = userState.clientStateMap.get(clientToken);
2013                if (clientState != null) {
2014                    while (clientState.sessionTokens.size() > 0) {
2015                        releaseSessionLocked(
2016                                clientState.sessionTokens.get(0), Process.SYSTEM_UID, userId);
2017                    }
2018                }
2019                clientToken = null;
2020            }
2021        }
2022    }
2023
2024    private final class ServiceState {
2025        private final List<IBinder> sessionTokens = new ArrayList<>();
2026        private final ServiceConnection connection;
2027        private final ComponentName component;
2028        private final boolean isHardware;
2029        private final List<TvInputInfo> hardwareInputList = new ArrayList<>();
2030
2031        private ITvInputService service;
2032        private ServiceCallback callback;
2033        private boolean bound;
2034        private boolean reconnecting;
2035
2036        private ServiceState(ComponentName component, int userId) {
2037            this.component = component;
2038            this.connection = new InputServiceConnection(component, userId);
2039            this.isHardware = hasHardwarePermission(mContext.getPackageManager(), component);
2040        }
2041    }
2042
2043    private static final class TvInputState {
2044        // A TvInputInfo object which represents the TV input.
2045        private TvInputInfo info;
2046
2047        // The state of TV input. Connected by default.
2048        private int state = INPUT_STATE_CONNECTED;
2049
2050        @Override
2051        public String toString() {
2052            return "info: " + info + "; state: " + state;
2053        }
2054    }
2055
2056    private final class SessionState implements IBinder.DeathRecipient {
2057        private final String inputId;
2058        private final ComponentName componentName;
2059        private final boolean isRecordingSession;
2060        private final ITvInputClient client;
2061        private final int seq;
2062        private final int callingUid;
2063        private final int userId;
2064        private final IBinder sessionToken;
2065        private ITvInputSession session;
2066        private Uri logUri;
2067        // Not null if this session represents an external device connected to a hardware TV input.
2068        private IBinder hardwareSessionToken;
2069
2070        private SessionState(IBinder sessionToken, String inputId, ComponentName componentName,
2071                boolean isRecordingSession, ITvInputClient client, int seq, int callingUid,
2072                int userId) {
2073            this.sessionToken = sessionToken;
2074            this.inputId = inputId;
2075            this.componentName = componentName;
2076            this.isRecordingSession = isRecordingSession;
2077            this.client = client;
2078            this.seq = seq;
2079            this.callingUid = callingUid;
2080            this.userId = userId;
2081        }
2082
2083        @Override
2084        public void binderDied() {
2085            synchronized (mLock) {
2086                session = null;
2087                if (client != null) {
2088                    try {
2089                        client.onSessionReleased(seq);
2090                    } catch(RemoteException e) {
2091                        Slog.e(TAG, "error in onSessionReleased", e);
2092                    }
2093                }
2094                // If there are any other sessions based on this session, they should be released.
2095                UserState userState = getOrCreateUserStateLocked(userId);
2096                for (SessionState sessionState : userState.sessionStateMap.values()) {
2097                    if (sessionToken == sessionState.hardwareSessionToken) {
2098                        releaseSessionLocked(sessionState.sessionToken, Process.SYSTEM_UID,
2099                                userId);
2100                        try {
2101                            sessionState.client.onSessionReleased(sessionState.seq);
2102                        } catch (RemoteException e) {
2103                            Slog.e(TAG, "error in onSessionReleased", e);
2104                        }
2105                    }
2106                }
2107                removeSessionStateLocked(sessionToken, userId);
2108            }
2109        }
2110    }
2111
2112    private final class InputServiceConnection implements ServiceConnection {
2113        private final ComponentName mComponent;
2114        private final int mUserId;
2115
2116        private InputServiceConnection(ComponentName component, int userId) {
2117            mComponent = component;
2118            mUserId = userId;
2119        }
2120
2121        @Override
2122        public void onServiceConnected(ComponentName component, IBinder service) {
2123            if (DEBUG) {
2124                Slog.d(TAG, "onServiceConnected(component=" + component + ")");
2125            }
2126            synchronized (mLock) {
2127                UserState userState = mUserStates.get(mUserId);
2128                if (userState == null) {
2129                    // The user was removed while connecting.
2130                    mContext.unbindService(this);
2131                    return;
2132                }
2133                ServiceState serviceState = userState.serviceStateMap.get(mComponent);
2134                serviceState.service = ITvInputService.Stub.asInterface(service);
2135
2136                // Register a callback, if we need to.
2137                if (serviceState.isHardware && serviceState.callback == null) {
2138                    serviceState.callback = new ServiceCallback(mComponent, mUserId);
2139                    try {
2140                        serviceState.service.registerCallback(serviceState.callback);
2141                    } catch (RemoteException e) {
2142                        Slog.e(TAG, "error in registerCallback", e);
2143                    }
2144                }
2145
2146                // And create sessions, if any.
2147                for (IBinder sessionToken : serviceState.sessionTokens) {
2148                    createSessionInternalLocked(serviceState.service, sessionToken, mUserId);
2149                }
2150
2151                for (TvInputState inputState : userState.inputMap.values()) {
2152                    if (inputState.info.getComponent().equals(component)
2153                            && inputState.state != INPUT_STATE_CONNECTED) {
2154                        notifyInputStateChangedLocked(userState, inputState.info.getId(),
2155                                inputState.state, null);
2156                    }
2157                }
2158
2159                if (serviceState.isHardware) {
2160                    List<TvInputHardwareInfo> hardwareInfoList =
2161                            mTvInputHardwareManager.getHardwareList();
2162                    for (TvInputHardwareInfo hardwareInfo : hardwareInfoList) {
2163                        try {
2164                            serviceState.service.notifyHardwareAdded(hardwareInfo);
2165                        } catch (RemoteException e) {
2166                            Slog.e(TAG, "error in notifyHardwareAdded", e);
2167                        }
2168                    }
2169
2170                    List<HdmiDeviceInfo> deviceInfoList =
2171                            mTvInputHardwareManager.getHdmiDeviceList();
2172                    for (HdmiDeviceInfo deviceInfo : deviceInfoList) {
2173                        try {
2174                            serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
2175                        } catch (RemoteException e) {
2176                            Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
2177                        }
2178                    }
2179                }
2180            }
2181        }
2182
2183        @Override
2184        public void onServiceDisconnected(ComponentName component) {
2185            if (DEBUG) {
2186                Slog.d(TAG, "onServiceDisconnected(component=" + component + ")");
2187            }
2188            if (!mComponent.equals(component)) {
2189                throw new IllegalArgumentException("Mismatched ComponentName: "
2190                        + mComponent + " (expected), " + component + " (actual).");
2191            }
2192            synchronized (mLock) {
2193                UserState userState = getOrCreateUserStateLocked(mUserId);
2194                ServiceState serviceState = userState.serviceStateMap.get(mComponent);
2195                if (serviceState != null) {
2196                    serviceState.reconnecting = true;
2197                    serviceState.bound = false;
2198                    serviceState.service = null;
2199                    serviceState.callback = null;
2200
2201                    abortPendingCreateSessionRequestsLocked(serviceState, null, mUserId);
2202                }
2203            }
2204        }
2205    }
2206
2207    private final class ServiceCallback extends ITvInputServiceCallback.Stub {
2208        private final ComponentName mComponent;
2209        private final int mUserId;
2210
2211        ServiceCallback(ComponentName component, int userId) {
2212            mComponent = component;
2213            mUserId = userId;
2214        }
2215
2216        private void ensureHardwarePermission() {
2217            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
2218                    != PackageManager.PERMISSION_GRANTED) {
2219                throw new SecurityException("The caller does not have hardware permission");
2220            }
2221        }
2222
2223        private void ensureValidInput(TvInputInfo inputInfo) {
2224            if (inputInfo.getId() == null || !mComponent.equals(inputInfo.getComponent())) {
2225                throw new IllegalArgumentException("Invalid TvInputInfo");
2226            }
2227        }
2228
2229        private void addHardwareInputLocked(TvInputInfo inputInfo) {
2230            ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
2231            serviceState.hardwareInputList.add(inputInfo);
2232            buildTvInputListLocked(mUserId, null);
2233        }
2234
2235        public void addHardwareInput(int deviceId, TvInputInfo inputInfo) {
2236            ensureHardwarePermission();
2237            ensureValidInput(inputInfo);
2238            synchronized (mLock) {
2239                mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo);
2240                addHardwareInputLocked(inputInfo);
2241            }
2242        }
2243
2244        public void addHdmiInput(int id, TvInputInfo inputInfo) {
2245            ensureHardwarePermission();
2246            ensureValidInput(inputInfo);
2247            synchronized (mLock) {
2248                mTvInputHardwareManager.addHdmiInput(id, inputInfo);
2249                addHardwareInputLocked(inputInfo);
2250            }
2251        }
2252
2253        public void removeHardwareInput(String inputId) {
2254            ensureHardwarePermission();
2255            synchronized (mLock) {
2256                ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
2257                boolean removed = false;
2258                for (Iterator<TvInputInfo> it = serviceState.hardwareInputList.iterator();
2259                        it.hasNext(); ) {
2260                    if (it.next().getId().equals(inputId)) {
2261                        it.remove();
2262                        removed = true;
2263                        break;
2264                    }
2265                }
2266                if (removed) {
2267                    buildTvInputListLocked(mUserId, null);
2268                    mTvInputHardwareManager.removeHardwareInput(inputId);
2269                } else {
2270                    Slog.e(TAG, "failed to remove input " + inputId);
2271                }
2272            }
2273        }
2274    }
2275
2276    private final class SessionCallback extends ITvInputSessionCallback.Stub {
2277        private final SessionState mSessionState;
2278        private final InputChannel[] mChannels;
2279
2280        SessionCallback(SessionState sessionState, InputChannel[] channels) {
2281            mSessionState = sessionState;
2282            mChannels = channels;
2283        }
2284
2285        @Override
2286        public void onSessionCreated(ITvInputSession session, IBinder hardwareSessionToken) {
2287            if (DEBUG) {
2288                Slog.d(TAG, "onSessionCreated(inputId=" + mSessionState.inputId + ")");
2289            }
2290            synchronized (mLock) {
2291                mSessionState.session = session;
2292                mSessionState.hardwareSessionToken = hardwareSessionToken;
2293                if (session != null && addSessionTokenToClientStateLocked(session)) {
2294                    sendSessionTokenToClientLocked(mSessionState.client,
2295                            mSessionState.inputId, mSessionState.sessionToken, mChannels[0],
2296                            mSessionState.seq);
2297                } else {
2298                    removeSessionStateLocked(mSessionState.sessionToken, mSessionState.userId);
2299                    sendSessionTokenToClientLocked(mSessionState.client,
2300                            mSessionState.inputId, null, null, mSessionState.seq);
2301                }
2302                mChannels[0].dispose();
2303            }
2304        }
2305
2306        private boolean addSessionTokenToClientStateLocked(ITvInputSession session) {
2307            try {
2308                session.asBinder().linkToDeath(mSessionState, 0);
2309            } catch (RemoteException e) {
2310                Slog.e(TAG, "session process has already died", e);
2311                return false;
2312            }
2313
2314            IBinder clientToken = mSessionState.client.asBinder();
2315            UserState userState = getOrCreateUserStateLocked(mSessionState.userId);
2316            ClientState clientState = userState.clientStateMap.get(clientToken);
2317            if (clientState == null) {
2318                clientState = new ClientState(clientToken, mSessionState.userId);
2319                try {
2320                    clientToken.linkToDeath(clientState, 0);
2321                } catch (RemoteException e) {
2322                    Slog.e(TAG, "client process has already died", e);
2323                    return false;
2324                }
2325                userState.clientStateMap.put(clientToken, clientState);
2326            }
2327            clientState.sessionTokens.add(mSessionState.sessionToken);
2328            return true;
2329        }
2330
2331        @Override
2332        public void onChannelRetuned(Uri channelUri) {
2333            synchronized (mLock) {
2334                if (DEBUG) {
2335                    Slog.d(TAG, "onChannelRetuned(" + channelUri + ")");
2336                }
2337                if (mSessionState.session == null || mSessionState.client == null) {
2338                    return;
2339                }
2340                try {
2341                    // TODO: Consider adding this channel change in the watch log. When we do
2342                    // that, how we can protect the watch log from malicious tv inputs should
2343                    // be addressed. e.g. add a field which represents where the channel change
2344                    // originated from.
2345                    mSessionState.client.onChannelRetuned(channelUri, mSessionState.seq);
2346                } catch (RemoteException e) {
2347                    Slog.e(TAG, "error in onChannelRetuned", e);
2348                }
2349            }
2350        }
2351
2352        @Override
2353        public void onTracksChanged(List<TvTrackInfo> tracks) {
2354            synchronized (mLock) {
2355                if (DEBUG) {
2356                    Slog.d(TAG, "onTracksChanged(" + tracks + ")");
2357                }
2358                if (mSessionState.session == null || mSessionState.client == null) {
2359                    return;
2360                }
2361                try {
2362                    mSessionState.client.onTracksChanged(tracks, mSessionState.seq);
2363                } catch (RemoteException e) {
2364                    Slog.e(TAG, "error in onTracksChanged", e);
2365                }
2366            }
2367        }
2368
2369        @Override
2370        public void onTrackSelected(int type, String trackId) {
2371            synchronized (mLock) {
2372                if (DEBUG) {
2373                    Slog.d(TAG, "onTrackSelected(type=" + type + ", trackId=" + trackId + ")");
2374                }
2375                if (mSessionState.session == null || mSessionState.client == null) {
2376                    return;
2377                }
2378                try {
2379                    mSessionState.client.onTrackSelected(type, trackId, mSessionState.seq);
2380                } catch (RemoteException e) {
2381                    Slog.e(TAG, "error in onTrackSelected", e);
2382                }
2383            }
2384        }
2385
2386        @Override
2387        public void onVideoAvailable() {
2388            synchronized (mLock) {
2389                if (DEBUG) {
2390                    Slog.d(TAG, "onVideoAvailable()");
2391                }
2392                if (mSessionState.session == null || mSessionState.client == null) {
2393                    return;
2394                }
2395                try {
2396                    mSessionState.client.onVideoAvailable(mSessionState.seq);
2397                } catch (RemoteException e) {
2398                    Slog.e(TAG, "error in onVideoAvailable", e);
2399                }
2400            }
2401        }
2402
2403        @Override
2404        public void onVideoUnavailable(int reason) {
2405            synchronized (mLock) {
2406                if (DEBUG) {
2407                    Slog.d(TAG, "onVideoUnavailable(" + reason + ")");
2408                }
2409                if (mSessionState.session == null || mSessionState.client == null) {
2410                    return;
2411                }
2412                try {
2413                    mSessionState.client.onVideoUnavailable(reason, mSessionState.seq);
2414                } catch (RemoteException e) {
2415                    Slog.e(TAG, "error in onVideoUnavailable", e);
2416                }
2417            }
2418        }
2419
2420        @Override
2421        public void onContentAllowed() {
2422            synchronized (mLock) {
2423                if (DEBUG) {
2424                    Slog.d(TAG, "onContentAllowed()");
2425                }
2426                if (mSessionState.session == null || mSessionState.client == null) {
2427                    return;
2428                }
2429                try {
2430                    mSessionState.client.onContentAllowed(mSessionState.seq);
2431                } catch (RemoteException e) {
2432                    Slog.e(TAG, "error in onContentAllowed", e);
2433                }
2434            }
2435        }
2436
2437        @Override
2438        public void onContentBlocked(String rating) {
2439            synchronized (mLock) {
2440                if (DEBUG) {
2441                    Slog.d(TAG, "onContentBlocked()");
2442                }
2443                if (mSessionState.session == null || mSessionState.client == null) {
2444                    return;
2445                }
2446                try {
2447                    mSessionState.client.onContentBlocked(rating, mSessionState.seq);
2448                } catch (RemoteException e) {
2449                    Slog.e(TAG, "error in onContentBlocked", e);
2450                }
2451            }
2452        }
2453
2454        @Override
2455        public void onLayoutSurface(int left, int top, int right, int bottom) {
2456            synchronized (mLock) {
2457                if (DEBUG) {
2458                    Slog.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top
2459                            + ", right=" + right + ", bottom=" + bottom + ",)");
2460                }
2461                if (mSessionState.session == null || mSessionState.client == null) {
2462                    return;
2463                }
2464                try {
2465                    mSessionState.client.onLayoutSurface(left, top, right, bottom,
2466                            mSessionState.seq);
2467                } catch (RemoteException e) {
2468                    Slog.e(TAG, "error in onLayoutSurface", e);
2469                }
2470            }
2471        }
2472
2473        @Override
2474        public void onSessionEvent(String eventType, Bundle eventArgs) {
2475            synchronized (mLock) {
2476                if (DEBUG) {
2477                    Slog.d(TAG, "onEvent(what=" + eventType + ", data=" + eventArgs + ")");
2478                }
2479                if (mSessionState.session == null || mSessionState.client == null) {
2480                    return;
2481                }
2482                try {
2483                    mSessionState.client.onSessionEvent(eventType, eventArgs, mSessionState.seq);
2484                } catch (RemoteException e) {
2485                    Slog.e(TAG, "error in onSessionEvent", e);
2486                }
2487            }
2488        }
2489
2490        @Override
2491        public void onTimeShiftStatusChanged(int status) {
2492            synchronized (mLock) {
2493                if (DEBUG) {
2494                    Slog.d(TAG, "onTimeShiftStatusChanged()");
2495                }
2496                if (mSessionState.session == null || mSessionState.client == null) {
2497                    return;
2498                }
2499                try {
2500                    mSessionState.client.onTimeShiftStatusChanged(status, mSessionState.seq);
2501                } catch (RemoteException e) {
2502                    Slog.e(TAG, "error in onTimeShiftStatusChanged", e);
2503                }
2504            }
2505        }
2506
2507        @Override
2508        public void onTimeShiftStartPositionChanged(long timeMs) {
2509            synchronized (mLock) {
2510                if (DEBUG) {
2511                    Slog.d(TAG, "onTimeShiftStartPositionChanged()");
2512                }
2513                if (mSessionState.session == null || mSessionState.client == null) {
2514                    return;
2515                }
2516                try {
2517                    mSessionState.client.onTimeShiftStartPositionChanged(timeMs, mSessionState.seq);
2518                } catch (RemoteException e) {
2519                    Slog.e(TAG, "error in onTimeShiftStartPositionChanged", e);
2520                }
2521            }
2522        }
2523
2524        @Override
2525        public void onTimeShiftCurrentPositionChanged(long timeMs) {
2526            synchronized (mLock) {
2527                if (DEBUG) {
2528                    Slog.d(TAG, "onTimeShiftCurrentPositionChanged()");
2529                }
2530                if (mSessionState.session == null || mSessionState.client == null) {
2531                    return;
2532                }
2533                try {
2534                    mSessionState.client.onTimeShiftCurrentPositionChanged(timeMs,
2535                            mSessionState.seq);
2536                } catch (RemoteException e) {
2537                    Slog.e(TAG, "error in onTimeShiftCurrentPositionChanged", e);
2538                }
2539            }
2540        }
2541
2542        // For the recording session only
2543        @Override
2544        public void onConnected() {
2545            synchronized (mLock) {
2546                if (DEBUG) {
2547                    Slog.d(TAG, "onConnected()");
2548                }
2549                if (mSessionState.session == null || mSessionState.client == null) {
2550                    return;
2551                }
2552                try {
2553                    mSessionState.client.onConnected(mSessionState.seq);
2554                } catch (RemoteException e) {
2555                    Slog.e(TAG, "error in onConnected", e);
2556                }
2557            }
2558        }
2559
2560        // For the recording session only
2561        @Override
2562        public void onRecordingStarted() {
2563            synchronized (mLock) {
2564                if (DEBUG) {
2565                    Slog.d(TAG, "onRecordingStarted()");
2566                }
2567                if (mSessionState.session == null || mSessionState.client == null) {
2568                    return;
2569                }
2570                try {
2571                    mSessionState.client.onRecordingStarted(mSessionState.seq);
2572                } catch (RemoteException e) {
2573                    Slog.e(TAG, "error in onRecordingStarted", e);
2574                }
2575            }
2576        }
2577
2578        // For the recording session only
2579        @Override
2580        public void onRecordingStopped(Uri recordedProgramUri) {
2581            synchronized (mLock) {
2582                if (DEBUG) {
2583                    Slog.d(TAG, "onRecordingStopped()");
2584                }
2585                if (mSessionState.session == null || mSessionState.client == null) {
2586                    return;
2587                }
2588                try {
2589                    mSessionState.client.onRecordingStopped(recordedProgramUri, mSessionState.seq);
2590                } catch (RemoteException e) {
2591                    Slog.e(TAG, "error in onRecordingStopped", e);
2592                }
2593            }
2594        }
2595
2596        // For the recording session only
2597        @Override
2598        public void onError(int error) {
2599            synchronized (mLock) {
2600                if (DEBUG) {
2601                    Slog.d(TAG, "onError()");
2602                }
2603                if (mSessionState.session == null || mSessionState.client == null) {
2604                    return;
2605                }
2606                try {
2607                    mSessionState.client.onError(error, mSessionState.seq);
2608                } catch (RemoteException e) {
2609                    Slog.e(TAG, "error in onError", e);
2610                }
2611            }
2612        }
2613    }
2614
2615    private static final class WatchLogHandler extends Handler {
2616        // There are only two kinds of watch events that can happen on the system:
2617        // 1. The current TV input session is tuned to a new channel.
2618        // 2. The session is released for some reason.
2619        // The former indicates the end of the previous log entry, if any, followed by the start of
2620        // a new entry. The latter indicates the end of the most recent entry for the given session.
2621        // Here the system supplies the database the smallest set of information only that is
2622        // sufficient to consolidate the log entries while minimizing database operations in the
2623        // system service.
2624        static final int MSG_LOG_WATCH_START = 1;
2625        static final int MSG_LOG_WATCH_END = 2;
2626        static final int MSG_SWITCH_CONTENT_RESOLVER = 3;
2627
2628        private ContentResolver mContentResolver;
2629
2630        WatchLogHandler(ContentResolver contentResolver, Looper looper) {
2631            super(looper);
2632            mContentResolver = contentResolver;
2633        }
2634
2635        @Override
2636        public void handleMessage(Message msg) {
2637            switch (msg.what) {
2638                case MSG_LOG_WATCH_START: {
2639                    SomeArgs args = (SomeArgs) msg.obj;
2640                    String packageName = (String) args.arg1;
2641                    long watchStartTime = (long) args.arg2;
2642                    long channelId = (long) args.arg3;
2643                    Bundle tuneParams = (Bundle) args.arg4;
2644                    IBinder sessionToken = (IBinder) args.arg5;
2645
2646                    ContentValues values = new ContentValues();
2647                    values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
2648                    values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
2649                            watchStartTime);
2650                    values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
2651                    if (tuneParams != null) {
2652                        values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS,
2653                                encodeTuneParams(tuneParams));
2654                    }
2655                    values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
2656                            sessionToken.toString());
2657
2658                    mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
2659                    args.recycle();
2660                    break;
2661                }
2662                case MSG_LOG_WATCH_END: {
2663                    SomeArgs args = (SomeArgs) msg.obj;
2664                    IBinder sessionToken = (IBinder) args.arg1;
2665                    long watchEndTime = (long) args.arg2;
2666
2667                    ContentValues values = new ContentValues();
2668                    values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
2669                            watchEndTime);
2670                    values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
2671                            sessionToken.toString());
2672
2673                    mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
2674                    args.recycle();
2675                    break;
2676                }
2677                case MSG_SWITCH_CONTENT_RESOLVER: {
2678                    mContentResolver = (ContentResolver) msg.obj;
2679                    break;
2680                }
2681                default: {
2682                    Slog.w(TAG, "unhandled message code: " + msg.what);
2683                    break;
2684                }
2685            }
2686        }
2687
2688        private String encodeTuneParams(Bundle tuneParams) {
2689            StringBuilder builder = new StringBuilder();
2690            Set<String> keySet = tuneParams.keySet();
2691            Iterator<String> it = keySet.iterator();
2692            while (it.hasNext()) {
2693                String key = it.next();
2694                Object value = tuneParams.get(key);
2695                if (value == null) {
2696                    continue;
2697                }
2698                builder.append(replaceEscapeCharacters(key));
2699                builder.append("=");
2700                builder.append(replaceEscapeCharacters(value.toString()));
2701                if (it.hasNext()) {
2702                    builder.append(", ");
2703                }
2704            }
2705            return builder.toString();
2706        }
2707
2708        private String replaceEscapeCharacters(String src) {
2709            final char ESCAPE_CHARACTER = '%';
2710            final String ENCODING_TARGET_CHARACTERS = "%=,";
2711            StringBuilder builder = new StringBuilder();
2712            for (char ch : src.toCharArray()) {
2713                if (ENCODING_TARGET_CHARACTERS.indexOf(ch) >= 0) {
2714                    builder.append(ESCAPE_CHARACTER);
2715                }
2716                builder.append(ch);
2717            }
2718            return builder.toString();
2719        }
2720    }
2721
2722    private final class HardwareListener implements TvInputHardwareManager.Listener {
2723        @Override
2724        public void onStateChanged(String inputId, int state) {
2725            synchronized (mLock) {
2726                setStateLocked(inputId, state, mCurrentUserId);
2727            }
2728        }
2729
2730        @Override
2731        public void onHardwareDeviceAdded(TvInputHardwareInfo info) {
2732            synchronized (mLock) {
2733                UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
2734                // Broadcast the event to all hardware inputs.
2735                for (ServiceState serviceState : userState.serviceStateMap.values()) {
2736                    if (!serviceState.isHardware || serviceState.service == null) continue;
2737                    try {
2738                        serviceState.service.notifyHardwareAdded(info);
2739                    } catch (RemoteException e) {
2740                        Slog.e(TAG, "error in notifyHardwareAdded", e);
2741                    }
2742                }
2743            }
2744        }
2745
2746        @Override
2747        public void onHardwareDeviceRemoved(TvInputHardwareInfo info) {
2748            synchronized (mLock) {
2749                UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
2750                // Broadcast the event to all hardware inputs.
2751                for (ServiceState serviceState : userState.serviceStateMap.values()) {
2752                    if (!serviceState.isHardware || serviceState.service == null) continue;
2753                    try {
2754                        serviceState.service.notifyHardwareRemoved(info);
2755                    } catch (RemoteException e) {
2756                        Slog.e(TAG, "error in notifyHardwareRemoved", e);
2757                    }
2758                }
2759            }
2760        }
2761
2762        @Override
2763        public void onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
2764            synchronized (mLock) {
2765                UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
2766                // Broadcast the event to all hardware inputs.
2767                for (ServiceState serviceState : userState.serviceStateMap.values()) {
2768                    if (!serviceState.isHardware || serviceState.service == null) continue;
2769                    try {
2770                        serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
2771                    } catch (RemoteException e) {
2772                        Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
2773                    }
2774                }
2775            }
2776        }
2777
2778        @Override
2779        public void onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
2780            synchronized (mLock) {
2781                UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
2782                // Broadcast the event to all hardware inputs.
2783                for (ServiceState serviceState : userState.serviceStateMap.values()) {
2784                    if (!serviceState.isHardware || serviceState.service == null) continue;
2785                    try {
2786                        serviceState.service.notifyHdmiDeviceRemoved(deviceInfo);
2787                    } catch (RemoteException e) {
2788                        Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e);
2789                    }
2790                }
2791            }
2792        }
2793
2794        @Override
2795        public void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo deviceInfo) {
2796            synchronized (mLock) {
2797                Integer state;
2798                switch (deviceInfo.getDevicePowerStatus()) {
2799                    case HdmiControlManager.POWER_STATUS_ON:
2800                        state = INPUT_STATE_CONNECTED;
2801                        break;
2802                    case HdmiControlManager.POWER_STATUS_STANDBY:
2803                    case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON:
2804                    case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY:
2805                        state = INPUT_STATE_CONNECTED_STANDBY;
2806                        break;
2807                    case HdmiControlManager.POWER_STATUS_UNKNOWN:
2808                    default:
2809                        state = null;
2810                        break;
2811                }
2812                if (state != null) {
2813                    setStateLocked(inputId, state, mCurrentUserId);
2814                }
2815            }
2816        }
2817    }
2818
2819    private static class SessionNotFoundException extends IllegalArgumentException {
2820        public SessionNotFoundException(String name) {
2821            super(name);
2822        }
2823    }
2824}
2825