Factorize describeDifferences

Fixes: 259479229
Test: new test for this in this patch
Change-Id: Iecbccd10b04ada49f3485097dfcf3511c26c0925
diff --git a/common/framework/com/android/net/module/util/BitUtils.java b/common/framework/com/android/net/module/util/BitUtils.java
index 2b32e86..3062d8c 100644
--- a/common/framework/com/android/net/module/util/BitUtils.java
+++ b/common/framework/com/android/net/module/util/BitUtils.java
@@ -17,6 +17,7 @@
 package com.android.net.module.util;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 /**
  * @hide
@@ -107,4 +108,33 @@
             ++bitPos;
         }
     }
+
+    /**
+     * Returns a short but human-readable string of updates between an old and a new bit fields.
+     *
+     * @param oldVal the old bit field to diff from
+     * @param newVal the new bit field to diff to
+     * @return a string fit for logging differences, or null if no differences.
+     *         this method cannot return the empty string.
+     */
+    @Nullable
+    public static String describeDifferences(final long oldVal, final long newVal,
+            @NonNull final NameOf nameFetcher) {
+        final long changed = oldVal ^ newVal;
+        if (0 == changed) return null;
+        // If the control reaches here, there are changes (additions, removals, or both) so
+        // the code below is guaranteed to add something to the string and can't return "".
+        final long removed = oldVal & changed;
+        final long added = newVal & changed;
+        final StringBuilder sb = new StringBuilder();
+        if (0 != removed) {
+            sb.append("-");
+            appendStringRepresentationOfBitMaskToStringBuilder(sb, removed, nameFetcher, "-");
+        }
+        if (0 != added) {
+            sb.append("+");
+            appendStringRepresentationOfBitMaskToStringBuilder(sb, added, nameFetcher, "+");
+        }
+        return sb.toString();
+    }
 }
diff --git a/common/tests/unit/src/com/android/net/module/util/BitUtilsTests.kt b/common/tests/unit/src/com/android/net/module/util/BitUtilsTests.kt
index 0236716..49940ea 100644
--- a/common/tests/unit/src/com/android/net/module/util/BitUtilsTests.kt
+++ b/common/tests/unit/src/com/android/net/module/util/BitUtilsTests.kt
@@ -17,11 +17,13 @@
 package com.android.net.module.util
 
 import com.android.net.module.util.BitUtils.appendStringRepresentationOfBitMaskToStringBuilder
+import com.android.net.module.util.BitUtils.describeDifferences
 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.assertNull
 import kotlin.test.assertTrue
+import org.junit.Test
 
 class BitUtilsTests {
     @Test
@@ -58,4 +60,23 @@
             assertEquals(expected, it.toString())
         }
     }
+
+    @Test
+    fun testDescribeDifferences() {
+        fun describe(a: Long, b: Long) = describeDifferences(a, b, Integer::toString)
+        assertNull(describe(0, 0))
+        assertNull(describe(5, 5))
+        assertNull(describe(Long.MAX_VALUE, Long.MAX_VALUE))
+
+        assertEquals("+0", describe(0, 1))
+        assertEquals("-0", describe(1, 0))
+
+        assertEquals("+0+2", describe(0, 5))
+        assertEquals("+2", describe(1, 5))
+        assertEquals("-0+2", describe(1, 4))
+
+        fun makeField(vararg i: Int) = i.sumOf { 1L shl it }
+        assertEquals("-0-4-6-9+1+3+11", describe(makeField(0, 4, 6, 9), makeField(1, 3, 11)))
+        assertEquals("-1-5-9+6+8", describe(makeField(0, 1, 3, 4, 5, 9), makeField(0, 3, 4, 6, 8)))
+    }
 }