BatteryService: Clean up init logic.

* Use getService instead of getTransport, because
  getService checks VINTF already. Init has fewer #
  of hwbinder calls and simpler logic.

* init() calls registerCallback() to HAL synchronously. Now that
  there is a way to check for equality of interfaces in Java,
  registerCallback can be called in HealthServiceWrapper.init()
  earlier, and registerCallback in service notification can
  be avoided when the service pre-exists.

* Instead of making hwbinder calls in a lock in hwbinder threads
  (service notification callback), post to a background HandlerThread.
  As a consequence, no lock is needed because ordering is ensured.
  (Making hwbinder calls in a lock can lead to deadlocks if an
  implementation calls back to system server and tries to acquire the
  same lock.)

Test: boot 20 times
Test: BatteryServiceTest

Change-Id: Id27b789da78f0df9f867cba75d15203a4fb74e04
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 1b61866..7a60a5d 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -49,6 +49,7 @@
 import android.os.Binder;
 import android.os.FileUtils;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBatteryPropertiesListener;
 import android.os.IBatteryPropertiesRegistrar;
 import android.os.IBinder;
@@ -75,6 +76,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.NoSuchElementException;
+import java.util.Objects;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
@@ -176,6 +178,7 @@
     private HealthServiceWrapper mHealthServiceWrapper;
     private HealthHalCallback mHealthHalCallback;
     private BatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
+    private HandlerThread mHandlerThread;
 
     public BatteryService(Context context) {
         super(context);
@@ -1195,14 +1198,13 @@
                 Arrays.asList(INSTANCE_VENDOR, INSTANCE_HEALTHD);
 
         private final IServiceNotification mNotification = new Notification();
+        private final HandlerThread mHandlerThread = new HandlerThread("HealthServiceRefresh");
         // These variables are fixed after init.
         private Callback mCallback;
         private IHealthSupplier mHealthSupplier;
         private String mInstanceName;
 
-        private final Object mLastServiceSetLock = new Object();
         // Last IHealth service received.
-        // set must be also be guarded with mLastServiceSetLock to ensure ordering.
         private final AtomicReference<IHealth> mLastService = new AtomicReference<>();
 
         /**
@@ -1220,6 +1222,10 @@
          * Start monitoring registration of new IHealth services. Only instances that are in
          * {@code sAllInstances} and in device / framework manifest are used. This function should
          * only be called once.
+         *
+         * mCallback.onRegistration() is called synchronously (aka in init thread) before
+         * this method returns.
+         *
          * @throws RemoteException transaction error when talking to IServiceManager
          * @throws NoSuchElementException if one of the following cases:
          *         - No service manager;
@@ -1240,40 +1246,48 @@
             mCallback = callback;
             mHealthSupplier = healthSupplier;
 
-            traceBegin("HealthInitGetManager");
-            try {
-                manager = managerSupplier.get();
-            } finally {
-                traceEnd();
-            }
+            // Initialize mLastService and call callback for the first time (in init thread)
+            IHealth newService = null;
             for (String name : sAllInstances) {
-                traceBegin("HealthInitGetTransport_" + name);
+                traceBegin("HealthInitGetService_" + name);
                 try {
-                    if (manager.getTransport(IHealth.kInterfaceName, name) !=
-                            IServiceManager.Transport.EMPTY) {
-                        mInstanceName = name;
-                        break;
-                    }
+                    newService = healthSupplier.get(name);
+                } catch (NoSuchElementException ex) {
+                    /* ignored, handled below */
                 } finally {
                     traceEnd();
                 }
+                if (newService != null) {
+                    mInstanceName = name;
+                    mLastService.set(newService);
+                    break;
+                }
             }
 
-            if (mInstanceName == null) {
+            if (mInstanceName == null || newService == null) {
                 throw new NoSuchElementException(String.format(
                         "No IHealth service instance among %s is available. Perhaps no permission?",
                         sAllInstances.toString()));
             }
+            mCallback.onRegistration(null, newService, mInstanceName);
 
+            // Register for future service registrations
             traceBegin("HealthInitRegisterNotification");
+            mHandlerThread.start();
             try {
-                manager.registerForNotifications(IHealth.kInterfaceName, mInstanceName, mNotification);
+                managerSupplier.get().registerForNotifications(
+                        IHealth.kInterfaceName, mInstanceName, mNotification);
             } finally {
                 traceEnd();
             }
             Slog.i(TAG, "health: HealthServiceWrapper listening to instance " + mInstanceName);
         }
 
+        @VisibleForTesting
+        HandlerThread getHandlerThread() {
+            return mHandlerThread;
+        }
+
         interface Callback {
             /**
              * This function is invoked asynchronously when a new and related IServiceNotification
@@ -1302,7 +1316,7 @@
          */
         interface IHealthSupplier {
             default IHealth get(String name) throws NoSuchElementException, RemoteException {
-                return IHealth.getService(name);
+                return IHealth.getService(name, true /* retry */);
             }
         }
 
@@ -1312,18 +1326,27 @@
                     boolean preexisting) {
                 if (!IHealth.kInterfaceName.equals(interfaceName)) return;
                 if (!mInstanceName.equals(instanceName)) return;
-                try {
-                    // ensures the order of multiple onRegistration on different threads.
-                    synchronized (mLastServiceSetLock) {
-                        IHealth newService = mHealthSupplier.get(instanceName);
-                        IHealth oldService = mLastService.getAndSet(newService);
-                        Slog.i(TAG, "health: new instance registered " + instanceName);
-                        mCallback.onRegistration(oldService, newService, instanceName);
+
+                // This runnable only runs on mHandlerThread and ordering is ensured, hence
+                // no locking is needed inside the runnable.
+                mHandlerThread.getThreadHandler().post(new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            IHealth newService = mHealthSupplier.get(mInstanceName);
+                            IHealth oldService = mLastService.getAndSet(newService);
+
+                            // preexisting may be inaccurate (race). Check for equality here.
+                            if (Objects.equals(newService, oldService)) return;
+
+                            Slog.i(TAG, "health: new instance registered " + mInstanceName);
+                            mCallback.onRegistration(oldService, newService, mInstanceName);
+                        } catch (NoSuchElementException | RemoteException ex) {
+                            Slog.e(TAG, "health: Cannot get instance '" + mInstanceName
+                                    + "': " + ex.getMessage() + ". Perhaps no permission?");
+                        }
                     }
-                } catch (NoSuchElementException | RemoteException ex) {
-                    Slog.e(TAG, "health: Cannot get instance '" + instanceName + "': " +
-                           ex.getMessage() + ". Perhaps no permission?");
-                }
+                });
             }
         }
     }