Merge "Implement queryOffloadCapability" into udc-dev
diff --git a/nearby/service/java/com/android/server/nearby/NearbyService.java b/nearby/service/java/com/android/server/nearby/NearbyService.java
index 514de16..2878fb1 100644
--- a/nearby/service/java/com/android/server/nearby/NearbyService.java
+++ b/nearby/service/java/com/android/server/nearby/NearbyService.java
@@ -147,7 +147,7 @@
 
     @Override
     public void queryOffloadCapability(IOffloadCallback callback) {
-
+        mProviderManager.queryOffloadCapability(callback);
     }
 
     /**
diff --git a/nearby/service/java/com/android/server/nearby/provider/ChreCommunication.java b/nearby/service/java/com/android/server/nearby/provider/ChreCommunication.java
index b24e7d9..020c7b2 100644
--- a/nearby/service/java/com/android/server/nearby/provider/ChreCommunication.java
+++ b/nearby/service/java/com/android/server/nearby/provider/ChreCommunication.java
@@ -39,7 +39,9 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Responsible for setting up communication with the appropriate contexthub on the device and
@@ -47,6 +49,8 @@
  */
 public class ChreCommunication extends ContextHubClientCallback {
 
+    public static final int INVALID_NANO_APP_VERSION = -1;
+
     /** Callback that receives messages forwarded from the context hub. */
     public interface ContextHubCommsCallback {
         /** Indicates whether {@link ChreCommunication} was started successfully. */
@@ -72,8 +76,10 @@
     private boolean mStarted = false;
     // null when CHRE availability result has not been returned
     @Nullable private Boolean mChreSupport = null;
+    private long mNanoAppVersion = INVALID_NANO_APP_VERSION;
     @Nullable private ContextHubCommsCallback mCallback;
     @Nullable private ContextHubClient mContextHubClient;
+    private CountDownLatch mCountDownLatch;
 
     public ChreCommunication(Injector injector, Context context, Executor executor) {
         mInjector = injector;
@@ -169,6 +175,25 @@
         return true;
     }
 
+    /**
+     * Checks the Nano App version
+     */
+    public long queryNanoAppVersion() {
+        if (mCountDownLatch == null || mCountDownLatch.getCount() == 0) {
+            // already gets result from CHRE
+            return mNanoAppVersion;
+        }
+        try {
+            boolean success = mCountDownLatch.await(1, TimeUnit.SECONDS);
+            if (!success) {
+                Log.w(TAG, "Failed to get ContextHubTransaction result before the timeout.");
+            }
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        return mNanoAppVersion;
+    }
+
     @Override
     public synchronized void onMessageFromNanoApp(ContextHubClient client, NanoAppMessage message) {
         mCallback.onMessageFromNanoApp(message);
@@ -245,6 +270,7 @@
                 return;
             }
 
+            mCountDownLatch = new CountDownLatch(1);
             if (response.getResult() == ContextHubTransaction.RESULT_SUCCESS) {
                 for (NanoAppState state : response.getContents()) {
                     long version = state.getNanoAppVersion();
@@ -264,6 +290,8 @@
                                 mExecutor, ChreCommunication.this);
                         mChreSupport = true;
                         mCallback.started(true);
+                        mNanoAppVersion = version;
+                        mCountDownLatch.countDown();
                         return;
                     }
                 }
@@ -281,6 +309,7 @@
             }
 
             mContextHubs.remove(mQueriedContextHub);
+            mCountDownLatch.countDown();
             // If this is the last context hub response left to receive, indicate that
             // there isn't a valid context available on this device.
             if (mContextHubs.isEmpty()) {
diff --git a/nearby/service/java/com/android/server/nearby/provider/ChreDiscoveryProvider.java b/nearby/service/java/com/android/server/nearby/provider/ChreDiscoveryProvider.java
index 40a9ee9..ff18e49 100644
--- a/nearby/service/java/com/android/server/nearby/provider/ChreDiscoveryProvider.java
+++ b/nearby/service/java/com/android/server/nearby/provider/ChreDiscoveryProvider.java
@@ -29,10 +29,13 @@
 import android.nearby.DataElement;
 import android.nearby.NearbyDevice;
 import android.nearby.NearbyDeviceParcelable;
+import android.nearby.OffloadCapability;
 import android.nearby.PresenceDevice;
 import android.nearby.PresenceScanFilter;
 import android.nearby.PublicCredential;
 import android.nearby.ScanFilter;
+import android.nearby.aidl.IOffloadCallback;
+import android.os.RemoteException;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
@@ -141,6 +144,23 @@
         return mChreCommunication.available();
     }
 
+    /**
+     * Query offload capability in a device.
+     */
+    public void queryOffloadCapability(IOffloadCallback callback) {
+        OffloadCapability.Builder builder = new OffloadCapability.Builder();
+        mExecutor.execute(() -> {
+            long version = mChreCommunication.queryNanoAppVersion();
+            builder.setVersion(version);
+            builder.setFastPairSupported(version != ChreCommunication.INVALID_NANO_APP_VERSION);
+            try {
+                callback.onQueryComplete(builder.build());
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+        });
+    }
+
     @VisibleForTesting
     List<ScanFilter> getFiltersLocked() {
         synchronized (mLock) {
diff --git a/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
index 8d973d9..6ee924a 100644
--- a/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
+++ b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
@@ -31,6 +31,7 @@
 import android.nearby.ScanCallback;
 import android.nearby.ScanFilter;
 import android.nearby.ScanRequest;
+import android.nearby.aidl.IOffloadCallback;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
@@ -278,6 +279,13 @@
     }
 
     /**
+     * Query offload capability in a device.
+     */
+    public void queryOffloadCapability(IOffloadCallback callback) {
+        mChreDiscoveryProvider.queryOffloadCapability(callback);
+    }
+
+    /**
      * @return {@code null} when all providers are initializing
      * {@code false} when fail to start all the providers
      * {@code true} when any one of the provider starts successfully
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/ChreCommunicationTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/ChreCommunicationTest.java
index 58b8584..fe28a8c 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/provider/ChreCommunicationTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/ChreCommunicationTest.java
@@ -21,6 +21,7 @@
 import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
 
 import static com.android.server.nearby.NearbyConfiguration.NEARBY_MAINLINE_NANO_APP_MIN_VERSION;
+import static com.android.server.nearby.provider.ChreCommunication.INVALID_NANO_APP_VERSION;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -36,8 +37,10 @@
 import android.hardware.location.ContextHubTransaction;
 import android.hardware.location.NanoAppMessage;
 import android.hardware.location.NanoAppState;
+import android.os.Build;
 import android.provider.DeviceConfig;
 
+import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.server.nearby.injector.Injector;
@@ -54,6 +57,8 @@
 import java.util.concurrent.Executor;
 
 public class ChreCommunicationTest {
+    private static final int APP_VERSION = 1;
+
     @Mock Injector mInjector;
     @Mock Context mContext;
     @Mock ContextHubManager mManager;
@@ -83,7 +88,10 @@
         when(mTransactionResponse.getContents())
                 .thenReturn(
                         Collections.singletonList(
-                                new NanoAppState(ChreDiscoveryProvider.NANOAPP_ID, 1, true)));
+                                new NanoAppState(
+                                        ChreDiscoveryProvider.NANOAPP_ID,
+                                        APP_VERSION,
+                                        true)));
 
         mChreCommunication = new ChreCommunication(mInjector, mContext, new InlineExecutor());
     }
@@ -121,6 +129,19 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public void test_getNanoVersion() {
+        assertThat(mChreCommunication.queryNanoAppVersion()).isEqualTo(INVALID_NANO_APP_VERSION);
+
+        mChreCommunication.start(
+                mChreCallback, Collections.singleton(ChreDiscoveryProvider.NANOAPP_ID));
+        verify(mTransaction).setOnCompleteListener(mOnQueryCompleteListenerCaptor.capture(), any());
+        mOnQueryCompleteListenerCaptor.getValue().onComplete(mTransaction, mTransactionResponse);
+
+        assertThat(mChreCommunication.queryNanoAppVersion()).isEqualTo(APP_VERSION);
+    }
+
+    @Test
     public void testSendMessageToNanApp() {
         mChreCommunication.start(
                 mChreCallback, Collections.singleton(ChreDiscoveryProvider.NANOAPP_ID));
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/ChreDiscoveryProviderTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/ChreDiscoveryProviderTest.java
index 25d3f73..5a56c3c 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/provider/ChreDiscoveryProviderTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/ChreDiscoveryProviderTest.java
@@ -26,11 +26,17 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.hardware.location.NanoAppMessage;
 import android.nearby.DataElement;
 import android.nearby.NearbyDeviceParcelable;
+import android.nearby.OffloadCapability;
+import android.nearby.aidl.IOffloadCallback;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.RemoteException;
 import android.provider.DeviceConfig;
 
 import androidx.test.filters.SdkSuppress;
@@ -57,6 +63,7 @@
 public class ChreDiscoveryProviderTest {
     @Mock AbstractDiscoveryProvider.Listener mListener;
     @Mock ChreCommunication mChreCommunication;
+    @Mock IBinder mIBinder;
 
     @Captor ArgumentCaptor<ChreCommunication.ContextHubCommsCallback> mChreCallbackCaptor;
     @Captor ArgumentCaptor<NearbyDeviceParcelable> mNearbyDevice;
@@ -70,7 +77,9 @@
     private static final int DATA_TYPE_TEST_DE_BEGIN_KEY = 2147483520;
     private static final int DATA_TYPE_TEST_DE_END_KEY = 2147483647;
 
+    private final Object mLock = new Object();
     private ChreDiscoveryProvider mChreDiscoveryProvider;
+    private OffloadCapability mOffloadCapability;
 
     @Before
     public void setUp() {
@@ -92,6 +101,64 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public void test_queryInvalidVersion() {
+        when(mChreCommunication.queryNanoAppVersion()).thenReturn(
+                (long) ChreCommunication.INVALID_NANO_APP_VERSION);
+        IOffloadCallback callback = new IOffloadCallback() {
+            @Override
+            public void onQueryComplete(OffloadCapability capability) throws RemoteException {
+                synchronized (mLock) {
+                    mOffloadCapability = capability;
+                    mLock.notify();
+                }
+            }
+
+            @Override
+            public IBinder asBinder() {
+                return mIBinder;
+            }
+        };
+        mChreDiscoveryProvider.queryOffloadCapability(callback);
+        OffloadCapability capability =
+                new OffloadCapability
+                        .Builder()
+                        .setFastPairSupported(false)
+                        .setVersion(ChreCommunication.INVALID_NANO_APP_VERSION)
+                        .build();
+        assertThat(mOffloadCapability).isEqualTo(capability);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public void test_queryVersion() {
+        long version = 500L;
+        when(mChreCommunication.queryNanoAppVersion()).thenReturn(version);
+        IOffloadCallback callback = new IOffloadCallback() {
+            @Override
+            public void onQueryComplete(OffloadCapability capability) throws RemoteException {
+                synchronized (mLock) {
+                    mOffloadCapability = capability;
+                    mLock.notify();
+                }
+            }
+
+            @Override
+            public IBinder asBinder() {
+                return mIBinder;
+            }
+        };
+        mChreDiscoveryProvider.queryOffloadCapability(callback);
+        OffloadCapability capability =
+                new OffloadCapability
+                        .Builder()
+                        .setFastPairSupported(true)
+                        .setVersion(version)
+                        .build();
+        assertThat(mOffloadCapability).isEqualTo(capability);
+    }
+
+    @Test
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
     public void testOnNearbyDeviceDiscovered() {
         Blefilter.PublicCredential credential =