diff options
| -rw-r--r-- | api/current.txt | 10 | ||||
| -rw-r--r-- | core/java/android/tv/ITvInputClient.aidl | 1 | ||||
| -rw-r--r-- | core/java/android/tv/TvInputManager.java | 111 | ||||
| -rw-r--r-- | core/java/android/tv/TvView.java | 30 | ||||
| -rw-r--r-- | services/core/java/com/android/server/tv/TvInputManagerService.java | 162 |
5 files changed, 230 insertions, 84 deletions
diff --git a/api/current.txt b/api/current.txt index 8513b5fca7a5..17f5e5394cd7 100644 --- a/api/current.txt +++ b/api/current.txt @@ -29179,7 +29179,7 @@ package android.tv { } public final class TvInputManager { - method public void createSession(java.lang.String, android.tv.TvInputManager.SessionCreateCallback, android.os.Handler); + method public void createSession(java.lang.String, android.tv.TvInputManager.SessionCallback, android.os.Handler); method public boolean getAvailability(java.lang.String); method public java.util.List<android.tv.TvInputInfo> getTvInputList(); method public void registerListener(java.lang.String, android.tv.TvInputManager.TvInputListener, android.os.Handler); @@ -29192,8 +29192,10 @@ package android.tv { method public void tune(android.net.Uri); } - public static abstract interface TvInputManager.SessionCreateCallback { - method public abstract void onSessionCreated(android.tv.TvInputManager.Session); + public static abstract class TvInputManager.SessionCallback { + ctor public TvInputManager.SessionCallback(); + method public void onSessionCreated(android.tv.TvInputManager.Session); + method public void onSessionReleased(android.tv.TvInputManager.Session); } public static abstract class TvInputManager.TvInputListener { @@ -29230,7 +29232,7 @@ package android.tv { ctor public TvView(android.content.Context); ctor public TvView(android.content.Context, android.util.AttributeSet); ctor public TvView(android.content.Context, android.util.AttributeSet, int); - method public void bindTvInput(java.lang.String, android.tv.TvInputManager.SessionCreateCallback); + method public void bindTvInput(java.lang.String, android.tv.TvInputManager.SessionCallback); method public boolean dispatchUnhandledInputEvent(android.view.InputEvent); method public boolean onUnhandledInputEvent(android.view.InputEvent); method public void setOnUnhandledInputEventListener(android.tv.TvView.OnUnhandledInputEventListener); diff --git a/core/java/android/tv/ITvInputClient.aidl b/core/java/android/tv/ITvInputClient.aidl index 2adcaf108308..ac83356a969e 100644 --- a/core/java/android/tv/ITvInputClient.aidl +++ b/core/java/android/tv/ITvInputClient.aidl @@ -28,4 +28,5 @@ import android.view.InputChannel; oneway interface ITvInputClient { void onSessionCreated(in String inputId, IBinder token, in InputChannel channel, int seq); void onAvailabilityChanged(in String inputId, boolean isAvailable); + void onSessionReleased(int seq); } diff --git a/core/java/android/tv/TvInputManager.java b/core/java/android/tv/TvInputManager.java index e246fcf8dd1a..c5f179a337c0 100644 --- a/core/java/android/tv/TvInputManager.java +++ b/core/java/android/tv/TvInputManager.java @@ -52,12 +52,12 @@ public final class TvInputManager { private final Map<String, List<TvInputListenerRecord>> mTvInputListenerRecordsMap = new HashMap<String, List<TvInputListenerRecord>>(); - // A mapping from the sequence number of a session to its SessionCreateCallbackRecord. - private final SparseArray<SessionCreateCallbackRecord> mSessionCreateCallbackRecordMap = - new SparseArray<SessionCreateCallbackRecord>(); + // A mapping from the sequence number of a session to its SessionCallbackRecord. + private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap = + new SparseArray<SessionCallbackRecord>(); // A sequence number for the next session to be created. Should be protected by a lock - // {@code mSessionCreateCallbackRecordMap}. + // {@code mSessionCallbackRecordMap}. private int mNextSeq; private final ITvInputClient mClient; @@ -67,31 +67,52 @@ public final class TvInputManager { /** * Interface used to receive the created session. */ - public interface SessionCreateCallback { + public abstract static class SessionCallback { /** * This is called after {@link TvInputManager#createSession} has been processed. * * @param session A {@link TvInputManager.Session} instance created. This can be * {@code null} if the creation request failed. */ - void onSessionCreated(Session session); + public void onSessionCreated(Session session) { + } + + /** + * This is called when {@link TvInputManager.Session} is released. + * This typically happens when the process hosting the session has crashed or been killed. + * + * @param session A {@link TvInputManager.Session} instance released. + */ + public void onSessionReleased(Session session) { + } } - private static final class SessionCreateCallbackRecord { - private final SessionCreateCallback mSessionCreateCallback; + private static final class SessionCallbackRecord { + private final SessionCallback mSessionCallback; private final Handler mHandler; + private Session mSession; - public SessionCreateCallbackRecord(SessionCreateCallback sessionCreateCallback, + public SessionCallbackRecord(SessionCallback sessionCallback, Handler handler) { - mSessionCreateCallback = sessionCreateCallback; + mSessionCallback = sessionCallback; mHandler = handler; } public void postSessionCreated(final Session session) { + mSession = session; mHandler.post(new Runnable() { @Override public void run() { - mSessionCreateCallback.onSessionCreated(session); + mSessionCallback.onSessionCreated(session); + } + }); + } + + public void postSessionReleased() { + mHandler.post(new Runnable() { + @Override + public void run() { + mSessionCallback.onSessionReleased(mSession); } }); } @@ -145,22 +166,36 @@ public final class TvInputManager { @Override public void onSessionCreated(String inputId, IBinder token, InputChannel channel, int seq) { - synchronized (mSessionCreateCallbackRecordMap) { - SessionCreateCallbackRecord record = mSessionCreateCallbackRecordMap.get(seq); - mSessionCreateCallbackRecordMap.delete(seq); + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); if (record == null) { Log.e(TAG, "Callback not found for " + token); return; } Session session = null; if (token != null) { - session = new Session(token, channel, mService, mUserId); + session = new Session(token, channel, mService, mUserId, seq, + mSessionCallbackRecordMap); } record.postSessionCreated(session); } } @Override + public void onSessionReleased(int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + mSessionCallbackRecordMap.delete(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq:" + seq); + return; + } + record.mSession.releaseInternal(); + record.postSessionReleased(); + } + } + + @Override public void onAvailabilityChanged(String inputId, boolean isAvailable) { synchronized (mTvInputListenerRecordsMap) { List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId); @@ -298,7 +333,7 @@ public final class TvInputManager { * @param handler a {@link Handler} that the session creation will be delivered to. * @throws IllegalArgumentException if any of the arguments is {@code null}. */ - public void createSession(String inputId, final SessionCreateCallback callback, + public void createSession(String inputId, final SessionCallback callback, Handler handler) { if (inputId == null) { throw new IllegalArgumentException("id cannot be null"); @@ -309,10 +344,10 @@ public final class TvInputManager { if (handler == null) { throw new IllegalArgumentException("handler cannot be null"); } - SessionCreateCallbackRecord record = new SessionCreateCallbackRecord(callback, handler); - synchronized (mSessionCreateCallbackRecordMap) { + SessionCallbackRecord record = new SessionCallbackRecord(callback, handler); + synchronized (mSessionCallbackRecordMap) { int seq = mNextSeq++; - mSessionCreateCallbackRecordMap.put(seq, record); + mSessionCallbackRecordMap.put(seq, record); try { mService.createSession(mClient, inputId, seq, mUserId); } catch (RemoteException e) { @@ -331,6 +366,7 @@ public final class TvInputManager { private final ITvInputManager mService; private final int mUserId; + private final int mSeq; // For scheduling input event handling on the main thread. This also serves as a lock to // protect pending input events and the input channel. @@ -338,17 +374,21 @@ public final class TvInputManager { private final Pool<PendingEvent> mPendingEventPool = new SimplePool<PendingEvent>(20); private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<PendingEvent>(20); + private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap; private IBinder mToken; private TvInputEventSender mSender; private InputChannel mChannel; /** @hide */ - private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId) { + private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId, + int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) { mToken = token; mChannel = channel; mService = service; mUserId = userId; + mSeq = seq; + mSessionCallbackRecordMap = sessionCallbackRecordMap; } /** @@ -362,22 +402,11 @@ public final class TvInputManager { } try { mService.releaseSession(mToken, mUserId); - mToken = null; } catch (RemoteException e) { throw new RuntimeException(e); } - synchronized (mHandler) { - if (mChannel != null) { - if (mSender != null) { - flushPendingEventsLocked(); - mSender.dispose(); - mSender = null; - } - mChannel.dispose(); - mChannel = null; - } - } + releaseInternal(); } /** @@ -669,6 +698,24 @@ public final class TvInputManager { mPendingEventPool.release(p); } + private void releaseInternal() { + mToken = null; + synchronized (mHandler) { + if (mChannel != null) { + if (mSender != null) { + flushPendingEventsLocked(); + mSender.dispose(); + mSender = null; + } + mChannel.dispose(); + mChannel = null; + } + } + synchronized (mSessionCallbackRecordMap) { + mSessionCallbackRecordMap.remove(mSeq); + } + } + private final class InputEventHandler extends Handler { public static final int MSG_SEND_INPUT_EVENT = 1; public static final int MSG_TIMEOUT_INPUT_EVENT = 2; diff --git a/core/java/android/tv/TvView.java b/core/java/android/tv/TvView.java index e84659cc820f..80501e872072 100644 --- a/core/java/android/tv/TvView.java +++ b/core/java/android/tv/TvView.java @@ -22,7 +22,7 @@ import android.os.Handler; import android.text.TextUtils; import android.tv.TvInputManager.Session; import android.tv.TvInputManager.Session.FinishedInputEventCallback; -import android.tv.TvInputManager.SessionCreateCallback; +import android.tv.TvInputManager.SessionCallback; import android.util.AttributeSet; import android.util.Log; import android.view.InputEvent; @@ -46,7 +46,7 @@ public class TvView extends SurfaceView { private boolean mOverlayViewCreated; private Rect mOverlayViewFrame; private final TvInputManager mTvInputManager; - private SessionCreateCallback mSessionCreateCallback; + private SessionCallback mSessionCallback; private OnUnhandledInputEventListener mOnUnhandledInputEventListener; private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { @@ -108,7 +108,7 @@ public class TvView extends SurfaceView { } /** - * Binds a TV input to this view. {@link SessionCreateCallback#onSessionCreated} will be + * Binds a TV input to this view. {@link SessionCallback#onSessionCreated} will be * called to send the result of this binding with {@link TvInputManager.Session}. * If a TV input is already bound, the input will be unbound from this view and its session * will be released. @@ -118,7 +118,7 @@ public class TvView extends SurfaceView { * {@link TvInputManager.Session} * @throws IllegalArgumentException if any of the arguments is {@code null}. */ - public void bindTvInput(String inputId, SessionCreateCallback callback) { + public void bindTvInput(String inputId, SessionCallback callback) { if (TextUtils.isEmpty(inputId)) { throw new IllegalArgumentException("inputId cannot be null or an empty string"); } @@ -130,11 +130,11 @@ public class TvView extends SurfaceView { } // When bindTvInput is called multiple times before the callback is called, // only the callback of the last bindTvInput call will be actually called back. - // The previous callbacks will be ignored. For the logic, mSessionCreateCallback + // The previous callbacks will be ignored. For the logic, mSessionCallback // is newly assigned for every bindTvInput call and compared with // MySessionCreateCallback.this. - mSessionCreateCallback = new MySessionCreateCallback(callback); - mTvInputManager.createSession(inputId, mSessionCreateCallback, mHandler); + mSessionCallback = new MySessionCallback(callback); + mTvInputManager.createSession(inputId, mSessionCallback, mHandler); } /** @@ -336,16 +336,16 @@ public class TvView extends SurfaceView { boolean onUnhandledInputEvent(InputEvent event); } - private class MySessionCreateCallback implements SessionCreateCallback { - final SessionCreateCallback mExternalCallback; + private class MySessionCallback extends SessionCallback { + final SessionCallback mExternalCallback; - MySessionCreateCallback(SessionCreateCallback externalCallback) { + MySessionCallback(SessionCallback externalCallback) { mExternalCallback = externalCallback; } @Override public void onSessionCreated(Session session) { - if (this != mSessionCreateCallback) { + if (this != mSessionCallback) { // This callback is obsolete. session.release(); return; @@ -364,5 +364,13 @@ public class TvView extends SurfaceView { mExternalCallback.onSessionCreated(session); } } + + @Override + public void onSessionReleased(Session session) { + mSession = null; + if (mExternalCallback != null) { + mExternalCallback.onSessionReleased(session); + } + } } } diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 5524bcaab97c..05f99475a495 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -225,7 +225,7 @@ public final class TvInputManagerService extends SystemService { return serviceState; } - private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) { + private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid, int userId) { UserState userState = getUserStateLocked(userId); SessionState sessionState = userState.sessionStateMap.get(sessionToken); if (sessionState == null) { @@ -236,6 +236,11 @@ public final class TvInputManagerService extends SystemService { throw new SecurityException("Illegal access to the session with token " + sessionToken + " from uid " + callingUid); } + return sessionState; + } + + private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) { + SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId); ITvInputSession session = sessionState.mSession; if (session == null) { throw new IllegalStateException("Session not yet created for token " + sessionToken); @@ -255,6 +260,13 @@ public final class TvInputManagerService extends SystemService { if (serviceState == null) { return; } + if (serviceState.mReconnecting) { + if (!serviceState.mSessionTokens.isEmpty()) { + // wait until all the sessions are removed. + return; + } + serviceState.mReconnecting = false; + } boolean isStateEmpty = serviceState.mClients.isEmpty() && serviceState.mSessionTokens.isEmpty(); if (serviceState.mService == null && !isStateEmpty && userId == mCurrentUserId) { @@ -307,9 +319,14 @@ public final class TvInputManagerService extends SystemService { sessionState.mSession = session; if (session == null) { removeSessionStateLocked(sessionToken, userId); - sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId, null, - null, sessionState.mSeq, userId); + sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId, + null, null, sessionState.mSeq, userId); } else { + try { + session.asBinder().linkToDeath(sessionState, 0); + } catch (RemoteException e) { + Slog.e(TAG, "Session is already died."); + } sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId, sessionToken, channels[0], sessionState.mSeq, userId); } @@ -337,11 +354,19 @@ public final class TvInputManagerService extends SystemService { } catch (RemoteException exception) { Slog.e(TAG, "error in onSessionCreated", exception); } + } - if (sessionToken == null) { - // This means that the session creation failed. We might want to disconnect the service. - updateServiceConnectionLocked(inputId, userId); + private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) { + SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId); + if (sessionState.mSession != null) { + try { + sessionState.mSession.release(); + } catch (RemoteException e) { + Slog.w(TAG, "session is already disapeared", e); + } + sessionState.mSession = null; } + removeSessionStateLocked(sessionToken, userId); } private void removeSessionStateLocked(IBinder sessionToken, int userId) { @@ -365,6 +390,18 @@ public final class TvInputManagerService extends SystemService { updateServiceConnectionLocked(sessionState.mInputId, userId); } + private void broadcastServiceAvailabilityChangedLocked(ServiceState serviceState) { + for (IBinder iBinder : serviceState.mClients) { + ITvInputClient client = ITvInputClient.Stub.asInterface(iBinder); + try { + client.onAvailabilityChanged( + serviceState.mTvInputInfo.getId(), serviceState.mAvailable); + } catch (RemoteException e) { + Slog.e(TAG, "error in onAvailabilityChanged", e); + } + } + } + private final class BinderService extends ITvInputManager.Stub { @Override public List<TvInputInfo> getTvInputList(int userId) { @@ -489,22 +526,28 @@ public final class TvInputManagerService extends SystemService { final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { - // Create a new session token and a session state. - IBinder sessionToken = new Binder(); - SessionState sessionState = new SessionState(inputId, client, seq, callingUid); - sessionState.mSession = null; - - // Add them to the global session state map of the current user. UserState userState = getUserStateLocked(resolvedUserId); - userState.sessionStateMap.put(sessionToken, sessionState); - - // Also, add them to the session state map of the current service. ServiceState serviceState = userState.serviceStateMap.get(inputId); if (serviceState == null) { serviceState = new ServiceState( userState.inputMap.get(inputId), resolvedUserId); userState.serviceStateMap.put(inputId, serviceState); } + // Send a null token immediately while reconnecting. + if (serviceState.mReconnecting == true) { + sendSessionTokenToClientLocked(client, inputId, null, null, seq, userId); + return; + } + + // Create a new session token and a session state. + IBinder sessionToken = new Binder(); + SessionState sessionState = new SessionState( + sessionToken, inputId, client, seq, callingUid, resolvedUserId); + + // Add them to the global session state map of the current user. + userState.sessionStateMap.put(sessionToken, sessionState); + + // Also, add them to the session state map of the current service. serviceState.mSessionTokens.add(sessionToken); if (serviceState.mService != null) { @@ -527,14 +570,7 @@ public final class TvInputManagerService extends SystemService { final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { - // Release the session. - try { - getSessionLocked(sessionToken, callingUid, resolvedUserId).release(); - } catch (RemoteException e) { - Slog.e(TAG, "error in release", e); - } - - removeSessionStateLocked(sessionToken, resolvedUserId); + releaseSessionLocked(sessionToken, callingUid, resolvedUserId); } } finally { Binder.restoreCallingIdentity(identity); @@ -710,34 +746,57 @@ public final class TvInputManagerService extends SystemService { } private final class ServiceState { + // TODO: need to implement DeathRecipient for clients. private final List<IBinder> mClients = new ArrayList<IBinder>(); private final List<IBinder> mSessionTokens = new ArrayList<IBinder>(); private final ServiceConnection mConnection; + private final TvInputInfo mTvInputInfo; private ITvInputService mService; private ServiceCallback mCallback; private boolean mBound; private boolean mAvailable; + private boolean mReconnecting; private ServiceState(TvInputInfo inputInfo, int userId) { - this.mConnection = new InputServiceConnection(inputInfo, userId); + mTvInputInfo = inputInfo; + mConnection = new InputServiceConnection(inputInfo, userId); } } - private static final class SessionState { + private final class SessionState implements IBinder.DeathRecipient { private final String mInputId; private final ITvInputClient mClient; private final int mSeq; private final int mCallingUid; - + private final int mUserId; + private final IBinder mToken; private ITvInputSession mSession; private Uri mLogUri; - private SessionState(String inputId, ITvInputClient client, int seq, int callingUid) { - this.mInputId = inputId; - this.mClient = client; - this.mSeq = seq; - this.mCallingUid = callingUid; + private SessionState(IBinder token, String inputId, ITvInputClient client, int seq, + int callingUid, int userId) { + mToken = token; + mInputId = inputId; + mClient = client; + mSeq = seq; + mCallingUid = callingUid; + mUserId = userId; + } + + @Override + public void binderDied() { + synchronized (mLock) { + mSession = null; + if (mClient != null) { + try { + mClient.onSessionReleased(mSeq); + } catch(RemoteException e) { + Slog.e(TAG, "error in onSessionReleased", e); + } + } + removeSessionStateLocked(mToken, mUserId); + } } } @@ -781,6 +840,37 @@ public final class TvInputManagerService extends SystemService { if (DEBUG) { Slog.d(TAG, "onServiceDisconnected(inputId=" + mTvInputInfo.getId() + ")"); } + if (!mTvInputInfo.getComponent().equals(name)) { + throw new IllegalArgumentException("Mismatched ComponentName: " + + mTvInputInfo.getComponent() + " (expected), " + name + " (actual)."); + } + synchronized (mLock) { + UserState userState = getUserStateLocked(mUserId); + ServiceState serviceState = userState.serviceStateMap.get(mTvInputInfo.getId()); + if (serviceState != null) { + serviceState.mReconnecting = true; + serviceState.mBound = false; + serviceState.mService = null; + serviceState.mCallback = null; + + // Send null tokens for not finishing create session events. + for (IBinder sessionToken : serviceState.mSessionTokens) { + SessionState sessionState = userState.sessionStateMap.get(sessionToken); + if (sessionState.mSession == null) { + removeSessionStateLocked(sessionToken, sessionState.mUserId); + sendSessionTokenToClientLocked(sessionState.mClient, + sessionState.mInputId, null, null, sessionState.mSeq, + sessionState.mUserId); + } + } + + if (serviceState.mAvailable) { + serviceState.mAvailable = false; + broadcastServiceAvailabilityChangedLocked(serviceState); + } + updateServiceConnectionLocked(mTvInputInfo.getId(), mUserId); + } + } } } @@ -792,18 +882,16 @@ public final class TvInputManagerService extends SystemService { } @Override - public void onAvailabilityChanged(String inputId, boolean isAvailable) - throws RemoteException { + public void onAvailabilityChanged(String inputId, boolean isAvailable) { if (DEBUG) { Slog.d(TAG, "onAvailabilityChanged(inputId=" + inputId + ", isAvailable=" + isAvailable + ")"); } synchronized (mLock) { ServiceState serviceState = getServiceStateLocked(inputId, mUserId); - serviceState.mAvailable = isAvailable; - for (IBinder iBinder : serviceState.mClients) { - ITvInputClient client = ITvInputClient.Stub.asInterface(iBinder); - client.onAvailabilityChanged(inputId, isAvailable); + if (serviceState.mAvailable != isAvailable) { + serviceState.mAvailable = isAvailable; + broadcastServiceAvailabilityChangedLocked(serviceState); } } } |