OSDN Git Service

TIF: Revisit availability changes
authorWonsik Kim <wonsik@google.com>
Tue, 24 Jun 2014 07:33:17 +0000 (16:33 +0900)
committerWonsik Kim <wonsik@google.com>
Tue, 15 Jul 2014 02:58:33 +0000 (02:58 +0000)
Bug: 15838097, Bug: 15973274
Change-Id: Ida060696cb6222c8ced576d86c100c25d94dc5c0

Android.mk
api/current.txt
media/java/android/media/tv/ITvInputClient.aidl
media/java/android/media/tv/ITvInputManager.aidl
media/java/android/media/tv/ITvInputManagerCallback.aidl [new file with mode: 0644]
media/java/android/media/tv/ITvInputServiceCallback.aidl
media/java/android/media/tv/TvInputInfo.java
media/java/android/media/tv/TvInputManager.java
media/java/android/media/tv/TvInputService.java
services/core/java/com/android/server/tv/TvInputHardwareManager.java
services/core/java/com/android/server/tv/TvInputManagerService.java

index a823ba0..8a50ae8 100644 (file)
@@ -336,6 +336,7 @@ LOCAL_SRC_FILES += \
        media/java/android/media/tv/ITvInputHardware.aidl \
        media/java/android/media/tv/ITvInputHardwareCallback.aidl \
        media/java/android/media/tv/ITvInputManager.aidl \
+       media/java/android/media/tv/ITvInputManagerCallback.aidl \
        media/java/android/media/tv/ITvInputService.aidl \
        media/java/android/media/tv/ITvInputServiceCallback.aidl \
        media/java/android/media/tv/ITvInputSession.aidl \
index fcc02d0..1f980b3 100644 (file)
@@ -16425,8 +16425,13 @@ package android.media.tv {
   }
 
   public final class TvInputManager {
-    method public boolean getAvailability(java.lang.String);
+    method public int getInputState(java.lang.String);
     method public java.util.List<android.media.tv.TvInputInfo> getTvInputList();
+    method public void registerListener(android.media.tv.TvInputManager.TvInputListener, android.os.Handler);
+    method public void unregisterListener(android.media.tv.TvInputManager.TvInputListener);
+    field public static final int INPUT_STATE_CONNECTED = 0; // 0x0
+    field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1
+    field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2
     field public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = 3; // 0x3
     field public static final int VIDEO_UNAVAILABLE_REASON_TUNE = 1; // 0x1
     field public static final int VIDEO_UNAVAILABLE_REASON_UNKNOWN = 0; // 0x0
@@ -16435,7 +16440,7 @@ package android.media.tv {
 
   public static abstract class TvInputManager.TvInputListener {
     ctor public TvInputManager.TvInputListener();
-    method public void onAvailabilityChanged(java.lang.String, boolean);
+    method public void onInputStateChanged(java.lang.String, int);
   }
 
   public abstract class TvInputService extends android.app.Service {
index cac8a14..423e317 100644 (file)
@@ -30,7 +30,6 @@ 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);
     void onSessionEvent(in String name, in Bundle args, int seq);
     void onChannelRetuned(in Uri channelUri, int seq);
index 9a6a648..6a0c592 100644 (file)
@@ -18,9 +18,10 @@ package android.media.tv;
 
 import android.content.ComponentName;
 import android.graphics.Rect;
+import android.media.tv.ITvInputClient;
 import android.media.tv.ITvInputHardware;
 import android.media.tv.ITvInputHardwareCallback;
-import android.media.tv.ITvInputClient;
+import android.media.tv.ITvInputManagerCallback;
 import android.media.tv.TvInputHardwareInfo;
 import android.media.tv.TvInputInfo;
 import android.media.tv.TvTrackInfo;
@@ -34,10 +35,8 @@ import android.view.Surface;
 interface ITvInputManager {
     List<TvInputInfo> getTvInputList(int userId);
 
-    boolean getAvailability(in ITvInputClient client, in String inputId, int userId);
-
-    void registerCallback(in ITvInputClient client, in String inputId, int userId);
-    void unregisterCallback(in ITvInputClient client, in String inputId, int userId);
+    void registerCallback(in ITvInputManagerCallback callback, int userId);
+    void unregisterCallback(in ITvInputManagerCallback callback, int userId);
 
     void createSession(in ITvInputClient client, in String inputId, int seq, int userId);
     void releaseSession(in IBinder sessionToken, int userId);
@@ -56,7 +55,12 @@ interface ITvInputManager {
 
     // For TV input hardware binding
     List<TvInputHardwareInfo> getHardwareList();
+    /*
+     * All TvInputServices which want to use hardware must call this method on
+     * BOOT_COMPLETE.
+     */
+    void registerTvInputInfo(in TvInputInfo info, int deviceId);
     ITvInputHardware acquireTvInputHardware(int deviceId, in ITvInputHardwareCallback callback,
-            int userId);
+            in TvInputInfo info, int userId);
     void releaseTvInputHardware(int deviceId, in ITvInputHardware hardware, int userId);
 }
diff --git a/media/java/android/media/tv/ITvInputManagerCallback.aidl b/media/java/android/media/tv/ITvInputManagerCallback.aidl
new file mode 100644 (file)
index 0000000..5c8a0a3
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+/**
+ * Interface to receive callbacks from ITvInputManager regardless of sessions.
+ * @hide
+ */
+oneway interface ITvInputManagerCallback {
+    void onInputStateChanged(in String inputId, int state);
+}
index c9484dd..1fdb8c5 100644 (file)
@@ -24,5 +24,5 @@ import android.content.ComponentName;
  * @hide
  */
 oneway interface ITvInputServiceCallback {
-    void onAvailabilityChanged(in String inputId, boolean isAvailable);
+    void onInputStateChanged(int state);
 }
index 7b8f2ec..5624f3e 100644 (file)
@@ -80,7 +80,7 @@ public final class TvInputInfo implements Parcelable {
     // Attributes from XML meta data.
     private String mSetupActivity;
     private String mSettingsActivity;
-    private int mType;
+    private int mType = TYPE_VIRTUAL;
 
     /**
      * Create a new instance of the TvInputInfo class,
@@ -128,10 +128,13 @@ public final class TvInputInfo implements Parcelable {
                 Log.d(TAG, "Settings activity loaded. [" + input.mSettingsActivity + "] for "
                         + si.name);
             }
-            input.mType = sa.getInt(
-                    com.android.internal.R.styleable.TvInputService_tvInputType, TYPE_VIRTUAL);
-            if (DEBUG) {
-                Log.d(TAG, "Type loaded. [" + input.mType + "] for " + si.name);
+            if (pm.checkPermission(android.Manifest.permission.TV_INPUT_HARDWARE, si.packageName)
+                    == PackageManager.PERMISSION_GRANTED) {
+                input.mType = sa.getInt(
+                        com.android.internal.R.styleable.TvInputService_tvInputType, TYPE_VIRTUAL);
+                if (DEBUG) {
+                    Log.d(TAG, "Type loaded. [" + input.mType + "] for " + si.name);
+                }
             }
             sa.recycle();
 
index 867b0db..79a83b0 100644 (file)
@@ -24,6 +24,7 @@ import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pools.Pool;
 import android.util.Pools.SimplePool;
@@ -37,6 +38,7 @@ import android.view.View;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
@@ -65,11 +67,43 @@ public final class TvInputManager {
      */
     public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = 3;
 
+    /**
+     * The TV input is connected.
+     * <p>
+     * State for {@link #getInputState} and {@link
+     * TvInputManager.TvInputListener#onInputStateChanged}.
+     * </p>
+     */
+    public static final int INPUT_STATE_CONNECTED = 0;
+    /**
+     * The TV input is connected but in standby mode. It would take a while until it becomes
+     * fully ready.
+     * <p>
+     * State for {@link #getInputState} and {@link
+     * TvInputManager.TvInputListener#onInputStateChanged}.
+     * </p>
+     */
+    public static final int INPUT_STATE_CONNECTED_STANDBY = 1;
+    /**
+     * The TV input is disconnected.
+     * <p>
+     * State for {@link #getInputState} and {@link
+     * TvInputManager.TvInputListener#onInputStateChanged}.
+     * </p>
+     */
+    public static final int INPUT_STATE_DISCONNECTED = 2;
+
     private final ITvInputManager mService;
 
-    // A mapping from an input to the list of its TvInputListenerRecords.
-    private final Map<String, List<TvInputListenerRecord>> mTvInputListenerRecordsMap =
-            new HashMap<String, List<TvInputListenerRecord>>();
+    private final Object mLock = new Object();
+
+    // @GuardedBy(mLock)
+    private final List<TvInputListenerRecord> mTvInputListenerRecordsList =
+            new LinkedList<TvInputListenerRecord>();
+
+    // A mapping from TV input ID to the state of corresponding input.
+    // @GuardedBy(mLock)
+    private final Map<String, Integer> mStateMap = new ArrayMap<String, Integer>();
 
     // A mapping from the sequence number of a session to its SessionCallbackRecord.
     private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap =
@@ -81,6 +115,8 @@ public final class TvInputManager {
 
     private final ITvInputClient mClient;
 
+    private final ITvInputManagerCallback mCallback;
+
     private final int mUserId;
 
     /**
@@ -242,13 +278,17 @@ public final class TvInputManager {
      */
     public abstract static class TvInputListener {
         /**
-         * This is called when the availability status of a given TV input is changed.
+         * This is called when the state of a given TV input is changed.
          *
          * @param inputId the id of the TV input.
-         * @param isAvailable {@code true} if the given TV input is available to show TV programs.
-         *            {@code false} otherwise.
+         * @param state state of the TV input. The value is one of the following:
+         * <ul>
+         * <li>{@link TvInputManager#INPUT_STATE_CONNECTED}
+         * <li>{@link TvInputManager#INPUT_STATE_CONNECTED_STANDBY}
+         * <li>{@link TvInputManager#INPUT_STATE_DISCONNECTED}
+         * </ul>
          */
-        public void onAvailabilityChanged(String inputId, boolean isAvailable) {
+        public void onInputStateChanged(String inputId, int state) {
         }
     }
 
@@ -265,11 +305,11 @@ public final class TvInputManager {
             return mListener;
         }
 
-        public void postAvailabilityChanged(final String inputId, final boolean isAvailable) {
+        public void postStateChanged(final String inputId, final int state) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    mListener.onAvailabilityChanged(inputId, isAvailable);
+                    mListener.onInputStateChanged(inputId, state);
                 }
             });
         }
@@ -373,22 +413,23 @@ public final class TvInputManager {
                     record.postSessionEvent(eventType, eventArgs);
                 }
             }
-
+        };
+        mCallback = new ITvInputManagerCallback.Stub() {
             @Override
-            public void onAvailabilityChanged(String inputId, boolean isAvailable) {
-                synchronized (mTvInputListenerRecordsMap) {
-                    List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
-                    if (records == null) {
-                        // Silently ignore - no listener is registered yet.
-                        return;
-                    }
-                    int recordsCount = records.size();
-                    for (int i = 0; i < recordsCount; i++) {
-                        records.get(i).postAvailabilityChanged(inputId, isAvailable);
+            public void onInputStateChanged(String inputId, int state) {
+                synchronized (mLock) {
+                    mStateMap.put(inputId, state);
+                    for (TvInputListenerRecord record : mTvInputListenerRecordsList) {
+                        record.postStateChanged(inputId, state);
                     }
                 }
             }
         };
+        try {
+            mService.registerCallback(mCallback, mUserId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "mService.registerCallback failed: " + e);
+        }
     }
 
     /**
@@ -405,98 +446,66 @@ public final class TvInputManager {
     }
 
     /**
-     * Returns the availability of a given TV input.
+     * Returns the state of a given TV input. It retuns one of the following:
+     * <ul>
+     * <li>{@link #INPUT_STATE_CONNECTED}
+     * <li>{@link #INPUT_STATE_CONNECTED_STANDBY}
+     * <li>{@link #INPUT_STATE_DISCONNECTED}
+     * </ul>
      *
      * @param inputId the id of the TV input.
-     * @throws IllegalArgumentException if the argument is {@code null}.
-     * @throws IllegalStateException If there is no {@link TvInputListener} registered on the given
-     *             TV input.
+     * @throws IllegalArgumentException if the argument is {@code null} or if there is no
+     *        {@link TvInputInfo} corresponding to {@code inputId}.
      */
-    public boolean getAvailability(String inputId) {
+    public int getInputState(String inputId) {
         if (inputId == null) {
             throw new IllegalArgumentException("id cannot be null");
         }
-        synchronized (mTvInputListenerRecordsMap) {
-            List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
-            if (records == null || records.size() == 0) {
-                throw new IllegalStateException("At least one listener should be registered.");
+        synchronized (mLock) {
+            Integer state = mStateMap.get(inputId);
+            if (state == null) {
+                throw new IllegalArgumentException("Unrecognized input ID: " + inputId);
             }
-        }
-        try {
-            return mService.getAvailability(mClient, inputId, mUserId);
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
+            return state.intValue();
         }
     }
 
     /**
-     * Registers a {@link TvInputListener} for a given TV input.
+     * Registers a {@link TvInputListener}.
      *
-     * @param inputId the id of the TV input.
-     * @param listener a listener used to monitor status of the given TV input.
+     * @param listener a listener used to monitor status of the TV inputs.
      * @param handler a {@link Handler} that the status change will be delivered to.
      * @throws IllegalArgumentException if any of the arguments is {@code null}.
-     * @hide
      */
-    public void registerListener(String inputId, TvInputListener listener, Handler handler) {
-        if (inputId == null) {
-            throw new IllegalArgumentException("id cannot be null");
-        }
+    public void registerListener(TvInputListener listener, Handler handler) {
         if (listener == null) {
             throw new IllegalArgumentException("listener cannot be null");
         }
         if (handler == null) {
             throw new IllegalArgumentException("handler cannot be null");
         }
-        synchronized (mTvInputListenerRecordsMap) {
-            List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
-            if (records == null) {
-                records = new ArrayList<TvInputListenerRecord>();
-                mTvInputListenerRecordsMap.put(inputId, records);
-                try {
-                    mService.registerCallback(mClient, inputId, mUserId);
-                } catch (RemoteException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-            records.add(new TvInputListenerRecord(listener, handler));
+        synchronized (mLock) {
+            mTvInputListenerRecordsList.add(new TvInputListenerRecord(listener, handler));
         }
     }
 
     /**
-     * Unregisters the existing {@link TvInputListener} for a given TV input.
+     * Unregisters the existing {@link TvInputListener}.
      *
-     * @param inputId the id of the TV input.
-     * @param listener the existing listener to remove for the given TV input.
+     * @param listener the existing listener to remove.
      * @throws IllegalArgumentException if any of the arguments is {@code null}.
-     * @hide
      */
-    public void unregisterListener(String inputId, final TvInputListener listener) {
-        if (inputId == null) {
-            throw new IllegalArgumentException("id cannot be null");
-        }
+    public void unregisterListener(final TvInputListener listener) {
         if (listener == null) {
             throw new IllegalArgumentException("listener cannot be null");
         }
-        synchronized (mTvInputListenerRecordsMap) {
-            List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
-            if (records == null) {
-                Log.e(TAG, "No listener found for " + inputId);
-                return;
-            }
-            for (Iterator<TvInputListenerRecord> it = records.iterator(); it.hasNext();) {
+        synchronized (mLock) {
+            for (Iterator<TvInputListenerRecord> it = mTvInputListenerRecordsList.iterator();
+                    it.hasNext(); ) {
                 TvInputListenerRecord record = it.next();
                 if (record.getListener() == listener) {
                     it.remove();
-                }
-            }
-            if (records.isEmpty()) {
-                try {
-                    mService.unregisterCallback(mClient, inputId, mUserId);
-                } catch (RemoteException e) {
-                    throw new RuntimeException(e);
-                } finally {
-                    mTvInputListenerRecordsMap.remove(inputId);
+                    break;
                 }
             }
         }
index a994f54..3206320 100644 (file)
@@ -82,20 +82,9 @@ public abstract class TvInputService extends Service {
      */
     public static final String SERVICE_META_DATA = "android.media.tv.input";
 
-    private String mId;
     private final Handler mHandler = new ServiceHandler();
     private final RemoteCallbackList<ITvInputServiceCallback> mCallbacks =
             new RemoteCallbackList<ITvInputServiceCallback>();
-    // STOPSHIP: Redesign the API around the availability change. For now, the service will be
-    // always available.
-    private final boolean mAvailable = true;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mId = TvInputInfo.generateInputIdForComponentName(
-                new ComponentName(getPackageName(), getClass().getName()));
-    }
 
     @Override
     public final IBinder onBind(Intent intent) {
@@ -104,13 +93,6 @@ public abstract class TvInputService extends Service {
             public void registerCallback(ITvInputServiceCallback cb) {
                 if (cb != null) {
                     mCallbacks.register(cb);
-                    // The first time a callback is registered, the service needs to report its
-                    // availability status so that the system can know its initial value.
-                    try {
-                        cb.onAvailabilityChanged(mId, mAvailable);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "error in onAvailabilityChanged", e);
-                    }
                 }
             }
 
@@ -733,7 +715,6 @@ public abstract class TvInputService extends Service {
     @SuppressLint("HandlerLeak")
     private final class ServiceHandler extends Handler {
         private static final int DO_CREATE_SESSION = 1;
-        private static final int DO_BROADCAST_AVAILABILITY_CHANGE = 2;
 
         @Override
         public final void handleMessage(Message msg) {
@@ -759,20 +740,6 @@ public abstract class TvInputService extends Service {
                     args.recycle();
                     return;
                 }
-                case DO_BROADCAST_AVAILABILITY_CHANGE: {
-                    boolean isAvailable = (Boolean) msg.obj;
-                    int n = mCallbacks.beginBroadcast();
-                    try {
-                        for (int i = 0; i < n; i++) {
-                            mCallbacks.getBroadcastItem(i).onAvailabilityChanged(mId, isAvailable);
-                        }
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Unexpected exception", e);
-                    } finally {
-                        mCallbacks.finishBroadcast();
-                    }
-                    return;
-                }
                 default: {
                     Log.w(TAG, "Unhandled message code: " + msg.what);
                     return;
index efe543b..8f237db 100644 (file)
 
 package com.android.server.tv;
 
+import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
+import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
+
 import android.content.Context;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiHotplugEvent;
 import android.media.AudioDevicePort;
 import android.media.AudioManager;
 import android.media.AudioPatch;
@@ -25,14 +30,22 @@ import android.media.AudioPortConfig;
 import android.media.tv.ITvInputHardware;
 import android.media.tv.ITvInputHardwareCallback;
 import android.media.tv.TvInputHardwareInfo;
+import android.media.tv.TvInputInfo;
 import android.media.tv.TvStreamConfig;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.view.KeyEvent;
 import android.view.Surface;
 
+import com.android.server.SystemService;
+
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -46,23 +59,42 @@ import java.util.Set;
  *
  * @hide
  */
-class TvInputHardwareManager implements TvInputHal.Callback {
+class TvInputHardwareManager
+        implements TvInputHal.Callback, HdmiControlManager.HotplugEventListener {
     private static final String TAG = TvInputHardwareManager.class.getSimpleName();
     private final TvInputHal mHal = new TvInputHal(this);
     private final SparseArray<Connection> mConnections = new SparseArray<Connection>();
     private final List<TvInputHardwareInfo> mInfoList = new ArrayList<TvInputHardwareInfo>();
     private final Context mContext;
+    private final TvInputManagerService.Client mClient;
     private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>();
     private final AudioManager mAudioManager;
+    private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray();
+    // TODO: Should handle INACTIVE case.
+    private final SparseArray<TvInputInfo> mTvInputInfoMap = new SparseArray<TvInputInfo>();
+
+    // Calls to mClient should happen here.
+    private final HandlerThread mHandlerThread = new HandlerThread(TAG);
+    private final Handler mHandler;
 
     private final Object mLock = new Object();
 
-    public TvInputHardwareManager(Context context) {
+    public TvInputHardwareManager(Context context, TvInputManagerService.Client client) {
         mContext = context;
+        mClient = client;
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-        // TODO(hdmi): mHdmiManager = mContext.getSystemService(...);
-        // TODO(hdmi): mHdmiClient = mHdmiManager.getTvClient();
         mHal.init();
+
+        mHandlerThread.start();
+        mHandler = new ClientHandler(mHandlerThread.getLooper());
+    }
+
+    public void onBootPhase(int phase) {
+        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+            HdmiControlManager hdmiControlManager =
+                    (HdmiControlManager) mContext.getSystemService(Context.HDMI_CONTROL_SERVICE);
+            hdmiControlManager.addHotplugEventListener(this);
+        }
     }
 
     @Override
@@ -80,7 +112,7 @@ class TvInputHardwareManager implements TvInputHal.Callback {
     private void buildInfoListLocked() {
         mInfoList.clear();
         for (int i = 0; i < mConnections.size(); ++i) {
-            mInfoList.add(mConnections.valueAt(i).getInfoLocked());
+            mInfoList.add(mConnections.valueAt(i).getHardwareInfoLocked());
         }
     }
 
@@ -92,7 +124,7 @@ class TvInputHardwareManager implements TvInputHal.Callback {
                 Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId);
                 return;
             }
-            connection.resetLocked(null, null, null, null);
+            connection.resetLocked(null, null, null, null, null);
             mConnections.remove(deviceId);
             buildInfoListLocked();
             // TODO: notify if necessary
@@ -136,6 +168,37 @@ class TvInputHardwareManager implements TvInputHal.Callback {
         return false;
     }
 
+    private int convertConnectedToState(boolean connected) {
+        if (connected) {
+            return INPUT_STATE_CONNECTED;
+        } else {
+            return INPUT_STATE_DISCONNECTED;
+        }
+    }
+
+    public void registerTvInputInfo(TvInputInfo info, int deviceId) {
+        if (info.getType() == TvInputInfo.TYPE_VIRTUAL) {
+            throw new IllegalArgumentException("info (" + info + ") has virtual type.");
+        }
+        synchronized (mLock) {
+            if (mTvInputInfoMap.indexOfKey(deviceId) >= 0) {
+                Slog.w(TAG, "Trying to override previous registration: old = "
+                        + mTvInputInfoMap.get(deviceId) + ":" + deviceId + ", new = "
+                        + info + ":" + deviceId);
+            }
+            mTvInputInfoMap.put(deviceId, info);
+
+            for (int i = 0; i < mHdmiStateMap.size(); ++i) {
+                String inputId = findInputIdForHdmiPortLocked(mHdmiStateMap.keyAt(i));
+                if (inputId != null && inputId.equals(info.getId())) {
+                    mHandler.obtainMessage(ClientHandler.DO_SET_AVAILABLE,
+                            convertConnectedToState(mHdmiStateMap.valueAt(i)), 0,
+                            inputId).sendToTarget();
+                }
+            }
+        }
+    }
+
     /**
      * Create a TvInputHardware object with a specific deviceId. One service at a time can access
      * the object, and if more than one process attempts to create hardware with the same deviceId,
@@ -143,7 +206,7 @@ class TvInputHardwareManager implements TvInputHal.Callback {
      * release is notified via ITvInputHardwareCallback.onReleased().
      */
     public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback,
-            int callingUid, int resolvedUserId) {
+            TvInputInfo info, int callingUid, int resolvedUserId) {
         if (callback == null) {
             throw new NullPointerException();
         }
@@ -154,14 +217,15 @@ class TvInputHardwareManager implements TvInputHal.Callback {
                 return null;
             }
             if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
-                TvInputHardwareImpl hardware = new TvInputHardwareImpl(connection.getInfoLocked());
+                TvInputHardwareImpl hardware =
+                        new TvInputHardwareImpl(connection.getHardwareInfoLocked());
                 try {
                     callback.asBinder().linkToDeath(connection, 0);
                 } catch (RemoteException e) {
                     hardware.release();
                     return null;
                 }
-                connection.resetLocked(hardware, callback, callingUid, resolvedUserId);
+                connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId);
             }
             return connection.getHardwareLocked();
         }
@@ -182,26 +246,55 @@ class TvInputHardwareManager implements TvInputHal.Callback {
                     || checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
                 return;
             }
-            connection.resetLocked(null, null, null, null);
+            connection.resetLocked(null, null, null, null, null);
+        }
+    }
+
+    private String findInputIdForHdmiPortLocked(int port) {
+        for (TvInputHardwareInfo hardwareInfo : mInfoList) {
+            if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI
+                    && hardwareInfo.getHdmiPortId() == port) {
+                TvInputInfo info = mTvInputInfoMap.get(hardwareInfo.getDeviceId());
+                return (info == null) ? null : info.getId();
+            }
+        }
+        return null;
+    }
+
+    // HdmiControlManager.HotplugEventListener implementation.
+
+    @Override
+    public void onReceived(HdmiHotplugEvent event) {
+        String inputId = null;
+
+        synchronized (mLock) {
+            mHdmiStateMap.put(event.getPort(), event.isConnected());
+            inputId = findInputIdForHdmiPortLocked(event.getPort());
+            if (inputId == null) {
+                return;
+            }
+            mHandler.obtainMessage(ClientHandler.DO_SET_AVAILABLE,
+                    convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget();
         }
     }
 
     private class Connection implements IBinder.DeathRecipient {
-        private final TvInputHardwareInfo mInfo;
+        private final TvInputHardwareInfo mHardwareInfo;
+        private TvInputInfo mInfo;
         private TvInputHardwareImpl mHardware = null;
         private ITvInputHardwareCallback mCallback;
         private TvStreamConfig[] mConfigs = null;
         private Integer mCallingUid = null;
         private Integer mResolvedUserId = null;
 
-        public Connection(TvInputHardwareInfo info) {
-            mInfo = info;
+        public Connection(TvInputHardwareInfo hardwareInfo) {
+            mHardwareInfo = hardwareInfo;
         }
 
         // *Locked methods assume TvInputHardwareManager.mLock is held.
 
-        public void resetLocked(TvInputHardwareImpl hardware,
-                ITvInputHardwareCallback callback, Integer callingUid, Integer resolvedUserId) {
+        public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback,
+                TvInputInfo info, Integer callingUid, Integer resolvedUserId) {
             if (mHardware != null) {
                 try {
                     mCallback.onReleased();
@@ -212,6 +305,7 @@ class TvInputHardwareManager implements TvInputHal.Callback {
             }
             mHardware = hardware;
             mCallback = callback;
+            mInfo = info;
             mCallingUid = callingUid;
             mResolvedUserId = resolvedUserId;
 
@@ -228,7 +322,11 @@ class TvInputHardwareManager implements TvInputHal.Callback {
             mConfigs = configs;
         }
 
-        public TvInputHardwareInfo getInfoLocked() {
+        public TvInputHardwareInfo getHardwareInfoLocked() {
+            return mHardwareInfo;
+        }
+
+        public TvInputInfo getInfoLocked() {
             return mInfo;
         }
 
@@ -255,7 +353,7 @@ class TvInputHardwareManager implements TvInputHal.Callback {
         @Override
         public void binderDied() {
             synchronized (mLock) {
-                resetLocked(null, null, null, null);
+                resetLocked(null, null, null, null, null);
             }
         }
     }
@@ -403,4 +501,28 @@ class TvInputHardwareManager implements TvInputHal.Callback {
             return false;
         }
     }
+
+    private class ClientHandler extends Handler {
+        private static final int DO_SET_AVAILABLE = 1;
+
+        ClientHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public final void handleMessage(Message msg) {
+            switch (msg.what) {
+                case DO_SET_AVAILABLE: {
+                    String inputId = (String) msg.obj;
+                    int state = msg.arg1;
+                    mClient.setState(inputId, state);
+                    break;
+                }
+                default: {
+                    Slog.w(TAG, "Unhandled message: " + msg);
+                    break;
+                }
+            }
+        }
+    }
 }
index 5e95af4..20fdefa 100644 (file)
@@ -16,6 +16,9 @@
 
 package com.android.server.tv;
 
+import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
+import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
+
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -38,6 +41,7 @@ import android.media.tv.ITvInputClient;
 import android.media.tv.ITvInputHardware;
 import android.media.tv.ITvInputHardwareCallback;
 import android.media.tv.ITvInputManager;
+import android.media.tv.ITvInputManagerCallback;
 import android.media.tv.ITvInputService;
 import android.media.tv.ITvInputServiceCallback;
 import android.media.tv.ITvInputSession;
@@ -109,7 +113,7 @@ public final class TvInputManagerService extends SystemService {
         mContentResolver = context.getContentResolver();
         mLogHandler = new LogHandler(IoThread.get().getLooper());
 
-        mTvInputHardwareManager = new TvInputHardwareManager(context);
+        mTvInputHardwareManager = new TvInputHardwareManager(context, new Client());
 
         synchronized (mLock) {
             mUserStates.put(mCurrentUserId, new UserState());
@@ -129,6 +133,7 @@ public final class TvInputManagerService extends SystemService {
                 buildTvInputListLocked(mCurrentUserId);
             }
         }
+        mTvInputHardwareManager.onBootPhase(phase);
     }
 
     private void registerBroadcastReceivers() {
@@ -144,7 +149,7 @@ public final class TvInputManagerService extends SystemService {
             public void onPackageRemoved(String packageName, int uid) {
                 synchronized (mLock) {
                     UserState userState = getUserStateLocked(mCurrentUserId);
-                    if (!userState.packageList.contains(packageName)) {
+                    if (!userState.packageSet.contains(packageName)) {
                         // Not a TV input package.
                         return;
                     }
@@ -198,8 +203,11 @@ public final class TvInputManagerService extends SystemService {
 
     private void buildTvInputListLocked(int userId) {
         UserState userState = getUserStateLocked(userId);
-        userState.inputMap.clear();
-        userState.packageList.clear();
+
+        Map<String, TvInputState> oldInputMap = userState.inputMap;
+        userState.inputMap = new HashMap<String, TvInputState>();
+
+        userState.packageSet.clear();
 
         if (DEBUG) Slog.d(TAG, "buildTvInputList");
         PackageManager pm = mContext.getPackageManager();
@@ -216,8 +224,13 @@ public final class TvInputManagerService extends SystemService {
             try {
                 TvInputInfo info = TvInputInfo.createTvInputInfo(mContext, ri);
                 if (DEBUG) Slog.d(TAG, "add " + info.getId());
-                userState.inputMap.put(info.getId(), info);
-                userState.packageList.add(si.packageName);
+                TvInputState state = oldInputMap.get(info.getId());
+                if (state == null) {
+                    state = new TvInputState();
+                }
+                userState.inputMap.put(info.getId(), state);
+                state.mInfo = info;
+                userState.packageSet.add(si.packageName);
 
                 // Reconnect the service if existing input is updated.
                 updateServiceConnectionLocked(info.getId(), userId);
@@ -225,6 +238,7 @@ public final class TvInputManagerService extends SystemService {
                 Slog.e(TAG, "Can't load TV input " + si.name, e);
             }
         }
+        oldInputMap.clear();
     }
 
     private void switchUser(int userId) {
@@ -359,7 +373,7 @@ public final class TvInputManagerService extends SystemService {
             }
 
             Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(
-                    userState.inputMap.get(inputId).getComponent());
+                    userState.inputMap.get(inputId).mInfo.getComponent());
             // Binding service may fail if the service is updating.
             // In that case, the connection will be revived in buildTvInputListLocked called by
             // onSomePackagesChanged.
@@ -588,7 +602,7 @@ public final class TvInputManagerService extends SystemService {
         updateServiceConnectionLocked(sessionState.mInputId, userId);
     }
 
-    private void unregisterCallbackInternalLocked(IBinder clientToken, String inputId,
+    private void unregisterClientInternalLocked(IBinder clientToken, String inputId,
             int userId) {
         UserState userState = getUserStateLocked(userId);
         ClientState clientState = userState.clientStateMap.get(clientToken);
@@ -623,17 +637,46 @@ public final class TvInputManagerService extends SystemService {
         }
     }
 
-    private void broadcastServiceAvailabilityChangedLocked(ServiceState serviceState) {
-        for (IBinder clientToken : serviceState.mClientTokens) {
+    private void notifyStateChangedLocked(UserState userState, String inputId,
+            int state, ITvInputManagerCallback targetCallback) {
+        if (DEBUG) {
+            Slog.d(TAG, "notifyStateChangedLocked: inputId = " + inputId
+                    + "; state = " + state);
+        }
+        if (targetCallback == null) {
+            for (ITvInputManagerCallback callback : userState.callbackSet) {
+                try {
+                    callback.onInputStateChanged(inputId, state);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Failed to report state change to callback.");
+                }
+            }
+        } else {
             try {
-                ITvInputClient.Stub.asInterface(clientToken).onAvailabilityChanged(
-                        serviceState.mTvInputInfo.getId(), serviceState.mAvailable);
+                targetCallback.onInputStateChanged(inputId, state);
             } catch (RemoteException e) {
-                Slog.e(TAG, "error in onAvailabilityChanged", e);
+                Slog.e(TAG, "Failed to report state change to callback.");
             }
         }
     }
 
+    private void setStateLocked(String inputId, int state, int userId) {
+        UserState userState = getUserStateLocked(userId);
+        TvInputState inputState = userState.inputMap.get(inputId);
+        ServiceState serviceState = userState.serviceStateMap.get(inputId);
+        int oldState = inputState.mState;
+        inputState.mState = state;
+        boolean isStateEmpty = serviceState.mClientTokens.isEmpty()
+                && serviceState.mSessionTokens.isEmpty();
+        if (serviceState != null && serviceState.mService == null && !isStateEmpty) {
+            // We don't notify state change while reconnecting. It should remain disconnected.
+            return;
+        }
+        if (oldState != state) {
+            notifyStateChangedLocked(userState, inputId, state, null);
+        }
+    }
+
     private final class BinderService extends ITvInputManager.Stub {
         @Override
         public List<TvInputInfo> getTvInputList(int userId) {
@@ -643,80 +686,29 @@ public final class TvInputManagerService extends SystemService {
             try {
                 synchronized (mLock) {
                     UserState userState = getUserStateLocked(resolvedUserId);
-                    return new ArrayList<TvInputInfo>(userState.inputMap.values());
-                }
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-
-        @Override
-        public boolean getAvailability(final ITvInputClient client, final String inputId,
-                int userId) {
-            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
-                    Binder.getCallingUid(), userId, "getAvailability");
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                synchronized (mLock) {
-                    UserState userState = getUserStateLocked(resolvedUserId);
-                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
-                    if (serviceState != null) {
-                        // We already know the status of this input service. Return the cached
-                        // status.
-                        return serviceState.mAvailable;
+                    List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
+                    for (TvInputState state : userState.inputMap.values()) {
+                        inputList.add(state.mInfo);
                     }
+                    return inputList;
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
-            // STOPSHIP: Redesign the API around the availability change. For now, the service
-            // will be always available.
-            return true;
         }
 
         @Override
-        public void registerCallback(final ITvInputClient client, final String inputId,
-                int userId) {
+        public void registerCallback(final ITvInputManagerCallback callback, int userId) {
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
                     Binder.getCallingUid(), userId, "registerCallback");
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
-                    // Create a new service callback and add it to the callback map of the current
-                    // service.
                     UserState userState = getUserStateLocked(resolvedUserId);
-                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
-                    if (serviceState == null) {
-                        serviceState = new ServiceState(
-                                userState.inputMap.get(inputId), resolvedUserId);
-                        userState.serviceStateMap.put(inputId, serviceState);
-                    }
-                    IBinder clientToken = client.asBinder();
-                    if (!serviceState.mClientTokens.contains(clientToken)) {
-                        serviceState.mClientTokens.add(clientToken);
-                    }
-
-                    ClientState clientState = userState.clientStateMap.get(clientToken);
-                    if (clientState == null) {
-                        clientState = createClientStateLocked(clientToken, resolvedUserId);
-                    }
-                    if (!clientState.mInputIds.contains(inputId)) {
-                        clientState.mInputIds.add(inputId);
-                    }
-
-                    if (serviceState.mService != null) {
-                        if (serviceState.mCallback != null) {
-                            // We already handled.
-                            return;
-                        }
-                        serviceState.mCallback = new ServiceCallback(resolvedUserId);
-                        try {
-                            serviceState.mService.registerCallback(serviceState.mCallback);
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "error in registerCallback", e);
-                        }
-                    } else {
-                        updateServiceConnectionLocked(inputId, resolvedUserId);
+                    userState.callbackSet.add(callback);
+                    for (TvInputState state : userState.inputMap.values()) {
+                        notifyStateChangedLocked(userState, state.mInfo.getId(),
+                                state.mState, callback);
                     }
                 }
             } finally {
@@ -725,13 +717,14 @@ public final class TvInputManagerService extends SystemService {
         }
 
         @Override
-        public void unregisterCallback(ITvInputClient client, String inputId, int userId) {
+        public void unregisterCallback(ITvInputManagerCallback callback, int userId) {
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
                     Binder.getCallingUid(), userId, "unregisterCallback");
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
-                    unregisterCallbackInternalLocked(client.asBinder(), inputId, resolvedUserId);
+                    UserState userState = getUserStateLocked(resolvedUserId);
+                    userState.callbackSet.remove(callback);
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -751,7 +744,7 @@ public final class TvInputManagerService extends SystemService {
                     ServiceState serviceState = userState.serviceStateMap.get(inputId);
                     if (serviceState == null) {
                         serviceState = new ServiceState(
-                                userState.inputMap.get(inputId), resolvedUserId);
+                                userState.inputMap.get(inputId).mInfo, resolvedUserId);
                         userState.serviceStateMap.put(inputId, serviceState);
                     }
                     // Send a null token immediately while reconnecting.
@@ -868,7 +861,7 @@ public final class TvInputManagerService extends SystemService {
                         }
 
                         // Create a log entry and fill it later.
-                        String packageName = userState.inputMap.get(sessionState.mInputId)
+                        String packageName = userState.inputMap.get(sessionState.mInputId).mInfo
                                 .getServiceInfo().packageName;
                         ContentValues values = new ContentValues();
                         values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
@@ -1017,8 +1010,7 @@ public final class TvInputManagerService extends SystemService {
 
         @Override
         public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
-            if (mContext.checkCallingPermission(
-                    android.Manifest.permission.TV_INPUT_HARDWARE)
+            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
                     != PackageManager.PERMISSION_GRANTED) {
                 return null;
             }
@@ -1032,10 +1024,25 @@ public final class TvInputManagerService extends SystemService {
         }
 
         @Override
+        public void registerTvInputInfo(TvInputInfo info, int deviceId) {
+            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
+                    != PackageManager.PERMISSION_GRANTED) {
+                return;
+            }
+
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mTvInputHardwareManager.registerTvInputInfo(info, deviceId);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public ITvInputHardware acquireTvInputHardware(int deviceId,
-                ITvInputHardwareCallback callback, int userId) throws RemoteException {
-            if (mContext.checkCallingPermission(
-                    android.Manifest.permission.TV_INPUT_HARDWARE)
+                ITvInputHardwareCallback callback, TvInputInfo info, int userId)
+                throws RemoteException {
+            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
                     != PackageManager.PERMISSION_GRANTED) {
                 return null;
             }
@@ -1046,7 +1053,7 @@ public final class TvInputManagerService extends SystemService {
                     userId, "acquireTvInputHardware");
             try {
                 return mTvInputHardwareManager.acquireHardware(
-                        deviceId, callback, callingUid, resolvedUserId);
+                        deviceId, callback, info, callingUid, resolvedUserId);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -1055,8 +1062,7 @@ public final class TvInputManagerService extends SystemService {
         @Override
         public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
                 throws RemoteException {
-            if (mContext.checkCallingPermission(
-                    android.Manifest.permission.TV_INPUT_HARDWARE)
+            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
                     != PackageManager.PERMISSION_GRANTED) {
                 return;
             }
@@ -1099,16 +1105,16 @@ public final class TvInputManagerService extends SystemService {
                     pw.println("UserState (" + userId + "):");
                     pw.increaseIndent();
 
-                    pw.println("inputMap: inputId -> TvInputInfo");
+                    pw.println("inputMap: inputId -> TvInputState");
                     pw.increaseIndent();
-                    for (TvInputInfo info : userState.inputMap.values()) {
-                        pw.println(info.toString());
+                    for (TvInputState state : userState.inputMap.values()) {
+                        pw.println(state.toString());
                     }
                     pw.decreaseIndent();
 
-                    pw.println("packageList:");
+                    pw.println("packageSet:");
                     pw.increaseIndent();
-                    for (String packageName : userState.packageList) {
+                    for (String packageName : userState.packageSet) {
                         pw.println(packageName);
                     }
                     pw.decreaseIndent();
@@ -1169,7 +1175,6 @@ public final class TvInputManagerService extends SystemService {
                         pw.println("mService: " + service.mService);
                         pw.println("mCallback: " + service.mCallback);
                         pw.println("mBound: " + service.mBound);
-                        pw.println("mAvailable: " + service.mAvailable);
                         pw.println("mReconnecting: " + service.mReconnecting);
 
                         pw.decreaseIndent();
@@ -1196,18 +1201,38 @@ public final class TvInputManagerService extends SystemService {
                     }
                     pw.decreaseIndent();
 
+                    pw.println("callbackSet:");
+                    pw.increaseIndent();
+                    for (ITvInputManagerCallback callback : userState.callbackSet) {
+                        pw.println(callback.toString());
+                    }
+                    pw.decreaseIndent();
+
                     pw.decreaseIndent();
                 }
             }
         }
     }
 
+    private static final class TvInputState {
+        // A TvInputInfo object which represents the TV input.
+        private TvInputInfo mInfo;
+
+        // The state of TV input. Connected by default.
+        private int mState = INPUT_STATE_CONNECTED;
+
+        @Override
+        public String toString() {
+            return "mInfo: " + mInfo + "; mState: " + mState;
+        }
+    }
+
     private static final class UserState {
-        // A mapping from the TV input id to its TvInputInfo.
-        private final Map<String, TvInputInfo> inputMap = new HashMap<String,TvInputInfo>();
+        // A mapping from the TV input id to its TvInputState.
+        private Map<String, TvInputState> inputMap = new HashMap<String, TvInputState>();
 
-        // A list of all TV input packages.
-        private final Set<String> packageList = new HashSet<String>();
+        // A set of all TV input packages.
+        private final Set<String> packageSet = new HashSet<String>();
 
         // A mapping from the token of a client to its state.
         private final Map<IBinder, ClientState> clientStateMap =
@@ -1220,6 +1245,10 @@ public final class TvInputManagerService extends SystemService {
         // A mapping from the token of a TV input session to its state.
         private final Map<IBinder, SessionState> sessionStateMap =
                 new HashMap<IBinder, SessionState>();
+
+        // A set of callbacks.
+        private final Set<ITvInputManagerCallback> callbackSet =
+                new HashSet<ITvInputManagerCallback>();
     }
 
     private final class ClientState implements IBinder.DeathRecipient {
@@ -1243,7 +1272,7 @@ public final class TvInputManagerService extends SystemService {
             synchronized (mLock) {
                 UserState userState = getUserStateLocked(mUserId);
                 // DO NOT remove the client state of clientStateMap in this method. It will be
-                // removed in releaseSessionLocked() or unregisterCallbackInternalLocked().
+                // removed in releaseSessionLocked() or unregisterClientInternalLocked().
                 ClientState clientState = userState.clientStateMap.get(mClientToken);
                 if (clientState != null) {
                     while (clientState.mSessionTokens.size() > 0) {
@@ -1251,7 +1280,7 @@ public final class TvInputManagerService extends SystemService {
                                 clientState.mSessionTokens.get(0), Process.SYSTEM_UID, mUserId);
                     }
                     while (clientState.mInputIds.size() > 0) {
-                        unregisterCallbackInternalLocked(
+                        unregisterClientInternalLocked(
                                 mClientToken, clientState.mInputIds.get(0), mUserId);
                     }
                 }
@@ -1269,7 +1298,6 @@ public final class TvInputManagerService extends SystemService {
         private ITvInputService mService;
         private ServiceCallback mCallback;
         private boolean mBound;
-        private boolean mAvailable;
         private boolean mReconnecting;
 
         private ServiceState(TvInputInfo inputInfo, int userId) {
@@ -1325,16 +1353,18 @@ public final class TvInputManagerService extends SystemService {
 
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
+            String inputId = mTvInputInfo.getId();
             if (DEBUG) {
-                Slog.d(TAG, "onServiceConnected(inputId=" + mTvInputInfo.getId() + ")");
+                Slog.d(TAG, "onServiceConnected(inputId=" + inputId + ")");
             }
             synchronized (mLock) {
-                ServiceState serviceState = getServiceStateLocked(mTvInputInfo.getId(), mUserId);
+                UserState userState = getUserStateLocked(mUserId);
+                ServiceState serviceState = userState.serviceStateMap.get(inputId);
                 serviceState.mService = ITvInputService.Stub.asInterface(service);
 
                 // Register a callback, if we need to.
                 if (!serviceState.mClientTokens.isEmpty() && serviceState.mCallback == null) {
-                    serviceState.mCallback = new ServiceCallback(mUserId);
+                    serviceState.mCallback = new ServiceCallback(mTvInputInfo.getId(), mUserId);
                     try {
                         serviceState.mService.registerCallback(serviceState.mCallback);
                     } catch (RemoteException e) {
@@ -1346,6 +1376,12 @@ public final class TvInputManagerService extends SystemService {
                 for (IBinder sessionToken : serviceState.mSessionTokens) {
                     createSessionInternalLocked(serviceState.mService, sessionToken, mUserId);
                 }
+
+                TvInputState inputState = userState.inputMap.get(inputId);
+                if (inputState != null && inputState.mState != INPUT_STATE_DISCONNECTED) {
+                    notifyStateChangedLocked(userState, mTvInputInfo.getId(),
+                            inputState.mState, null);
+                }
             }
         }
 
@@ -1377,10 +1413,8 @@ public final class TvInputManagerService extends SystemService {
                         }
                     }
 
-                    if (serviceState.mAvailable) {
-                        serviceState.mAvailable = false;
-                        broadcastServiceAvailabilityChangedLocked(serviceState);
-                    }
+                    notifyStateChangedLocked(userState, mTvInputInfo.getId(),
+                            INPUT_STATE_DISCONNECTED, null);
                     updateServiceConnectionLocked(mTvInputInfo.getId(), mUserId);
                 }
             }
@@ -1388,24 +1422,22 @@ public final class TvInputManagerService extends SystemService {
     }
 
     private final class ServiceCallback extends ITvInputServiceCallback.Stub {
+        private final String mInputId;
         private final int mUserId;
 
-        ServiceCallback(int userId) {
+        ServiceCallback(String inputId, int userId) {
+            mInputId = inputId;
             mUserId = userId;
         }
 
         @Override
-        public void onAvailabilityChanged(String inputId, boolean isAvailable) {
+        public void onInputStateChanged(int state) {
             if (DEBUG) {
-                Slog.d(TAG, "onAvailabilityChanged(inputId=" + inputId + ", isAvailable="
-                        + isAvailable + ")");
+                Slog.d(TAG, "onInputStateChanged(inputId=" + mInputId + ", state="
+                        + state + ")");
             }
             synchronized (mLock) {
-                ServiceState serviceState = getServiceStateLocked(inputId, mUserId);
-                if (serviceState.mAvailable != isAvailable) {
-                    serviceState.mAvailable = isAvailable;
-                    broadcastServiceAvailabilityChangedLocked(serviceState);
-                }
+                setStateLocked(mInputId, state, mUserId);
             }
         }
     }
@@ -1553,4 +1585,12 @@ public final class TvInputManagerService extends SystemService {
             mContentResolver.update(uri, values, null, null);
         }
     }
+
+    final class Client {
+        public void setState(String inputId, int state) {
+            synchronized (mLock) {
+                setStateLocked(inputId, state, mCurrentUserId);
+            }
+        }
+    }
 }