Add device state Hal service

Forward device state change changes to all
registered Hal service listeners.

Flag: EXEMPT HAL interface change
Test: atest VtsHalDeviceStateServiceTargetTest
Test: atest FrameworksServicesTests:DeviceStateManagerServiceTest
Bug: 362601542
Change-Id: If7d8616b97b44cfc1974ceddc6b12a816a0d688e
diff --git a/services/core/Android.bp b/services/core/Android.bp
index aea16b0..d2b8bda 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -178,6 +178,7 @@
 
     static_libs: [
         "android.frameworks.vibrator-V1-java", // AIDL
+        "android.frameworks.devicestate-V1-java", // AIDL
         "android.hardware.authsecret-V1.0-java",
         "android.hardware.authsecret-V1-java",
         "android.hardware.boot-V1.0-java", // HIDL
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 8b9c664..2513443 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -19,7 +19,16 @@
 import static android.Manifest.permission.CONTROL_DEVICE_STATE;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FEATURE_DUAL_DISPLAY;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FEATURE_REAR_DISPLAY;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT;
 import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
 import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
 import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST;
 import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS;
@@ -44,6 +53,10 @@
 import android.app.ActivityManagerInternal;
 import android.app.IProcessObserver;
 import android.content.Context;
+import android.frameworks.devicestate.DeviceStateConfiguration;
+import android.frameworks.devicestate.ErrorCode;
+import android.frameworks.devicestate.IDeviceStateListener;
+import android.frameworks.devicestate.IDeviceStateService;
 import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateInfo;
 import android.hardware.devicestate.DeviceStateManager;
@@ -56,9 +69,12 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
 import android.os.ShellCallback;
 import android.os.SystemProperties;
 import android.os.Trace;
+import android.util.LongSparseLongArray;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -82,6 +98,7 @@
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.WeakHashMap;
@@ -130,6 +147,8 @@
     @NonNull
     private final BinderService mBinderService;
     @NonNull
+    private final HalService mHalService;
+    @NonNull
     private final OverrideRequestController mOverrideRequestController;
     @NonNull
     private final DeviceStateProviderListener mDeviceStateProviderListener;
@@ -139,7 +158,7 @@
 
     // All supported device states keyed by identifier.
     @GuardedBy("mLock")
-    private SparseArray<DeviceState> mDeviceStates = new SparseArray<>();
+    private final SparseArray<DeviceState> mDeviceStates = new SparseArray<>();
 
     // The current committed device state. Will be empty until the first device state provided by
     // the DeviceStateProvider is committed.
@@ -177,7 +196,7 @@
     @GuardedBy("mLock")
     private final SparseArray<ProcessRecord> mProcessRecords = new SparseArray<>();
 
-    private Set<Integer> mDeviceStatesAvailableForAppRequests = new HashSet<>();
+    private final Set<Integer> mDeviceStatesAvailableForAppRequests = new HashSet<>();
 
     private Set<Integer> mFoldedDeviceStates = new HashSet<>();
 
@@ -259,6 +278,7 @@
         mDeviceStateProviderListener = new DeviceStateProviderListener();
         mDeviceStatePolicy.getDeviceStateProvider().setListener(mDeviceStateProviderListener);
         mBinderService = new BinderService();
+        mHalService = new HalService();
         mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
         mDeviceStateNotificationController = new DeviceStateNotificationController(
                 context, mHandler,
@@ -272,6 +292,10 @@
     @Override
     public void onStart() {
         publishBinderService(Context.DEVICE_STATE_SERVICE, mBinderService);
+        String halServiceName = IDeviceStateService.DESCRIPTOR + "/default";
+        if (ServiceManager.isDeclared(halServiceName)) {
+            publishBinderService(halServiceName, mHalService);
+        }
         publishLocalService(DeviceStateManagerInternal.class, new LocalService());
 
         if (!Flags.deviceStatePropertyMigration()) {
@@ -440,6 +464,11 @@
         return mBinderService;
     }
 
+    @VisibleForTesting
+    IDeviceStateService getHalBinderService() {
+        return mHalService;
+    }
+
     private void updateSupportedStates(DeviceState[] supportedDeviceStates,
             @DeviceStateProvider.SupportedStatesUpdatedReason int reason) {
         synchronized (mLock) {
@@ -1282,6 +1311,124 @@
         }
     }
 
+    private final class HalService extends IDeviceStateService.Stub {
+        private final LongSparseLongArray mPublicProperties = new LongSparseLongArray();
+        public HalService() {
+            mPublicProperties.put(
+                    DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED,
+                    FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED);
+            mPublicProperties.put(
+                    DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN,
+                    FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN);
+            mPublicProperties.put(
+                    DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN,
+                    FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN);
+            mPublicProperties.put(
+                    PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY,
+                    FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY);
+            mPublicProperties.put(
+                    PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY,
+                    FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY);
+            mPublicProperties.put(
+                    PROPERTY_FEATURE_REAR_DISPLAY,
+                    FEATURE_REAR_DISPLAY);
+            mPublicProperties.put(
+                    PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT,
+                    FEATURE_DUAL_DISPLAY);
+        }
+
+        private final class HalBinderCallback implements IDeviceStateManagerCallback {
+            private final IDeviceStateListener mListener;
+
+            private HalBinderCallback(@NonNull IDeviceStateListener listener) {
+                mListener = listener;
+            }
+
+            @Override
+            public void onDeviceStateInfoChanged(DeviceStateInfo info) throws RemoteException {
+                DeviceStateConfiguration config = new DeviceStateConfiguration();
+                Set<Integer> systemProperties = new HashSet<>(
+                        info.currentState.getConfiguration().getSystemProperties());
+                Set<Integer> physicalProperties = new HashSet<>(
+                        info.currentState.getConfiguration().getPhysicalProperties());
+                config.deviceProperties = 0;
+                for (Integer prop : systemProperties) {
+                    Long publicProperty = mPublicProperties.get(prop);
+                    if (publicProperty != null) {
+                        config.deviceProperties |= publicProperty.longValue();
+                    }
+                }
+                for (Integer prop : physicalProperties) {
+                    Long publicProperty = mPublicProperties.get(prop);
+                    if (publicProperty != null) {
+                        config.deviceProperties |= publicProperty.longValue();
+                    }
+                }
+                mListener.onDeviceStateChanged(config);
+            }
+
+            @Override
+            public void onRequestActive(IBinder token) {
+                //No-op
+            }
+
+            @Override
+            public void onRequestCanceled(IBinder token) {
+                //No-op
+            }
+
+            @Override
+            public IBinder asBinder() {
+                return mListener.asBinder();
+            }
+        }
+
+        @Override
+        public void registerListener(IDeviceStateListener listener) throws RemoteException {
+            if (listener == null) {
+                throw new ServiceSpecificException(ErrorCode.BAD_INPUT);
+            }
+
+            final int callingPid = Binder.getCallingPid();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                HalBinderCallback callback = new HalBinderCallback(listener);
+                DeviceStateInfo info = registerProcess(callingPid, callback);
+                if (info != null)  {
+                    callback.onDeviceStateInfoChanged(info);
+                }
+            } catch (SecurityException e) {
+                throw new ServiceSpecificException(ErrorCode.ALREADY_EXISTS);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void unregisterListener(IDeviceStateListener listener) throws RemoteException {
+            final int callingPid = Binder.getCallingPid();
+
+            synchronized (mLock) {
+                if (mProcessRecords.contains(callingPid)) {
+                    mProcessRecords.remove(callingPid);
+                    return;
+                }
+            }
+
+            throw new ServiceSpecificException(ErrorCode.BAD_INPUT);
+        }
+
+        @Override
+        public int getInterfaceVersion() throws RemoteException {
+            return IDeviceStateService.VERSION;
+        }
+
+        @Override
+        public String getInterfaceHash() throws RemoteException {
+            return IDeviceStateService.HASH;
+        }
+    }
+
     /** Implementation of {@link IDeviceStateManager} published as a binder service. */
     private final class BinderService extends IDeviceStateManager.Stub {
 
diff --git a/services/manifest_services.xml b/services/manifest_services.xml
index 114fe32..9457205 100644
--- a/services/manifest_services.xml
+++ b/services/manifest_services.xml
@@ -4,4 +4,9 @@
         <version>2</version>
         <fqname>IAltitudeService/default</fqname>
     </hal>
+    <hal format="aidl">
+        <name>android.frameworks.devicestate</name>
+        <version>1</version>
+        <fqname>IDeviceStateService/default</fqname>
+    </hal>
 </manifest>
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index ab5a5a9..5127b2d 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -24,9 +24,14 @@
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertThrows;
 
 import android.content.Context;
+import android.frameworks.devicestate.DeviceStateConfiguration;
+import android.frameworks.devicestate.ErrorCode;
+import android.frameworks.devicestate.IDeviceStateListener;
+import android.frameworks.devicestate.IDeviceStateService;
 import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateInfo;
 import android.hardware.devicestate.DeviceStateRequest;
@@ -34,6 +39,7 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.ServiceSpecificException;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.flag.junit.FlagsParameterization;
 import android.platform.test.flag.junit.SetFlagsRule;
@@ -336,6 +342,53 @@
     }
 
     @Test
+    public void halRegisterUnregisterCallback() throws RemoteException {
+        IDeviceStateService halService = mService.getHalBinderService();
+        IDeviceStateListener halListener = new IDeviceStateListener.Stub() {
+            @Override
+            public void onDeviceStateChanged(DeviceStateConfiguration deviceState) { }
+
+            @Override
+            public int getInterfaceVersion() {
+                return IDeviceStateListener.VERSION;
+            }
+
+            @Override
+            public String getInterfaceHash() {
+                return IDeviceStateListener.HASH;
+            }
+        };
+
+        int errorCode = ErrorCode.OK;
+        try {
+            halService.unregisterListener(halListener);
+        } catch(ServiceSpecificException e) {
+            errorCode = e.errorCode;
+        }
+        assertEquals(errorCode, ErrorCode.BAD_INPUT);
+
+        errorCode = ErrorCode.OK;
+        try {
+            halService.unregisterListener(null);
+        } catch(ServiceSpecificException e) {
+            errorCode = e.errorCode;
+        }
+        assertEquals(errorCode, ErrorCode.BAD_INPUT);
+
+        halService.registerListener(halListener);
+
+        errorCode = ErrorCode.OK;
+        try {
+            halService.registerListener(halListener);
+        } catch (ServiceSpecificException e) {
+            errorCode = e.errorCode;
+        }
+        assertEquals(errorCode, ErrorCode.ALREADY_EXISTS);
+
+        halService.unregisterListener(halListener);
+    }
+
+    @Test
     public void registerCallback() throws RemoteException {
         final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
         mService.getBinderService().registerCallback(callback);