Merge "Bind the hardware TIS only when needed" into main
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index b384725..92b5764 100755
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -309,6 +309,24 @@
         }
     }
 
+    public SparseArray<String> getHardwareInputIdMap() {
+        synchronized (mLock) {
+            return mHardwareInputIdMap.clone();
+        }
+    }
+
+    public SparseArray<String> getHdmiInputIdMap() {
+        synchronized (mLock) {
+            return mHdmiInputIdMap.clone();
+        }
+    }
+
+    public Map<String, TvInputInfo> getInputMap() {
+        synchronized (mLock) {
+            return Collections.unmodifiableMap(mInputMap);
+        }
+    }
+
     public Map<String, List<String>> getHdmiParentInputMap() {
         synchronized (mLock) {
             return Collections.unmodifiableMap(mHdmiParentInputMap);
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index ac1b4df..e434df7 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -135,6 +135,7 @@
     private static final int APP_TAG_SELF = TunedInfo.APP_TAG_SELF;
     private static final String PERMISSION_ACCESS_WATCHED_PROGRAMS =
             "com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS";
+    private static final long UPDATE_HARDWARE_TIS_BINDING_DELAY_IN_MILLIS = 10 * 1000; // 10 seconds
 
     // There are two different formats of DVB frontend devices. One is /dev/dvb%d.frontend%d,
     // another one is /dev/dvb/adapter%d/frontend%d. Followings are the patterns for selecting the
@@ -174,7 +175,7 @@
     @GuardedBy("mLock")
     private final Map<String, SessionState> mSessionIdToSessionStateMap = new HashMap<>();
 
-    private final WatchLogHandler mWatchLogHandler;
+    private final MessageHandler mMessageHandler;
 
     private final ActivityManager mActivityManager;
 
@@ -187,8 +188,8 @@
         super(context);
 
         mContext = context;
-        mWatchLogHandler = new WatchLogHandler(mContext.getContentResolver(),
-                IoThread.get().getLooper());
+        mMessageHandler =
+                new MessageHandler(mContext.getContentResolver(), IoThread.get().getLooper());
         mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
 
         mActivityManager =
@@ -372,10 +373,10 @@
                     // service to populate the hardware list.
                     serviceState = new ServiceState(component, userId);
                     userState.serviceStateMap.put(component, serviceState);
+                    updateServiceConnectionLocked(component, userId);
                 } else {
                     inputList.addAll(serviceState.hardwareInputMap.values());
                 }
-                updateServiceConnectionLocked(component, userId);
             } else {
                 try {
                     TvInputInfo info = new TvInputInfo.Builder(mContext, ri).build();
@@ -510,6 +511,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void startProfileLocked(int userId) {
         mRunningProfiles.add(userId);
         buildTvInputListLocked(userId, null);
@@ -538,8 +540,10 @@
             mCurrentUserId = userId;
             buildTvInputListLocked(userId, null);
             buildTvContentRatingSystemListLocked(userId);
-            mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_SWITCH_CONTENT_RESOLVER,
-                    getContentResolverForUser(userId)).sendToTarget();
+            mMessageHandler
+                    .obtainMessage(MessageHandler.MSG_SWITCH_CONTENT_RESOLVER,
+                            getContentResolverForUser(userId))
+                    .sendToTarget();
         }
     }
 
@@ -593,7 +597,7 @@
                         Slog.e(TAG, "error in unregisterCallback", e);
                     }
                 }
-                mContext.unbindService(serviceState.connection);
+                unbindService(serviceState);
                 it.remove();
             }
         }
@@ -661,7 +665,7 @@
                             Slog.e(TAG, "error in unregisterCallback", e);
                         }
                     }
-                    mContext.unbindService(serviceState.connection);
+                    unbindService(serviceState);
                 }
             }
             userState.serviceStateMap.clear();
@@ -774,7 +778,8 @@
 
         boolean shouldBind;
         if (userId == mCurrentUserId || mRunningProfiles.contains(userId)) {
-            shouldBind = !serviceState.sessionTokens.isEmpty() || serviceState.isHardware;
+            shouldBind = !serviceState.sessionTokens.isEmpty()
+                    || (serviceState.isHardware && serviceState.neverConnected);
         } else {
             // For a non-current user,
             // if sessionTokens is not empty, it contains recording sessions only
@@ -783,31 +788,14 @@
             shouldBind = !serviceState.sessionTokens.isEmpty();
         }
 
-        if (serviceState.service == null && shouldBind) {
-            // This means that the service is not yet connected but its state indicates that we
-            // have pending requests. Then, connect the service.
-            if (serviceState.bound) {
-                // We have already bound to the service so we don't try to bind again until after we
-                // unbind later on.
-                return;
+        // only bind/unbind when necessary.
+        if (shouldBind && !serviceState.bound) {
+            bindService(serviceState, userId);
+        } else if (!shouldBind && serviceState.bound) {
+            unbindService(serviceState);
+            if (!serviceState.isHardware) {
+                userState.serviceStateMap.remove(component);
             }
-            if (DEBUG) {
-                Slog.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
-            }
-
-            Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
-            serviceState.bound = mContext.bindServiceAsUser(
-                    i, serviceState.connection,
-                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
-                    new UserHandle(userId));
-        } else if (serviceState.service != null && !shouldBind) {
-            // This means that the service is already connected but its state indicates that we have
-            // nothing to do with it. Then, disconnect the service.
-            if (DEBUG) {
-                Slog.d(TAG, "unbindService(service=" + component + ")");
-            }
-            mContext.unbindService(serviceState.connection);
-            userState.serviceStateMap.remove(component);
         }
     }
 
@@ -829,7 +817,11 @@
             sendSessionTokenToClientLocked(sessionState.client,
                     sessionState.inputId, null, null, sessionState.seq);
         }
-        updateServiceConnectionLocked(serviceState.component, userId);
+        if (!serviceState.isHardware) {
+            updateServiceConnectionLocked(serviceState.component, userId);
+        } else {
+            updateHardwareServiceConnectionDelayed(userId);
+        }
     }
 
     @GuardedBy("mLock")
@@ -948,13 +940,17 @@
         if (serviceState != null) {
             serviceState.sessionTokens.remove(sessionToken);
         }
-        updateServiceConnectionLocked(sessionState.componentName, userId);
+        if (!serviceState.isHardware) {
+            updateServiceConnectionLocked(sessionState.componentName, userId);
+        } else {
+            updateHardwareServiceConnectionDelayed(userId);
+        }
 
         // Log the end of watch.
         SomeArgs args = SomeArgs.obtain();
         args.arg1 = sessionToken;
         args.arg2 = System.currentTimeMillis();
-        mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_END, args).sendToTarget();
+        mMessageHandler.obtainMessage(MessageHandler.MSG_LOG_WATCH_END, args).sendToTarget();
     }
 
     @GuardedBy("mLock")
@@ -1153,8 +1149,7 @@
         ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent());
         int oldState = inputState.state;
         inputState.state = state;
-        if (serviceState != null && serviceState.service == null
-                && (!serviceState.sessionTokens.isEmpty() || serviceState.isHardware)) {
+        if (serviceState != null && serviceState.reconnecting) {
             // We don't notify state change while reconnecting. It should remain disconnected.
             return;
         }
@@ -1881,7 +1876,7 @@
                         args.arg3 = ContentUris.parseId(channelUri);
                         args.arg4 = params;
                         args.arg5 = sessionToken;
-                        mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_START, args)
+                        mMessageHandler.obtainMessage(MessageHandler.MSG_LOG_WATCH_START, args)
                                 .sendToTarget();
                     } catch (RemoteException | SessionNotFoundException e) {
                         Slog.e(TAG, "error in tune", e);
@@ -3327,16 +3322,21 @@
         private final ComponentName component;
         private final boolean isHardware;
         private final Map<String, TvInputInfo> hardwareInputMap = new HashMap<>();
+        private final List<TvInputHardwareInfo> hardwareDeviceRemovedBuffer = new ArrayList<>();
+        private final List<HdmiDeviceInfo> hdmiDeviceRemovedBuffer = new ArrayList<>();
+        private final List<HdmiDeviceInfo> hdmiDeviceUpdatedBuffer = new ArrayList<>();
 
         private ITvInputService service;
         private ServiceCallback callback;
         private boolean bound;
         private boolean reconnecting;
+        private boolean neverConnected;
 
         private ServiceState(ComponentName component, int userId) {
             this.component = component;
             this.connection = new InputServiceConnection(component, userId);
             this.isHardware = hasHardwarePermission(mContext.getPackageManager(), component);
+            this.neverConnected = true;
         }
     }
 
@@ -3449,6 +3449,97 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private void bindService(ServiceState serviceState, int userId) {
+        if (serviceState.bound) {
+            // We have already bound to the service so we don't try to bind again until after we
+            // unbind later on.
+            // For hardware services, call updateHardwareServiceConnectionDelayed() to delay the
+            // possible unbinding.
+            if (serviceState.isHardware) {
+                updateHardwareServiceConnectionDelayed(userId);
+            }
+            return;
+        }
+        if (DEBUG) {
+            Slog.d(TAG,
+                    "bindServiceAsUser(service=" + serviceState.component + ", userId=" + userId
+                            + ")");
+        }
+        Intent i =
+                new Intent(TvInputService.SERVICE_INTERFACE).setComponent(serviceState.component);
+        serviceState.bound = mContext.bindServiceAsUser(i, serviceState.connection,
+                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
+                new UserHandle(userId));
+        if (!serviceState.bound) {
+            Slog.e(TAG, "failed to bind " + serviceState.component + " for userId " + userId);
+            mContext.unbindService(serviceState.connection);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void unbindService(ServiceState serviceState) {
+        if (!serviceState.bound) {
+            return;
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "unbindService(service=" + serviceState.component + ")");
+        }
+        mContext.unbindService(serviceState.connection);
+        serviceState.bound = false;
+        serviceState.service = null;
+        serviceState.callback = null;
+    }
+
+    @GuardedBy("mLock")
+    private void updateHardwareTvInputServiceBindingLocked(int userId) {
+        PackageManager pm = mContext.getPackageManager();
+        List<ResolveInfo> services =
+                pm.queryIntentServicesAsUser(new Intent(TvInputService.SERVICE_INTERFACE),
+                        PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, userId);
+        for (ResolveInfo ri : services) {
+            ServiceInfo si = ri.serviceInfo;
+            if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
+                continue;
+            }
+            ComponentName component = new ComponentName(si.packageName, si.name);
+            if (hasHardwarePermission(pm, component)) {
+                updateServiceConnectionLocked(component, userId);
+            }
+        }
+    }
+
+    private void updateHardwareServiceConnectionDelayed(int userId) {
+        mMessageHandler.removeMessages(MessageHandler.MSG_UPDATE_HARDWARE_TIS_BINDING);
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = userId;
+        Message msg =
+                mMessageHandler.obtainMessage(MessageHandler.MSG_UPDATE_HARDWARE_TIS_BINDING, args);
+        mMessageHandler.sendMessageDelayed(msg, UPDATE_HARDWARE_TIS_BINDING_DELAY_IN_MILLIS);
+    }
+
+    @GuardedBy("mLock")
+    private void addHardwareInputLocked(
+            TvInputInfo inputInfo, ComponentName component, int userId) {
+        ServiceState serviceState = getServiceStateLocked(component, userId);
+        serviceState.hardwareInputMap.put(inputInfo.getId(), inputInfo);
+        buildTvInputListLocked(userId, null);
+    }
+
+    @GuardedBy("mLock")
+    private void removeHardwareInputLocked(String inputId, int userId) {
+        if (!mTvInputHardwareManager.getInputMap().containsKey(inputId)) {
+            return;
+        }
+        ComponentName component = mTvInputHardwareManager.getInputMap().get(inputId).getComponent();
+        ServiceState serviceState = getServiceStateLocked(component, userId);
+        boolean removed = serviceState.hardwareInputMap.remove(inputId) != null;
+        if (removed) {
+            buildTvInputListLocked(userId, null);
+            mTvInputHardwareManager.removeHardwareInput(inputId);
+        }
+    }
+
     private final class InputServiceConnection implements ServiceConnection {
         private final ComponentName mComponent;
         private final int mUserId;
@@ -3472,6 +3563,7 @@
                 }
                 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
                 serviceState.service = ITvInputService.Stub.asInterface(service);
+                serviceState.neverConnected = false;
 
                 // Register a callback, if we need to.
                 if (serviceState.isHardware && serviceState.callback == null) {
@@ -3483,6 +3575,58 @@
                     }
                 }
 
+                for (TvInputState inputState : userState.inputMap.values()) {
+                    if (inputState.info.getComponent().equals(component)
+                            && inputState.state != INPUT_STATE_CONNECTED) {
+                        notifyInputStateChangedLocked(userState, inputState.info.getId(),
+                                inputState.state, null);
+                    }
+                }
+
+                if (serviceState.isHardware) {
+                    for (TvInputHardwareInfo hardwareToBeRemoved :
+                            serviceState.hardwareDeviceRemovedBuffer) {
+                        try {
+                            serviceState.service.notifyHardwareRemoved(hardwareToBeRemoved);
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "error in hardwareDeviceRemovedBuffer", e);
+                        }
+                    }
+                    serviceState.hardwareDeviceRemovedBuffer.clear();
+                    for (HdmiDeviceInfo hdmiDeviceToBeRemoved :
+                            serviceState.hdmiDeviceRemovedBuffer) {
+                        try {
+                            serviceState.service.notifyHdmiDeviceRemoved(hdmiDeviceToBeRemoved);
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "error in hdmiDeviceRemovedBuffer", e);
+                        }
+                    }
+                    serviceState.hdmiDeviceRemovedBuffer.clear();
+                    for (TvInputHardwareInfo hardware : mTvInputHardwareManager.getHardwareList()) {
+                        try {
+                            serviceState.service.notifyHardwareAdded(hardware);
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "error in notifyHardwareAdded", e);
+                        }
+                    }
+                    for (HdmiDeviceInfo device : mTvInputHardwareManager.getHdmiDeviceList()) {
+                        try {
+                            serviceState.service.notifyHdmiDeviceAdded(device);
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
+                        }
+                    }
+                    for (HdmiDeviceInfo hdmiDeviceToBeUpdated :
+                            serviceState.hdmiDeviceUpdatedBuffer) {
+                        try {
+                            serviceState.service.notifyHdmiDeviceUpdated(hdmiDeviceToBeUpdated);
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "error in hdmiDeviceUpdatedBuffer", e);
+                        }
+                    }
+                    serviceState.hdmiDeviceUpdatedBuffer.clear();
+                }
+
                 List<IBinder> tokensToBeRemoved = new ArrayList<>();
 
                 // And create sessions, if any.
@@ -3496,30 +3640,8 @@
                     removeSessionStateLocked(sessionToken, mUserId);
                 }
 
-                for (TvInputState inputState : userState.inputMap.values()) {
-                    if (inputState.info.getComponent().equals(component)
-                            && inputState.state != INPUT_STATE_CONNECTED) {
-                        notifyInputStateChangedLocked(userState, inputState.info.getId(),
-                                inputState.state, null);
-                    }
-                }
-
                 if (serviceState.isHardware) {
-                    serviceState.hardwareInputMap.clear();
-                    for (TvInputHardwareInfo hardware : mTvInputHardwareManager.getHardwareList()) {
-                        try {
-                            serviceState.service.notifyHardwareAdded(hardware);
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "error in notifyHardwareAdded", e);
-                        }
-                    }
-                    for (HdmiDeviceInfo device : mTvInputHardwareManager.getHdmiDeviceList()) {
-                        try {
-                            serviceState.service.notifyHdmiDeviceAdded(device);
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
-                        }
-                    }
+                    updateHardwareServiceConnectionDelayed(mUserId);
                 }
             }
         }
@@ -3570,13 +3692,6 @@
             }
         }
 
-        @GuardedBy("mLock")
-        private void addHardwareInputLocked(TvInputInfo inputInfo) {
-            ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
-            serviceState.hardwareInputMap.put(inputInfo.getId(), inputInfo);
-            buildTvInputListLocked(mUserId, null);
-        }
-
         public void addHardwareInput(int deviceId, TvInputInfo inputInfo) {
             ensureHardwarePermission();
             ensureValidInput(inputInfo);
@@ -3587,8 +3702,11 @@
                     if (serviceState.hardwareInputMap.containsKey(inputInfo.getId())) {
                         return;
                     }
+                    Slog.d("ServiceCallback",
+                            "addHardwareInput: device id " + deviceId + ", "
+                                    + inputInfo.toString());
                     mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo);
-                    addHardwareInputLocked(inputInfo);
+                    addHardwareInputLocked(inputInfo, mComponent, mUserId);
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -3606,7 +3724,7 @@
                         return;
                     }
                     mTvInputHardwareManager.addHdmiInput(id, inputInfo);
-                    addHardwareInputLocked(inputInfo);
+                    addHardwareInputLocked(inputInfo, mComponent, mUserId);
                     if (mOnScreenInputId != null && mOnScreenSessionState != null) {
                         if (TextUtils.equals(mOnScreenInputId, inputInfo.getParentId())) {
                             // catch the use case when a CEC device is plugged in an HDMI port,
@@ -3635,14 +3753,9 @@
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
-                    ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
-                    boolean removed = serviceState.hardwareInputMap.remove(inputId) != null;
-                    if (removed) {
-                        buildTvInputListLocked(mUserId, null);
-                        mTvInputHardwareManager.removeHardwareInput(inputId);
-                    } else {
-                        Slog.e(TAG, "failed to remove input " + inputId);
-                    }
+                    Slog.d("ServiceCallback",
+                            "removeHardwareInput " + inputId + " by " + mComponent);
+                    removeHardwareInputLocked(inputId, mUserId);
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -4226,11 +4339,12 @@
         return loggedReason;
     }
 
+    @GuardedBy("mLock")
     private UserState getUserStateLocked(int userId) {
         return mUserStates.get(userId);
     }
 
-    private static final class WatchLogHandler extends Handler {
+    private final class MessageHandler extends Handler {
         // There are only two kinds of watch events that can happen on the system:
         // 1. The current TV input session is tuned to a new channel.
         // 2. The session is released for some reason.
@@ -4242,10 +4356,11 @@
         static final int MSG_LOG_WATCH_START = 1;
         static final int MSG_LOG_WATCH_END = 2;
         static final int MSG_SWITCH_CONTENT_RESOLVER = 3;
+        static final int MSG_UPDATE_HARDWARE_TIS_BINDING = 4;
 
         private ContentResolver mContentResolver;
 
-        WatchLogHandler(ContentResolver contentResolver, Looper looper) {
+        MessageHandler(ContentResolver contentResolver, Looper looper) {
             super(looper);
             mContentResolver = contentResolver;
         }
@@ -4304,6 +4419,14 @@
                     mContentResolver = (ContentResolver) msg.obj;
                     break;
                 }
+                case MSG_UPDATE_HARDWARE_TIS_BINDING:
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    int userId = (int) args.arg1;
+                    synchronized (mLock) {
+                        updateHardwareTvInputServiceBindingLocked(userId);
+                    }
+                    args.recycle();
+                    break;
                 default: {
                     Slog.w(TAG, "unhandled message code: " + msg.what);
                     break;
@@ -4359,29 +4482,46 @@
                 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
                 // Broadcast the event to all hardware inputs.
                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
-                    if (!serviceState.isHardware || serviceState.service == null) continue;
+                    if (!serviceState.isHardware) {
+                        continue;
+                    }
                     try {
-                        serviceState.service.notifyHardwareAdded(info);
+                        bindService(serviceState, mCurrentUserId);
+                        if (serviceState.service != null) {
+                            serviceState.service.notifyHardwareAdded(info);
+                        }
                     } catch (RemoteException e) {
                         Slog.e(TAG, "error in notifyHardwareAdded", e);
                     }
                 }
+                updateHardwareServiceConnectionDelayed(mCurrentUserId);
             }
         }
 
         @Override
         public void onHardwareDeviceRemoved(TvInputHardwareInfo info) {
             synchronized (mLock) {
+                String relatedInputId =
+                        mTvInputHardwareManager.getHardwareInputIdMap().get(info.getDeviceId());
+                removeHardwareInputLocked(relatedInputId, mCurrentUserId);
                 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
                 // Broadcast the event to all hardware inputs.
                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
-                    if (!serviceState.isHardware || serviceState.service == null) continue;
+                    if (!serviceState.isHardware) {
+                        continue;
+                    }
                     try {
-                        serviceState.service.notifyHardwareRemoved(info);
+                        bindService(serviceState, mCurrentUserId);
+                        if (serviceState.service != null) {
+                            serviceState.service.notifyHardwareRemoved(info);
+                        } else {
+                            serviceState.hardwareDeviceRemovedBuffer.add(info);
+                        }
                     } catch (RemoteException e) {
                         Slog.e(TAG, "error in notifyHardwareRemoved", e);
                     }
                 }
+                updateHardwareServiceConnectionDelayed(mCurrentUserId);
             }
         }
 
@@ -4391,29 +4531,46 @@
                 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
                 // Broadcast the event to all hardware inputs.
                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
-                    if (!serviceState.isHardware || serviceState.service == null) continue;
+                    if (!serviceState.isHardware) {
+                        continue;
+                    }
                     try {
-                        serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
+                        bindService(serviceState, mCurrentUserId);
+                        if (serviceState.service != null) {
+                            serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
+                        }
                     } catch (RemoteException e) {
                         Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
                     }
                 }
+                updateHardwareServiceConnectionDelayed(mCurrentUserId);
             }
         }
 
         @Override
         public void onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
             synchronized (mLock) {
+                String relatedInputId =
+                        mTvInputHardwareManager.getHdmiInputIdMap().get(deviceInfo.getId());
+                removeHardwareInputLocked(relatedInputId, mCurrentUserId);
                 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
                 // Broadcast the event to all hardware inputs.
                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
-                    if (!serviceState.isHardware || serviceState.service == null) continue;
+                    if (!serviceState.isHardware) {
+                        continue;
+                    }
                     try {
-                        serviceState.service.notifyHdmiDeviceRemoved(deviceInfo);
+                        bindService(serviceState, mCurrentUserId);
+                        if (serviceState.service != null) {
+                            serviceState.service.notifyHdmiDeviceRemoved(deviceInfo);
+                        } else {
+                            serviceState.hdmiDeviceRemovedBuffer.add(deviceInfo);
+                        }
                     } catch (RemoteException e) {
                         Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e);
                     }
                 }
+                updateHardwareServiceConnectionDelayed(mCurrentUserId);
             }
         }
 
@@ -4441,13 +4598,21 @@
                 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
                 // Broadcast the event to all hardware inputs.
                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
-                    if (!serviceState.isHardware || serviceState.service == null) continue;
+                    if (!serviceState.isHardware) {
+                        continue;
+                    }
                     try {
-                        serviceState.service.notifyHdmiDeviceUpdated(deviceInfo);
+                        bindService(serviceState, mCurrentUserId);
+                        if (serviceState.service != null) {
+                            serviceState.service.notifyHdmiDeviceUpdated(deviceInfo);
+                        } else {
+                            serviceState.hdmiDeviceUpdatedBuffer.add(deviceInfo);
+                        }
                     } catch (RemoteException e) {
                         Slog.e(TAG, "error in notifyHdmiDeviceUpdated", e);
                     }
                 }
+                updateHardwareServiceConnectionDelayed(mCurrentUserId);
             }
         }