contexthub: add new getHubs hidden API for V4

- Add hidden API ContextHubManager.getHubs() -> HubInfo
- HubInfo is a union type for VendorHubInfo (newly introduced) and
  ContextHubInfo
- Create HubInfoRegistry for tracking hub and endpoint info

Refactor
- Renamed getHubs to getContextHubs for some internal methods

Sample dumpsys for HubInfo: https://paste.googleplex.com/4989897428697088?raw

Test: manual
Bug: 375487784
Flag: android.chre.flags.offload_api
Change-Id: I841ff14b5378fc1d4df3a0c6b0e6fc082a189059
diff --git a/Android.bp b/Android.bp
index 26d0d65..9cb3067 100644
--- a/Android.bp
+++ b/Android.bp
@@ -220,7 +220,7 @@
         "android.hardware.contexthub-V1.0-java",
         "android.hardware.contexthub-V1.1-java",
         "android.hardware.contexthub-V1.2-java",
-        "android.hardware.contexthub-V3-java",
+        "android.hardware.contexthub-V4-java",
         "android.hardware.gnss-V1.0-java",
         "android.hardware.gnss-V2.1-java",
         "android.hardware.health-V1.0-java-constants",
diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java
index 858ec23..af715e4 100644
--- a/core/java/android/hardware/location/ContextHubInfo.java
+++ b/core/java/android/hardware/location/ContextHubInfo.java
@@ -15,7 +15,6 @@
  */
 package android.hardware.location;
 
-import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 6284e70..494bfc9 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -18,6 +18,7 @@
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -31,7 +32,6 @@
 import android.app.PendingIntent;
 import android.chre.flags.Flags;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.hardware.contexthub.ErrorCode;
 import android.os.Handler;
@@ -484,15 +484,33 @@
         }
     }
 
-   /**
-    * Helper function to generate a stub for a query transaction callback.
-    *
-    * @param transaction the transaction to unblock when complete
-    *
-    * @return the callback
-    *
-    * @hide
-    */
+    /**
+     * Returns the list of HubInfo objects describing the available hubs (including ContextHub and
+     * VendorHub). This method is primarily used for debugging purposes as most clients care about
+     * endpoints and services more than hubs.
+     *
+     * @return the list of HubInfo objects
+     * @see HubInfo
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+    @NonNull
+    @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+    public List<HubInfo> getHubs() {
+        try {
+            return mService.getHubs();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Helper function to generate a stub for a query transaction callback.
+     *
+     * @param transaction the transaction to unblock when complete
+     * @return the callback
+     * @hide
+     */
     private IContextHubTransactionCallback createQueryCallback(
             ContextHubTransaction<List<NanoAppState>> transaction) {
         return new IContextHubTransactionCallback.Stub() {
diff --git a/core/java/android/hardware/location/HubInfo.aidl b/core/java/android/hardware/location/HubInfo.aidl
new file mode 100644
index 0000000..25b5b0a
--- /dev/null
+++ b/core/java/android/hardware/location/HubInfo.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 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.hardware.location;
+
+/** @hide */
+parcelable HubInfo;
diff --git a/core/java/android/hardware/location/HubInfo.java b/core/java/android/hardware/location/HubInfo.java
new file mode 100644
index 0000000..f7de127
--- /dev/null
+++ b/core/java/android/hardware/location/HubInfo.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2024 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.hardware.location;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.chre.flags.Flags;
+import android.os.BadParcelableException;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Union type for {@link ContextHubInfo} and {@link VendorHubInfo}
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_OFFLOAD_API)
+public final class HubInfo implements Parcelable {
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {TYPE_CONTEXT_HUB, TYPE_VENDOR_HUB})
+    private @interface HubType {}
+
+    public static final int TYPE_CONTEXT_HUB = 0;
+    public static final int TYPE_VENDOR_HUB = 1;
+
+    private final long mId;
+    @HubType private final int mType;
+    @Nullable private final ContextHubInfo mContextHubInfo;
+    @Nullable private final VendorHubInfo mVendorHubInfo;
+
+    /** @hide */
+    public HubInfo(long id, @NonNull ContextHubInfo contextHubInfo) {
+        mId = id;
+        mType = TYPE_CONTEXT_HUB;
+        mContextHubInfo = contextHubInfo;
+        mVendorHubInfo = null;
+    }
+
+    /** @hide */
+    public HubInfo(long id, @NonNull VendorHubInfo vendorHubInfo) {
+        mId = id;
+        mType = TYPE_VENDOR_HUB;
+        mContextHubInfo = null;
+        mVendorHubInfo = vendorHubInfo;
+    }
+
+    private HubInfo(Parcel in) {
+        mId = in.readLong();
+        mType = in.readInt();
+
+        switch (mType) {
+            case TYPE_CONTEXT_HUB:
+                mContextHubInfo = ContextHubInfo.CREATOR.createFromParcel(in);
+                mVendorHubInfo = null;
+                break;
+            case TYPE_VENDOR_HUB:
+                mVendorHubInfo = VendorHubInfo.CREATOR.createFromParcel(in);
+                mContextHubInfo = null;
+                break;
+            default:
+                throw new BadParcelableException("Parcelable has invalid type");
+        }
+    }
+
+    /** Get the hub unique identifier */
+    public long getId() {
+        return mId;
+    }
+
+    /** Get the hub type. The type can be {@link TYPE_CONTEXT_HUB} or {@link TYPE_VENDOR_HUB} */
+    public int getType() {
+        return mType;
+    }
+
+    /** Get the {@link ContextHubInfo} object, null if type is not {@link TYPE_CONTEXT_HUB} */
+    @Nullable
+    public ContextHubInfo getContextHubInfo() {
+        return mContextHubInfo;
+    }
+
+    /** Parcel implementation details */
+    public int describeContents() {
+        if (mType == TYPE_CONTEXT_HUB && mContextHubInfo != null) {
+            return mContextHubInfo.describeContents();
+        }
+        if (mType == TYPE_VENDOR_HUB && mVendorHubInfo != null) {
+            return mVendorHubInfo.describeContents();
+        }
+        return 0;
+    }
+
+    /** Parcel implementation details */
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeLong(mId);
+        out.writeInt(mType);
+
+        if (mType == TYPE_CONTEXT_HUB && mContextHubInfo != null) {
+            mContextHubInfo.writeToParcel(out, flags);
+        }
+
+        if (mType == TYPE_VENDOR_HUB && mVendorHubInfo != null) {
+            mVendorHubInfo.writeToParcel(out, flags);
+        }
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder out = new StringBuilder();
+        out.append("HubInfo ID: 0x");
+        out.append(Long.toHexString(mId));
+        out.append("\n");
+        if (mType == TYPE_CONTEXT_HUB && mContextHubInfo != null) {
+            out.append(" ContextHubDetails: ");
+            out.append(mContextHubInfo);
+        }
+        if (mType == TYPE_VENDOR_HUB && mVendorHubInfo != null) {
+            out.append(" VendorHubDetails: ");
+            out.append(mVendorHubInfo);
+        }
+        return out.toString();
+    }
+
+    public static final @NonNull Creator<HubInfo> CREATOR =
+            new Creator<>() {
+                public HubInfo createFromParcel(Parcel in) {
+                    return new HubInfo(in);
+                }
+
+                public HubInfo[] newArray(int size) {
+                    return new HubInfo[size];
+                }
+            };
+}
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index 11f30461..b0cc763 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -18,6 +18,7 @@
 
 // Declare any non-default types here with import statements
 import android.app.PendingIntent;
+import android.hardware.location.HubInfo;
 import android.hardware.location.ContextHubInfo;
 import android.hardware.location.ContextHubMessage;
 import android.hardware.location.NanoApp;
@@ -82,6 +83,10 @@
     @EnforcePermission("ACCESS_CONTEXT_HUB")
     List<ContextHubInfo> getContextHubs();
 
+    // Returns a list of HubInfo objects of available hubs (including ContextHub and VendorHub)
+    @EnforcePermission("ACCESS_CONTEXT_HUB")
+    List<HubInfo> getHubs();
+
     // Loads a nanoapp at the specified hub (new API)
     @EnforcePermission("ACCESS_CONTEXT_HUB")
     void loadNanoAppOnHub(
diff --git a/core/java/android/hardware/location/VendorHubInfo.aidl b/core/java/android/hardware/location/VendorHubInfo.aidl
new file mode 100644
index 0000000..a7936ac
--- /dev/null
+++ b/core/java/android/hardware/location/VendorHubInfo.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2024 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.hardware.location;
+
+/** @hide */
+parcelable VendorHubInfo;
\ No newline at end of file
diff --git a/core/java/android/hardware/location/VendorHubInfo.java b/core/java/android/hardware/location/VendorHubInfo.java
new file mode 100644
index 0000000..26772b1
--- /dev/null
+++ b/core/java/android/hardware/location/VendorHubInfo.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 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.hardware.location;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.chre.flags.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelableHolder;
+
+/**
+ * Information about a VendorHub. VendorHub is similar to ContextHub, but it does not run the
+ * Context Hub Runtime Environment (or nano apps). It provides a unified endpoint messaging API
+ * through the ContextHub V4 HAL.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_OFFLOAD_API)
+public final class VendorHubInfo implements Parcelable {
+    private final String mName;
+    private final int mVersion;
+    private final ParcelableHolder mExtendedInfo;
+
+    /** @hide */
+    public VendorHubInfo(android.hardware.contexthub.VendorHubInfo halHubInfo) {
+        mName = halHubInfo.name;
+        mVersion = halHubInfo.version;
+        mExtendedInfo = halHubInfo.extendedInfo;
+    }
+
+    private VendorHubInfo(Parcel in) {
+        mName = in.readString();
+        mVersion = in.readInt();
+        mExtendedInfo = ParcelableHolder.CREATOR.createFromParcel(in);
+    }
+
+    /** Get the hub name */
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    /** Get the hub version */
+    public int getVersion() {
+        return mVersion;
+    }
+
+    /** Parcel implementation details */
+    public int describeContents() {
+        return mExtendedInfo.describeContents();
+    }
+
+    /** Parcel implementation details */
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeString(mName);
+        out.writeInt(mVersion);
+        mExtendedInfo.writeToParcel(out, flags);
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder out = new StringBuilder();
+        out.append("VendorHub Name : ");
+        out.append(mName);
+        out.append(", Version : ");
+        out.append(mVersion);
+        return out.toString();
+    }
+
+    public static final @NonNull Creator<VendorHubInfo> CREATOR =
+            new Creator<>() {
+                public VendorHubInfo createFromParcel(Parcel in) {
+                    return new VendorHubInfo(in);
+                }
+
+                public VendorHubInfo[] newArray(int size) {
+                    return new VendorHubInfo[size];
+                }
+            };
+}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index acc8f66..f611c57 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -35,6 +35,7 @@
 import android.hardware.location.ContextHubInfo;
 import android.hardware.location.ContextHubMessage;
 import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.HubInfo;
 import android.hardware.location.IContextHubCallback;
 import android.hardware.location.IContextHubClient;
 import android.hardware.location.IContextHubClientCallback;
@@ -57,6 +58,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.Pair;
 import android.util.proto.ProtoOutputStream;
@@ -134,6 +136,9 @@
     private Map<Integer, ContextHubInfo> mContextHubIdToInfoMap;
     private List<String> mSupportedContextHubPerms;
     private List<ContextHubInfo> mContextHubInfoList;
+
+    @Nullable private final HubInfoRegistry mHubInfoRegistry;
+
     private final RemoteCallbackList<IContextHubCallback> mCallbacksList =
             new RemoteCallbackList<>();
 
@@ -309,10 +314,21 @@
         mContext = context;
         long startTimeNs = SystemClock.elapsedRealtimeNanos();
         mContextHubWrapper = contextHubWrapper;
+
         if (!initContextHubServiceState(startTimeNs)) {
             Log.e(TAG, "Failed to initialize the Context Hub Service");
+            mHubInfoRegistry = null;
             return;
         }
+
+        if (Flags.offloadApi()) {
+            mHubInfoRegistry = new HubInfoRegistry(mContextHubWrapper);
+            Log.i(TAG, "Enabling generic offload API");
+        } else {
+            mHubInfoRegistry = null;
+            Log.i(TAG, "Disabling generic offload API");
+        }
+
         initDefaultClientMap();
 
         initLocationSettingNotifications();
@@ -427,7 +443,7 @@
 
         Pair<List<ContextHubInfo>, List<String>> hubInfo;
         try {
-            hubInfo = mContextHubWrapper.getHubs();
+            hubInfo = mContextHubWrapper.getContextHubs();
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException while getting Context Hub info", e);
             hubInfo = new Pair<>(Collections.emptyList(), Collections.emptyList());
@@ -713,6 +729,16 @@
         return mContextHubInfoList;
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+    @Override
+    public List<HubInfo> getHubs() throws RemoteException {
+        super.getHubs_enforcePermission();
+        if (mHubInfoRegistry == null) {
+            return Collections.emptyList();
+        }
+        return mHubInfoRegistry.getHubs();
+    }
+
     /**
      * Creates an internal load transaction callback to be used for old API clients
      *
@@ -1417,6 +1443,8 @@
             }
         }
 
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+        pw = ipw;
         pw.println("Dumping ContextHub Service");
 
         pw.println("");
@@ -1428,6 +1456,11 @@
         pw.println("Supported permissions: "
                 + Arrays.toString(mSupportedContextHubPerms.toArray()));
         pw.println("");
+
+        if (mHubInfoRegistry != null) {
+            mHubInfoRegistry.dump(ipw);
+        }
+
         pw.println("=================== NANOAPPS ====================");
         // Dump nanoAppHash
         mNanoAppStateManager.foreachNanoAppInstanceInfo(pw::println);
diff --git a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
new file mode 100644
index 0000000..68de9db
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 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 com.android.server.location.contexthub;
+
+import android.hardware.location.HubInfo;
+import android.os.RemoteException;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+
+import java.util.Collections;
+import java.util.List;
+
+class HubInfoRegistry {
+    private static final String TAG = "HubInfoRegistry";
+
+    private final IContextHubWrapper mContextHubWrapper;
+
+    private final List<HubInfo> mHubsInfo;
+
+    HubInfoRegistry(IContextHubWrapper contextHubWrapper) {
+        List<HubInfo> hubInfos;
+        mContextHubWrapper = contextHubWrapper;
+        try {
+            hubInfos = mContextHubWrapper.getHubs();
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException while getting Hub info", e);
+            hubInfos = Collections.emptyList();
+        }
+        mHubsInfo = hubInfos;
+    }
+
+    /** Retrieve the list of hubs available. */
+    List<HubInfo> getHubs() {
+        return mHubsInfo;
+    }
+
+    void dump(IndentingPrintWriter ipw) {
+        ipw.println(TAG);
+
+        ipw.increaseIndent();
+        for (HubInfo hubInfo : mHubsInfo) {
+            ipw.println(hubInfo);
+        }
+        ipw.decreaseIndent();
+    }
+}
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 5e9277a..6656a6f 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -30,9 +30,11 @@
 import android.hardware.contexthub.V1_2.IContexthubCallback;
 import android.hardware.location.ContextHubInfo;
 import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.HubInfo;
 import android.hardware.location.NanoAppBinary;
 import android.hardware.location.NanoAppMessage;
 import android.hardware.location.NanoAppState;
+import android.hardware.location.VendorHubInfo;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -52,13 +54,14 @@
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * @hide
  */
 public abstract class IContextHubWrapper {
+    private static final boolean DEBUG = false;
     private static final String TAG = "IContextHubWrapper";
 
     /**
@@ -217,10 +220,14 @@
         return proxy == null ? null : new ContextHubWrapperAidl(proxy);
     }
 
-    /**
-     * Calls the appropriate getHubs function depending on the HAL version.
-     */
-    public abstract Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException;
+    /** Calls the appropriate getHubs function depending on the HAL version. */
+    public abstract Pair<List<ContextHubInfo>, List<String>> getContextHubs()
+            throws RemoteException;
+
+    /** Calls the appropriate getHubs function depending on the HAL version. */
+    public List<HubInfo> getHubs() throws RemoteException {
+        return Collections.emptyList();
+    }
 
     /**
      * @return True if this version of the Contexthub HAL supports Location setting notifications.
@@ -556,7 +563,7 @@
             mIsTestModeEnabled.set(false);
         }
 
-        public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
+        public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException {
             android.hardware.contexthub.IContextHub hub = getHub();
             if (hub == null) {
                 return new Pair<List<ContextHubInfo>, List<String>>(new ArrayList<ContextHubInfo>(),
@@ -574,6 +581,47 @@
             return new Pair(hubInfoList, new ArrayList<String>(supportedPermissions));
         }
 
+        public List<HubInfo> getHubs() throws RemoteException {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return Collections.emptyList();
+            }
+
+            List<HubInfo> retVal = new ArrayList<>();
+            final List<android.hardware.contexthub.HubInfo> halHubs = hub.getHubs();
+
+            for (android.hardware.contexthub.HubInfo halHub : halHubs) {
+                /* HAL -> API Type conversion */
+                final HubInfo hubInfo;
+                switch (halHub.hubDetails.getTag()) {
+                    case android.hardware.contexthub.HubInfo.HubDetails.contextHubInfo:
+                        ContextHubInfo contextHubInfo =
+                                new ContextHubInfo(halHub.hubDetails.getContextHubInfo());
+                        hubInfo = new HubInfo(halHub.hubId, contextHubInfo);
+                        break;
+                    case android.hardware.contexthub.HubInfo.HubDetails.vendorHubInfo:
+                        VendorHubInfo vendorHubInfo =
+                                new VendorHubInfo(halHub.hubDetails.getVendorHubInfo());
+                        hubInfo = new HubInfo(halHub.hubId, vendorHubInfo);
+                        break;
+                    default:
+                        Log.w(TAG, "getHubs: invalid hub: " + halHub);
+                        // Invalid
+                        continue;
+                }
+
+                if (DEBUG) {
+                    Log.i(TAG, "getHubs: hubInfo=" + hubInfo);
+                }
+                retVal.add(hubInfo);
+            }
+
+            if (DEBUG) {
+                Log.i(TAG, "getHubs: total count=" + retVal.size());
+            }
+            return retVal;
+        }
+
         public boolean supportsLocationSettingNotifications() {
             return true;
         }
@@ -1061,7 +1109,7 @@
             mHub = hub;
         }
 
-        public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
+        public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException {
             ArrayList<ContextHubInfo> hubInfoList = new ArrayList<>();
             for (ContextHub hub : mHub.getHubs()) {
                 hubInfoList.add(new ContextHubInfo(hub));
@@ -1106,7 +1154,7 @@
             mHub = hub;
         }
 
-        public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
+        public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException {
             ArrayList<ContextHubInfo> hubInfoList = new ArrayList<>();
             for (ContextHub hub : mHub.getHubs()) {
                 hubInfoList.add(new ContextHubInfo(hub));
@@ -1170,7 +1218,7 @@
             mHubInfo = new Pair(hubInfoList, supportedPermissions);
         }
 
-        public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
+        public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException {
             mHub.getHubs_1_2(this);
             return mHubInfo;
         }
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
index 685e8d6..e611867 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
@@ -65,7 +65,7 @@
                 new Pair<>(Arrays.asList(mMockContextHubInfo), Arrays.asList(""));
         when(mMockContextHubInfo.getId()).thenReturn(CONTEXT_HUB_ID);
         when(mMockContextHubInfo.toString()).thenReturn(CONTEXT_HUB_STRING);
-        when(mMockContextHubWrapper.getHubs()).thenReturn(hubInfo);
+        when(mMockContextHubWrapper.getContextHubs()).thenReturn(hubInfo);
 
         when(mMockContextHubWrapper.supportsLocationSettingNotifications()).thenReturn(true);
         when(mMockContextHubWrapper.supportsWifiSettingNotifications()).thenReturn(true);