[API] Add offloadOnly API

go/presence_api_u

Test: -m
Ignore-AOSP-First: nearby_not_in_aosp_yet
Bug: 265353983
Change-Id: Ib423285439ec62949ae59487081164b44f1580a6
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
index d4bcd32..f3da1f0 100644
--- a/framework-t/api/system-current.txt
+++ b/framework-t/api/system-current.txt
@@ -416,6 +416,7 @@
     method public int getScanType();
     method @NonNull public android.os.WorkSource getWorkSource();
     method public boolean isBleEnabled();
+    method public boolean isOffloadOnly();
     method public static boolean isValidScanMode(int);
     method public static boolean isValidScanType(int);
     method @NonNull public static String scanModeToString(int);
@@ -434,6 +435,7 @@
     method @NonNull public android.nearby.ScanRequest.Builder addScanFilter(@NonNull android.nearby.ScanFilter);
     method @NonNull public android.nearby.ScanRequest build();
     method @NonNull public android.nearby.ScanRequest.Builder setBleEnabled(boolean);
+    method @NonNull public android.nearby.ScanRequest.Builder setOffloadOnly(boolean);
     method @NonNull public android.nearby.ScanRequest.Builder setScanMode(int);
     method @NonNull public android.nearby.ScanRequest.Builder setScanType(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.nearby.ScanRequest.Builder setWorkSource(@Nullable android.os.WorkSource);
diff --git a/nearby/framework/java/android/nearby/ScanRequest.java b/nearby/framework/java/android/nearby/ScanRequest.java
index 9421820..9153631 100644
--- a/nearby/framework/java/android/nearby/ScanRequest.java
+++ b/nearby/framework/java/android/nearby/ScanRequest.java
@@ -33,6 +33,8 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * An encapsulation of various parameters for requesting nearby scans.
@@ -78,6 +80,7 @@
                     .setScanType(in.readInt())
                     .setScanMode(in.readInt())
                     .setBleEnabled(in.readBoolean())
+                    .setOffloadOnly(in.readBoolean())
                     .setWorkSource(in.readTypedObject(WorkSource.CREATOR));
             final int size = in.readInt();
             for (int i = 0; i < size; i++) {
@@ -95,14 +98,16 @@
     private final @ScanType int mScanType;
     private final @ScanMode int mScanMode;
     private final boolean mBleEnabled;
+    private final boolean mOffloadOnly;
     private final @NonNull WorkSource mWorkSource;
     private final List<ScanFilter> mScanFilters;
 
     private ScanRequest(@ScanType int scanType, @ScanMode int scanMode, boolean bleEnabled,
-            @NonNull WorkSource workSource, List<ScanFilter> scanFilters) {
+            boolean offloadOnly, @NonNull WorkSource workSource, List<ScanFilter> scanFilters) {
         mScanType = scanType;
         mScanMode = scanMode;
         mBleEnabled = bleEnabled;
+        mOffloadOnly = offloadOnly;
         mWorkSource = workSource;
         mScanFilters = scanFilters;
     }
@@ -168,6 +173,13 @@
     }
 
     /**
+     * Returns if CHRE enabled for scanning.
+     */
+    public boolean isOffloadOnly() {
+        return mOffloadOnly;
+    }
+
+    /**
      * Returns Scan Filters for this request.
      */
     @NonNull
@@ -203,7 +215,8 @@
         stringBuilder.append("Request[")
                 .append("scanType=").append(mScanType);
         stringBuilder.append(", scanMode=").append(scanModeToString(mScanMode));
-        stringBuilder.append(", enableBle=").append(mBleEnabled);
+        stringBuilder.append(", bleEnabled=").append(mBleEnabled);
+        stringBuilder.append(", offloadOnly=").append(mOffloadOnly);
         stringBuilder.append(", workSource=").append(mWorkSource);
         stringBuilder.append(", scanFilters=").append(mScanFilters);
         stringBuilder.append("]");
@@ -215,6 +228,7 @@
         dest.writeInt(mScanType);
         dest.writeInt(mScanMode);
         dest.writeBoolean(mBleEnabled);
+        dest.writeBoolean(mOffloadOnly);
         dest.writeTypedObject(mWorkSource, /* parcelableFlags= */0);
         final int size = mScanFilters.size();
         dest.writeInt(size);
@@ -230,6 +244,7 @@
             return mScanType == otherRequest.mScanType
                     && (mScanMode == otherRequest.mScanMode)
                     && (mBleEnabled == otherRequest.mBleEnabled)
+                    && (mOffloadOnly == otherRequest.mOffloadOnly)
                     && (Objects.equals(mWorkSource, otherRequest.mWorkSource));
         }
         return false;
@@ -237,7 +252,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mScanType, mScanMode, mBleEnabled, mWorkSource);
+        return Objects.hash(mScanType, mScanMode, mBleEnabled, mOffloadOnly, mWorkSource);
     }
 
     /** @hide **/
@@ -260,6 +275,7 @@
         private @ScanMode int mScanMode;
 
         private boolean mBleEnabled;
+        private boolean mOffloadOnly;
         private WorkSource mWorkSource;
         private List<ScanFilter> mScanFilters;
 
@@ -267,6 +283,7 @@
         public Builder() {
             mScanType = INVALID_SCAN_TYPE;
             mBleEnabled = true;
+            mOffloadOnly = true;
             mWorkSource = new WorkSource();
             mScanFilters = new ArrayList<>();
         }
@@ -307,6 +324,22 @@
         }
 
         /**
+         * By default, a scan request can be served by either offload or
+         * non-offload implementation, depending on the resource available in the device.
+         *
+         * A client can explicitly request a scan to be served by offload only.
+         * Before the request, the client should query the offload capability by
+         * using {@link NearbyManager#queryOffloadCapability(Executor, Consumer)}}. Otherwise,
+         * {@link ScanCallback#ERROR_UNSUPPORTED} will be returned on devices without
+         * offload capability.
+         */
+        @NonNull
+        public Builder setOffloadOnly(boolean offloadOnly) {
+            mOffloadOnly = offloadOnly;
+            return this;
+        }
+
+        /**
          * Sets the work source to use for power attribution for this scan request. Defaults to
          * empty work source, which implies the caller that sends the scan request will be used
          * for power attribution.
@@ -361,7 +394,8 @@
             Preconditions.checkState(isValidScanMode(mScanMode),
                     "invalid scan mode : " + mScanMode
                             + ", scan mode must be one of ScanMode#SCAN_MODE_");
-            return new ScanRequest(mScanType, mScanMode, mBleEnabled, mWorkSource, mScanFilters);
+            return new ScanRequest(
+                    mScanType, mScanMode, mBleEnabled, mOffloadOnly, mWorkSource, mScanFilters);
         }
     }
 }
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/ScanRequestTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/ScanRequestTest.java
index de4b1c3..2201f18 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/ScanRequestTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/ScanRequestTest.java
@@ -30,7 +30,6 @@
 import android.nearby.PublicCredential;
 import android.nearby.ScanRequest;
 import android.os.Build;
-import android.os.WorkSource;
 
 import androidx.annotation.RequiresApi;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -43,12 +42,10 @@
 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
 public class ScanRequestTest {
 
-    private static final int UID = 1001;
-    private static final String APP_NAME = "android.nearby.tests";
     private static final int RSSI = -40;
 
     @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
     public void testScanType() {
         ScanRequest request = new ScanRequest.Builder()
                 .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
@@ -59,13 +56,13 @@
 
     // Valid scan type must be set to one of ScanRequest#SCAN_TYPE_
     @Test(expected = IllegalStateException.class)
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
     public void testScanType_notSet_throwsException() {
         new ScanRequest.Builder().setScanMode(SCAN_MODE_BALANCED).build();
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
     public void testScanMode_defaultLowPower() {
         ScanRequest request = new ScanRequest.Builder()
                 .setScanType(SCAN_TYPE_FAST_PAIR)
@@ -76,7 +73,7 @@
 
     /** Verify setting work source with null value in the scan request is allowed */
     @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
     public void testSetWorkSource_nullValue() {
         ScanRequest request = new ScanRequest.Builder()
                 .setScanType(SCAN_TYPE_FAST_PAIR)
@@ -87,38 +84,8 @@
         assertThat(request.getWorkSource().isEmpty()).isTrue();
     }
 
-    /** Verify toString returns expected string. */
     @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testToString() {
-        WorkSource workSource = getWorkSource();
-        ScanRequest request = new ScanRequest.Builder()
-                .setScanType(SCAN_TYPE_FAST_PAIR)
-                .setScanMode(SCAN_MODE_BALANCED)
-                .setBleEnabled(true)
-                .setWorkSource(workSource)
-                .build();
-
-        assertThat(request.toString()).isEqualTo(
-                "Request[scanType=1, scanMode=SCAN_MODE_BALANCED, "
-                        + "enableBle=true, workSource=WorkSource{" + UID + " " + APP_NAME
-                        + "}, scanFilters=[]]");
-    }
-
-    /** Verify toString works correctly with null WorkSource. */
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testToString_nullWorkSource() {
-        ScanRequest request = new ScanRequest.Builder().setScanType(
-                SCAN_TYPE_FAST_PAIR).setWorkSource(null).build();
-
-        assertThat(request.toString()).isEqualTo("Request[scanType=1, "
-                + "scanMode=SCAN_MODE_LOW_POWER, enableBle=true, workSource=WorkSource{}, "
-                + "scanFilters=[]]");
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
     public void testisEnableBle_defaultTrue() {
         ScanRequest request = new ScanRequest.Builder()
                 .setScanType(SCAN_TYPE_FAST_PAIR)
@@ -128,7 +95,7 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
     public void test_isValidScanType() {
         assertThat(ScanRequest.isValidScanType(SCAN_TYPE_FAST_PAIR)).isTrue();
         assertThat(ScanRequest.isValidScanType(SCAN_TYPE_NEARBY_PRESENCE)).isTrue();
@@ -138,7 +105,7 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
     public void test_isValidScanMode() {
         assertThat(ScanRequest.isValidScanMode(SCAN_MODE_LOW_LATENCY)).isTrue();
         assertThat(ScanRequest.isValidScanMode(SCAN_MODE_BALANCED)).isTrue();
@@ -150,7 +117,7 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
     public void test_scanModeToString() {
         assertThat(ScanRequest.scanModeToString(2)).isEqualTo("SCAN_MODE_LOW_LATENCY");
         assertThat(ScanRequest.scanModeToString(1)).isEqualTo("SCAN_MODE_BALANCED");
@@ -162,7 +129,7 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
     public void testScanFilter() {
         ScanRequest request = new ScanRequest.Builder().setScanType(
                 SCAN_TYPE_NEARBY_PRESENCE).addScanFilter(getPresenceScanFilter()).build();
@@ -172,7 +139,7 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
     public void describeContents() {
         ScanRequest request = new ScanRequest.Builder()
                 .setScanType(SCAN_TYPE_FAST_PAIR)
@@ -181,7 +148,7 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
     public void testCreatorNewArray() {
         ScanRequest[] requests =
                 ScanRequest.CREATOR.newArray(2);
@@ -207,8 +174,4 @@
                 .addPresenceAction(action)
                 .build();
     }
-
-    private static WorkSource getWorkSource() {
-        return new WorkSource(UID, APP_NAME);
-    }
 }
diff --git a/nearby/tests/unit/src/android/nearby/ScanRequestTest.java b/nearby/tests/unit/src/android/nearby/ScanRequestTest.java
index 12de30e..4de4282 100644
--- a/nearby/tests/unit/src/android/nearby/ScanRequestTest.java
+++ b/nearby/tests/unit/src/android/nearby/ScanRequestTest.java
@@ -24,11 +24,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.os.Build;
 import android.os.Parcel;
 import android.os.WorkSource;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.annotation.RequiresApi;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
@@ -38,14 +41,15 @@
 @Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
 public class ScanRequestTest {
 
     private static final int RSSI = -40;
+    private static final int UID = 1001;
+    private static final String APP_NAME = "android.nearby.tests";
 
     private static WorkSource getWorkSource() {
-        final int uid = 1001;
-        final String appName = "android.nearby.tests";
-        return new WorkSource(uid, appName);
+        return new WorkSource(UID, APP_NAME);
     }
 
     /** Test creating a scan request. */
@@ -104,6 +108,7 @@
 
     /** Verify toString returns expected string. */
     @Test
+    @SdkSuppress(minSdkVersion = 34)
     public void testToString() {
         WorkSource workSource = getWorkSource();
         ScanRequest request = new ScanRequest.Builder()
@@ -115,28 +120,28 @@
 
         assertThat(request.toString()).isEqualTo(
                 "Request[scanType=1, scanMode=SCAN_MODE_BALANCED, "
-                        + "enableBle=true, workSource=WorkSource{1001 android.nearby.tests}, "
-                        + "scanFilters=[]]");
+                        + "bleEnabled=true, offloadOnly=true, "
+                        + "workSource=WorkSource{" + UID + " " + APP_NAME + "}, scanFilters=[]]");
     }
 
     /** Verify toString works correctly with null WorkSource. */
     @Test
+    @SdkSuppress(minSdkVersion = 34)
     public void testToString_nullWorkSource() {
         ScanRequest request = new ScanRequest.Builder().setScanType(
                 SCAN_TYPE_FAST_PAIR).setWorkSource(null).build();
 
         assertThat(request.toString()).isEqualTo("Request[scanType=1, "
-                + "scanMode=SCAN_MODE_LOW_POWER, enableBle=true, workSource=WorkSource{}, "
-                + "scanFilters=[]]");
+                + "scanMode=SCAN_MODE_LOW_POWER, bleEnabled=true, offloadOnly=true, "
+                + "workSource=WorkSource{}, scanFilters=[]]");
     }
 
     /** Verify writing and reading from parcel for scan request. */
     @Test
     public void testParceling() {
-        final int scanType = SCAN_TYPE_NEARBY_PRESENCE;
         WorkSource workSource = getWorkSource();
         ScanRequest originalRequest = new ScanRequest.Builder()
-                .setScanType(scanType)
+                .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
                 .setScanMode(SCAN_MODE_BALANCED)
                 .setBleEnabled(true)
                 .setWorkSource(workSource)