Move bit utility methods to a new BitUtils class.

In particular this moves NetworkCapabilies#
appendStringRepresentationOfBitMaskToStringBuilder to this
class. It also moves some other NetworkCapabilitiesUtils
methods that are better homed in this new class.

Test: new test in this patch
Change-Id: I16374d7ca1da92052bb814bde796602d62f5c42e
diff --git a/staticlibs/framework/com/android/net/module/util/BitUtils.java b/staticlibs/framework/com/android/net/module/util/BitUtils.java
new file mode 100644
index 0000000..2b32e86
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/BitUtils.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2022 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.net.module.util;
+
+import android.annotation.NonNull;
+
+/**
+ * @hide
+ */
+public class BitUtils {
+    /**
+     * Unpacks long value into an array of bits.
+     */
+    public static int[] unpackBits(long val) {
+        int size = Long.bitCount(val);
+        int[] result = new int[size];
+        int index = 0;
+        int bitPos = 0;
+        while (val != 0) {
+            if ((val & 1) == 1) result[index++] = bitPos;
+            val = val >>> 1;
+            bitPos++;
+        }
+        return result;
+    }
+
+    /**
+     * Packs a list of ints in the same way as packBits()
+     *
+     * Each passed int is the rank of a bit that should be set in the returned long.
+     * Example : passing (1,3) will return in 0b00001010 and passing (5,6,0) will return 0b01100001
+     *
+     * @param bits bits to pack
+     * @return a long with the specified bits set.
+     */
+    public static long packBitList(int... bits) {
+        return packBits(bits);
+    }
+
+    /**
+     * Packs array of bits into a long value.
+     *
+     * Each passed int is the rank of a bit that should be set in the returned long.
+     * Example : passing [1,3] will return in 0b00001010 and passing [5,6,0] will return 0b01100001
+     *
+     * @param bits bits to pack
+     * @return a long with the specified bits set.
+     */
+    public static long packBits(int[] bits) {
+        long packed = 0;
+        for (int b : bits) {
+            packed |= (1L << b);
+        }
+        return packed;
+    }
+
+    /**
+     * An interface for a function that can retrieve a name associated with an int.
+     *
+     * This is useful for bitfields like network capabilities or network score policies.
+     */
+    @FunctionalInterface
+    public interface NameOf {
+        /** Retrieve the name associated with the passed value */
+        String nameOf(int value);
+    }
+
+    /**
+     * Given a bitmask and a name fetcher, append names of all set bits to the builder
+     *
+     * This method takes all bit sets in the passed bitmask, will figure out the name associated
+     * with the weight of each bit with the passed name fetcher, and append each name to the
+     * passed StringBuilder, separated by the passed separator.
+     *
+     * For example, if the bitmask is 0110, and the name fetcher return "BIT_1" to "BIT_4" for
+     * numbers from 1 to 4, and the separator is "&", this method appends "BIT_2&BIT3" to the
+     * StringBuilder.
+     */
+    public static void appendStringRepresentationOfBitMaskToStringBuilder(@NonNull StringBuilder sb,
+            long bitMask, @NonNull NameOf nameFetcher, @NonNull String separator) {
+        int bitPos = 0;
+        boolean firstElementAdded = false;
+        while (bitMask != 0) {
+            if ((bitMask & 1) != 0) {
+                if (firstElementAdded) {
+                    sb.append(separator);
+                } else {
+                    firstElementAdded = true;
+                }
+                sb.append(nameFetcher.nameOf(bitPos));
+            }
+            bitMask >>>= 1;
+            ++bitPos;
+        }
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java b/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
index 26c24f8..54ce01e 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
@@ -44,6 +44,9 @@
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
 
+import static com.android.net.module.util.BitUtils.packBitList;
+import static com.android.net.module.util.BitUtils.unpackBits;
+
 import android.annotation.NonNull;
 import android.net.NetworkCapabilities;
 
@@ -181,49 +184,4 @@
         return false;
     }
 
-    /**
-     * Unpacks long value into an array of bits.
-     */
-    public static int[] unpackBits(long val) {
-        int size = Long.bitCount(val);
-        int[] result = new int[size];
-        int index = 0;
-        int bitPos = 0;
-        while (val != 0) {
-            if ((val & 1) == 1) result[index++] = bitPos;
-            val = val >>> 1;
-            bitPos++;
-        }
-        return result;
-    }
-
-    /**
-     * Packs a list of ints in the same way as packBits()
-     *
-     * Each passed int is the rank of a bit that should be set in the returned long.
-     * Example : passing (1,3) will return in 0b00001010 and passing (5,6,0) will return 0b01100001
-     *
-     * @param bits bits to pack
-     * @return a long with the specified bits set.
-     */
-    public static long packBitList(int... bits) {
-        return packBits(bits);
-    }
-
-    /**
-     * Packs array of bits into a long value.
-     *
-     * Each passed int is the rank of a bit that should be set in the returned long.
-     * Example : passing [1,3] will return in 0b00001010 and passing [5,6,0] will return 0b01100001
-     *
-     * @param bits bits to pack
-     * @return a long with the specified bits set.
-     */
-    public static long packBits(int[] bits) {
-        long packed = 0;
-        for (int b : bits) {
-            packed |= (1L << b);
-        }
-        return packed;
-    }
 }
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/BitUtilsTests.kt b/staticlibs/tests/unit/src/com/android/net/module/util/BitUtilsTests.kt
new file mode 100644
index 0000000..0236716
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/BitUtilsTests.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 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.net.module.util
+
+import com.android.net.module.util.BitUtils.appendStringRepresentationOfBitMaskToStringBuilder
+import com.android.net.module.util.BitUtils.packBits
+import com.android.net.module.util.BitUtils.unpackBits
+import org.junit.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+class BitUtilsTests {
+    @Test
+    fun testBitPackingTestCase() {
+        runBitPackingTestCase(0, intArrayOf())
+        runBitPackingTestCase(1, intArrayOf(0))
+        runBitPackingTestCase(3, intArrayOf(0, 1))
+        runBitPackingTestCase(4, intArrayOf(2))
+        runBitPackingTestCase(63, intArrayOf(0, 1, 2, 3, 4, 5))
+        runBitPackingTestCase(Long.MAX_VALUE.inv(), intArrayOf(63))
+        runBitPackingTestCase(Long.MAX_VALUE.inv() + 1, intArrayOf(0, 63))
+        runBitPackingTestCase(Long.MAX_VALUE.inv() + 2, intArrayOf(1, 63))
+    }
+
+    fun runBitPackingTestCase(packedBits: Long, bits: IntArray) {
+        assertEquals(packedBits, packBits(bits))
+        assertTrue(bits contentEquals unpackBits(packedBits))
+    }
+
+    @Test
+    fun testAppendStringRepresentationOfBitMaskToStringBuilder() {
+        runTestAppendStringRepresentationOfBitMaskToStringBuilder("", 0)
+        runTestAppendStringRepresentationOfBitMaskToStringBuilder("BIT0", 0b1)
+        runTestAppendStringRepresentationOfBitMaskToStringBuilder("BIT1&BIT2&BIT4", 0b10110)
+        runTestAppendStringRepresentationOfBitMaskToStringBuilder(
+                "BIT0&BIT60&BIT61&BIT62&BIT63",
+                (0b11110000_00000000_00000000_00000000 shl 32) +
+                        0b00000000_00000000_00000000_00000001)
+    }
+
+    fun runTestAppendStringRepresentationOfBitMaskToStringBuilder(expected: String, bitMask: Long) {
+        StringBuilder().let {
+            appendStringRepresentationOfBitMaskToStringBuilder(it, bitMask, { i -> "BIT$i" }, "&")
+            assertEquals(expected, it.toString())
+        }
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt
index 256ea1e..958f45f 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt
@@ -22,7 +22,6 @@
 import android.net.NetworkCapabilities.NET_CAPABILITY_CBS
 import android.net.NetworkCapabilities.NET_CAPABILITY_EIMS
 import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
-import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
 import android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID
 import android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH
 import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
@@ -38,8 +37,6 @@
 import com.android.net.module.util.NetworkCapabilitiesUtils.RESTRICTED_CAPABILITIES
 import com.android.net.module.util.NetworkCapabilitiesUtils.UNRESTRICTED_CAPABILITIES
 import com.android.net.module.util.NetworkCapabilitiesUtils.getDisplayTransport
-import com.android.net.module.util.NetworkCapabilitiesUtils.packBits
-import com.android.net.module.util.NetworkCapabilitiesUtils.unpackBits
 import org.junit.Test
 import org.junit.runner.RunWith
 import java.lang.IllegalArgumentException
@@ -75,23 +72,6 @@
         }
     }
 
-    @Test
-    fun testBitPackingTestCase() {
-        runBitPackingTestCase(0, intArrayOf())
-        runBitPackingTestCase(1, intArrayOf(0))
-        runBitPackingTestCase(3, intArrayOf(0, 1))
-        runBitPackingTestCase(4, intArrayOf(2))
-        runBitPackingTestCase(63, intArrayOf(0, 1, 2, 3, 4, 5))
-        runBitPackingTestCase(Long.MAX_VALUE.inv(), intArrayOf(63))
-        runBitPackingTestCase(Long.MAX_VALUE.inv() + 1, intArrayOf(0, 63))
-        runBitPackingTestCase(Long.MAX_VALUE.inv() + 2, intArrayOf(1, 63))
-    }
-
-    fun runBitPackingTestCase(packedBits: Long, bits: IntArray) {
-        assertEquals(packedBits, packBits(bits))
-        assertTrue(bits contentEquals unpackBits(packedBits))
-    }
-
     // NetworkCapabilities constructor and Builder are not available until R. Mark TargetApi to
     // ignore the linter error since it's used in only unit test.
     @Test @TargetApi(Build.VERSION_CODES.R)