Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bc2238eb authored by George Lu's avatar George Lu
Browse files

Fix RadioMetadata.hashCode().

ag/7063548 implemented RadioMetadata.hashCode() by calling Bundle.hashCode(),
but that function uses the default implementation that uses the address
of the Bundle. As a result, two RadioMetadata objects that were equal
probably had different hash codes.

RadioMetadata now instead lazily computes its hash code based upon the keys and
values in mBundle.

Fixes: 130750904
Test: atest com.android.server.broadcastradio.hal2.StartProgramListUpdatesFanoutTest
Change-Id: Ia49716594744871831bc3734aea37e0f505897bf
parent 7bdc19ba
Loading
Loading
Loading
Loading
+25 −1
Original line number Diff line number Diff line
@@ -26,6 +26,9 @@ import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseArray;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

/**
@@ -257,9 +260,22 @@ public final class RadioMetadata implements Parcelable {

    private final Bundle mBundle;

    // Lazily computed hash code based upon the contents of mBundle.
    private Integer mHashCode;

    @Override
    public int hashCode() {
        return mBundle.hashCode();
        if (mHashCode == null) {
            List<String> keys = new ArrayList<String>(mBundle.keySet());
            keys.sort(null);
            Object[] objs = new Object[2 * keys.size()];
            for (int i = 0; i < keys.size(); i++) {
                objs[2 * i] = keys.get(i);
                objs[2 * i + 1] = mBundle.get(keys.get(i));
            }
            mHashCode = Arrays.hashCode(objs);
        }
        return mHashCode;
    }

    @Override
@@ -626,6 +642,8 @@ public final class RadioMetadata implements Parcelable {
        String key = getKeyFromNativeKey(nativeKey);
        try {
            putInt(mBundle, key, value);
            // Invalidate mHashCode to force it to be recomputed.
            mHashCode = null;
            return 0;
        } catch (IllegalArgumentException ex) {
            return -1;
@@ -639,6 +657,8 @@ public final class RadioMetadata implements Parcelable {
            return -1;
        }
        mBundle.putString(key, value);
        // Invalidate mHashCode to force it to be recomputed.
        mHashCode = null;
        return 0;
    }

@@ -653,6 +673,8 @@ public final class RadioMetadata implements Parcelable {
            bmp = BitmapFactory.decodeByteArray(value, 0, value.length);
            if (bmp != null) {
                mBundle.putParcelable(key, bmp);
                // Invalidate mHashCode to force it to be recomputed.
                mHashCode = null;
                return 0;
            }
        } catch (Exception e) {
@@ -668,6 +690,8 @@ public final class RadioMetadata implements Parcelable {
        }
        mBundle.putParcelable(key, new RadioMetadata.Clock(
            utcEpochSeconds, timezoneOffsetInMinutes));
        // Invalidate mHashCode to force it to be recomputed.
        mHashCode = null;
        return 0;
    }
}
+1 −59
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@ package com.android.server.broadcastradio.hal2;

import static org.junit.Assert.*;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -41,7 +40,6 @@ import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
@@ -49,7 +47,6 @@ import org.mockito.stubbing.Answer;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Tests for v2 HAL RadioModule.
@@ -293,61 +290,6 @@ public class StartProgramListUpdatesFanoutTest {
        }
        ProgramList.Chunk expectedChunk = new ProgramList.Chunk(purge, true, modifiedSet,
                removedSet);
        verify(clientMock).onProgramListUpdated(argThat(new ChunkMatcher(expectedChunk)));
        verify(clientMock).onProgramListUpdated(expectedChunk);
    }

    // TODO(b/130750904): Remove this class and replace "argThat(new ChunkMatcher(chunk))" with
    // "eq(chunk)".
    //
    // Ideally, this class wouldn't exist, but currently RadioManager.ProgramInfo#hashCode() can
    // return different values for objects that satisfy ProgramInfo#equals(). As a short term
    // workaround, this class performs the O(N^2) comparison between the Chunks' mModified sets.
    //
    // To test if ProgramInfo#hashCode() has been fixed, remove commenting from
    // testProgramInfoHashCode() below.
    private class ChunkMatcher implements ArgumentMatcher<ProgramList.Chunk> {
        private final ProgramList.Chunk mExpected;

        ChunkMatcher(ProgramList.Chunk expected) {
            mExpected = expected;
        }

        @Override
        public boolean matches(ProgramList.Chunk actual) {
            if ((mExpected.isPurge() != actual.isPurge())
                    || (mExpected.isComplete() != actual.isComplete())
                    || (!mExpected.getRemoved().equals(actual.getRemoved()))) {
                return false;
            }
            Set<RadioManager.ProgramInfo> expectedModified = mExpected.getModified();
            Set<RadioManager.ProgramInfo> actualModified = new HashSet<>(actual.getModified());
            if (expectedModified.size() != actualModified.size()) {
                return false;
            }
            for (RadioManager.ProgramInfo expectedInfo : expectedModified) {
                boolean found = false;
                for (RadioManager.ProgramInfo actualInfo : actualModified) {
                    if (expectedInfo.equals(actualInfo)) {
                        found = true;
                        actualModified.remove(actualInfo);
                        break;
                    }
                }
                if (!found) {
                    return false;
                }
            }
            return true;
        }
    }

    // @Test
    // public void testProgramInfoHashCode() {
    //     RadioManager.ProgramInfo info1 = TestUtils.makeProgramInfo(
    //         ProgramSelector.PROGRAM_TYPE_FM, mAmFmIdentifier, 0);
    //     RadioManager.ProgramInfo info2 = TestUtils.makeProgramInfo(
    //         ProgramSelector.PROGRAM_TYPE_FM, mAmFmIdentifier, 0);
    //     assertEquals(info1, info2);
    //     assertEquals(info1.hashCode(), info2.hashCode());
    // }
}