Added audio focus entry unit test

Also added an audio focus entry copy builder.

Test: atest AudioFocusEntryUnitTest
Bug: 240615622
CTS-Coverage-Bug: 260005572
Change-Id: I021a6e70de1447fb6dc4d96683f35a0d80c7b194
Merged-In: I021a6e70de1447fb6dc4d96683f35a0d80c7b194
(cherry picked from commit 93441f82311af44362d79d35e04eafa7fd8f8d6b)
diff --git a/car-lib-module/api/system-current.txt b/car-lib-module/api/system-current.txt
index 7752531..7f13564 100644
--- a/car-lib-module/api/system-current.txt
+++ b/car-lib-module/api/system-current.txt
@@ -1176,6 +1176,7 @@
   }
 
   public static final class AudioFocusEntry.Builder {
+    ctor public AudioFocusEntry.Builder(@NonNull android.car.oem.AudioFocusEntry);
     ctor public AudioFocusEntry.Builder(@NonNull android.media.AudioFocusInfo, int, int, int);
     method @NonNull public android.car.oem.AudioFocusEntry build();
     method @NonNull public android.car.oem.AudioFocusEntry.Builder setAudioContextId(int);
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index 7752531..7f13564 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -1176,6 +1176,7 @@
   }
 
   public static final class AudioFocusEntry.Builder {
+    ctor public AudioFocusEntry.Builder(@NonNull android.car.oem.AudioFocusEntry);
     ctor public AudioFocusEntry.Builder(@NonNull android.media.AudioFocusInfo, int, int, int);
     method @NonNull public android.car.oem.AudioFocusEntry build();
     method @NonNull public android.car.oem.AudioFocusEntry.Builder setAudioContextId(int);
diff --git a/car-lib/api/test-current.txt b/car-lib/api/test-current.txt
index 988cc50..d8a6034 100644
--- a/car-lib/api/test-current.txt
+++ b/car-lib/api/test-current.txt
@@ -1244,6 +1244,7 @@
   }
 
   @android.car.annotation.ApiRequirements(minCarVersion=android.car.annotation.ApiRequirements.CarVersion.TIRAMISU_1, minPlatformVersion=android.car.annotation.ApiRequirements.PlatformVersion.TIRAMISU_0) public static final class AudioFocusEntry.Builder {
+    ctor public AudioFocusEntry.Builder(@NonNull android.car.oem.AudioFocusEntry);
     ctor public AudioFocusEntry.Builder(@NonNull android.media.AudioFocusInfo, int, int, int);
     method @android.car.annotation.ApiRequirements(minCarVersion=android.car.annotation.ApiRequirements.CarVersion.TIRAMISU_1, minPlatformVersion=android.car.annotation.ApiRequirements.PlatformVersion.TIRAMISU_0) @NonNull public android.car.oem.AudioFocusEntry build();
     method @android.car.annotation.ApiRequirements(minCarVersion=android.car.annotation.ApiRequirements.CarVersion.TIRAMISU_1, minPlatformVersion=android.car.annotation.ApiRequirements.PlatformVersion.TIRAMISU_0) @NonNull public android.car.oem.AudioFocusEntry.Builder setAudioContextId(int);
diff --git a/car-lib/src/android/car/oem/AudioFocusEntry.java b/car-lib/src/android/car/oem/AudioFocusEntry.java
index d61edd1..183a983 100644
--- a/car-lib/src/android/car/oem/AudioFocusEntry.java
+++ b/car-lib/src/android/car/oem/AudioFocusEntry.java
@@ -22,6 +22,10 @@
 import android.media.AudioFocusInfo;
 import android.os.Parcelable;
 
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
 /**
  * Class to encapsulate the focus information of evaluation from a car oem audio focus service
  *
@@ -43,7 +47,8 @@
             int audioContextId,
             int audioVolumeGroupId,
             int focusResult) {
-        mAudioFocusInfo = audioFocusInfo;
+        mAudioFocusInfo = Objects.requireNonNull(audioFocusInfo,
+                "Audio focus info can not be null");
         mAudioContextId = audioContextId;
         mAudioVolumeGroupId = audioVolumeGroupId;
         mAudioFocusResult = focusResult;
@@ -113,7 +118,7 @@
     @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.TIRAMISU_1,
             minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
     public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
-        dest.writeTypedObject(mAudioFocusInfo, flags);
+        mAudioFocusInfo.writeToParcel(dest, flags);
         dest.writeInt(mAudioContextId);
         dest.writeInt(mAudioVolumeGroupId);
         dest.writeInt(mAudioFocusResult);
@@ -128,8 +133,9 @@
 
     /** @hide */
     @SuppressWarnings({"unchecked", "RedundantCast"})
-    private AudioFocusEntry(@NonNull android.os.Parcel in) {
-        AudioFocusInfo audioFocusInfo = in.readTypedObject(AudioFocusInfo.CREATOR);
+    @VisibleForTesting
+    public AudioFocusEntry(@NonNull android.os.Parcel in) {
+        AudioFocusInfo audioFocusInfo = AudioFocusInfo.CREATOR.createFromParcel(in);
         int audioContextId = in.readInt();
         int audioVolumeGroupId = in.readInt();
         int focusResult = in.readInt();
@@ -156,6 +162,34 @@
         }
     };
 
+    @Override
+    @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.TIRAMISU_2,
+            minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof AudioFocusEntry)) {
+            return false;
+        }
+
+        AudioFocusEntry that = (AudioFocusEntry) o;
+
+        return mAudioContextId == that.mAudioContextId
+                && mAudioFocusResult == that.mAudioFocusResult
+                && mAudioVolumeGroupId == that.mAudioVolumeGroupId
+                && mAudioFocusInfo.equals(that.mAudioFocusInfo);
+    }
+
+    @Override
+    @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.TIRAMISU_2,
+            minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
+    public int hashCode() {
+        return Objects.hash(mAudioFocusInfo.hashCode(), mAudioContextId, mAudioFocusResult,
+                mAudioVolumeGroupId);
+    }
+
     /**
      * A builder for {@link AudioFocusEntry}
      */
@@ -171,13 +205,19 @@
 
         private long mBuilderFieldsSet = 0L;
 
+        public Builder(@NonNull AudioFocusEntry entry) {
+            this(Objects.requireNonNull(entry, "Audio focus entry can not be null")
+                            .mAudioFocusInfo, entry.mAudioContextId, entry.mAudioVolumeGroupId,
+                    entry.mAudioFocusResult);
+        }
 
         public Builder(
                 @NonNull AudioFocusInfo audioFocusInfo,
                 int audioContextId,
                 int audioVolumeGroupId,
                 int focusResult) {
-            mAudioFocusInfo = audioFocusInfo;
+            mAudioFocusInfo = Objects.requireNonNull(audioFocusInfo,
+                    "Audio focus info can not be null");
             mAudioContextId = audioContextId;
             mAudioVolumeGroupId = audioVolumeGroupId;
             mAudioFocusResult = focusResult;
@@ -186,10 +226,11 @@
         /** see {@link AudioFocusEntry#getAudioFocusInfo()} */
         @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.TIRAMISU_1,
                 minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
-        public @NonNull Builder setAudioFocusInfo(@NonNull AudioFocusInfo value) {
+        public @NonNull Builder setAudioFocusInfo(@NonNull AudioFocusInfo audioFocusInfo) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x1;
-            mAudioFocusInfo = value;
+            mAudioFocusInfo = Objects.requireNonNull(audioFocusInfo,
+                    "Audio focus info can not be null");
             return this;
         }
 
diff --git a/tests/carservice_unit_test/src/android/car/oem/AudioFocusEntryUnitTest.java b/tests/carservice_unit_test/src/android/car/oem/AudioFocusEntryUnitTest.java
new file mode 100644
index 0000000..ef86bf0
--- /dev/null
+++ b/tests/carservice_unit_test/src/android/car/oem/AudioFocusEntryUnitTest.java
@@ -0,0 +1,198 @@
+/*
+ * 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 android.car.oem;
+
+import static android.media.AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE;
+import static android.media.AudioAttributes.USAGE_MEDIA;
+import static android.os.Build.VERSION.SDK_INT;
+
+import static org.junit.Assert.assertThrows;
+
+import android.car.test.AbstractExpectableTestCase;
+import android.media.AudioAttributes;
+import android.media.AudioFocusInfo;
+import android.media.AudioManager;
+import android.os.Parcel;
+
+import org.junit.Test;
+
+public final class AudioFocusEntryUnitTest extends AbstractExpectableTestCase {
+
+    private static final AudioFocusInfo TEST_AUDIO_FOCUS_INFO = createAudioFocusInfoForMedia();
+    private static final int TEST_PARCEL_FLAGS = 0;
+    private static final int MEDIA_EMPTY_FLAG = 0;
+    private static final int TEST_AUDIO_CONTEXT = 1;
+    private static final int TEST_VOLUME_GROUP_ID = 2;
+    private static final int MEDIA_APP_UID = 100000;
+    private static final String MEDIA_CLIENT_ID = "client-id";
+    private static final String MEDIA_PACKAGE_NAME = "android.car.oem";
+
+    @Test
+    public void build() {
+        AudioFocusEntry entry = new AudioFocusEntry.Builder(TEST_AUDIO_FOCUS_INFO,
+                TEST_AUDIO_CONTEXT, TEST_VOLUME_GROUP_ID, AudioManager.AUDIOFOCUS_GAIN).build();
+
+        expectWithMessage("Audio focus info from builder").that(entry.getAudioFocusInfo())
+                .isEqualTo(TEST_AUDIO_FOCUS_INFO);
+        expectWithMessage("Audio context from builder").that(entry.getAudioContextId())
+                .isEqualTo(TEST_AUDIO_CONTEXT);
+        expectWithMessage("Volume group id from builder").that(entry.getAudioVolumeGroupId())
+                .isEqualTo(TEST_VOLUME_GROUP_ID);
+        expectWithMessage("Audio focus results from builder").that(entry.getAudioFocusResult())
+                .isEqualTo(AudioManager.AUDIOFOCUS_GAIN);
+    }
+
+    @Test
+    public void writeToParcel() {
+        Parcel parcel = Parcel.obtain();
+
+        AudioFocusEntry entry = createAndWriteEntryToParcel(parcel);
+
+        expectWithMessage("Car volume entry from parcel")
+                .that(new AudioFocusEntry(parcel)).isEqualTo(entry);
+    }
+
+    @Test
+    public void createFromParcel() {
+        Parcel parcel = Parcel.obtain();
+
+        AudioFocusEntry entry = createAndWriteEntryToParcel(parcel);
+
+        expectWithMessage("Car volume entry created from parcel")
+                .that(AudioFocusEntry.CREATOR.createFromParcel(parcel)).isEqualTo(entry);
+    }
+
+    @Test
+    public void setAudioFocusEntry() {
+        AudioFocusInfo testSecondInfo = createAudioFocusInfo(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE);
+
+        AudioFocusEntry entry = new AudioFocusEntry.Builder(TEST_AUDIO_FOCUS_INFO,
+                TEST_AUDIO_CONTEXT, TEST_VOLUME_GROUP_ID, AudioManager.AUDIOFOCUS_GAIN)
+                .setAudioFocusInfo(testSecondInfo).build();
+
+        expectWithMessage("Second audio focus entry from builder").that(entry.getAudioFocusInfo())
+                .isEqualTo(testSecondInfo);
+    }
+
+    @Test
+    public void setAudioFocusEntry_withNullInfo_fails() {
+        NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+                new AudioFocusEntry.Builder(TEST_AUDIO_FOCUS_INFO,
+                        TEST_AUDIO_CONTEXT, TEST_VOLUME_GROUP_ID, AudioManager.AUDIOFOCUS_GAIN)
+                        .setAudioFocusInfo(/* audioFocusInfo= */ null)
+        );
+
+        expectWithMessage("Null audio focus name exception")
+                .that(thrown).hasMessageThat().contains("Audio focus info");
+    }
+
+    @Test
+    public void setAudioContextId() {
+        int testAudioContext = 10;
+
+        AudioFocusEntry entry = new AudioFocusEntry.Builder(TEST_AUDIO_FOCUS_INFO,
+                TEST_AUDIO_CONTEXT, TEST_VOLUME_GROUP_ID, AudioManager.AUDIOFOCUS_GAIN)
+                .setAudioContextId(testAudioContext).build();
+
+        expectWithMessage("Set audio context from builder").that(entry.getAudioContextId())
+                .isEqualTo(testAudioContext);
+    }
+
+    @Test
+    public void setAudioVolumeGroupId() {
+        int testVolumeGroupId = 6;
+        AudioFocusEntry entry = new AudioFocusEntry.Builder(TEST_AUDIO_FOCUS_INFO,
+                TEST_AUDIO_CONTEXT, TEST_VOLUME_GROUP_ID, AudioManager.AUDIOFOCUS_GAIN)
+                .setAudioVolumeGroupId(testVolumeGroupId).build();
+
+        expectWithMessage("Set audio volume group from builder")
+                .that(entry.getAudioVolumeGroupId()).isEqualTo(testVolumeGroupId);
+    }
+
+    @Test
+    public void setAudioFocusResult() {
+        AudioFocusEntry entry = new AudioFocusEntry.Builder(TEST_AUDIO_FOCUS_INFO,
+                TEST_AUDIO_CONTEXT, TEST_VOLUME_GROUP_ID, AudioManager.AUDIOFOCUS_GAIN)
+                .setAudioFocusResult(AudioManager.AUDIOFOCUS_LOSS).build();
+
+        expectWithMessage("Set audio focus results from builder")
+                .that(entry.getAudioFocusResult()).isEqualTo(AudioManager.AUDIOFOCUS_LOSS);
+    }
+
+    @Test
+    public void builder_withReuse_fails() {
+        AudioFocusEntry.Builder builder = new AudioFocusEntry.Builder(TEST_AUDIO_FOCUS_INFO,
+                TEST_AUDIO_CONTEXT, TEST_VOLUME_GROUP_ID, AudioManager.AUDIOFOCUS_GAIN)
+                .setAudioFocusResult(AudioManager.AUDIOFOCUS_LOSS);
+        builder.build();
+
+        IllegalStateException thrown = assertThrows(IllegalStateException.class, () ->
+                builder.build()
+        );
+
+        expectWithMessage("Reuse builder exception")
+                .that(thrown).hasMessageThat().contains("should not be reused");
+    }
+
+    @Test
+    public void builder_withAudioFocusEntry() {
+        AudioFocusEntry testEntry = new AudioFocusEntry.Builder(TEST_AUDIO_FOCUS_INFO,
+                TEST_AUDIO_CONTEXT, TEST_VOLUME_GROUP_ID, AudioManager.AUDIOFOCUS_GAIN).build();
+
+        AudioFocusEntry entry = new AudioFocusEntry.Builder(testEntry).build();
+
+        expectWithMessage("Audio focus info from copy builder")
+                .that(entry.getAudioFocusInfo()).isEqualTo(TEST_AUDIO_FOCUS_INFO);
+        expectWithMessage("Audio context from copy builder").that(entry.getAudioContextId())
+                .isEqualTo(TEST_AUDIO_CONTEXT);
+        expectWithMessage("Volume group id from copy builder")
+                .that(entry.getAudioVolumeGroupId()).isEqualTo(TEST_VOLUME_GROUP_ID);
+        expectWithMessage("Audio focus results from copy builder")
+                .that(entry.getAudioFocusResult()).isEqualTo(AudioManager.AUDIOFOCUS_GAIN);
+    }
+
+    @Test
+    public void builder_withNullEntry_fails() {
+        NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+                new AudioFocusEntry.Builder(/* entry= */ null)
+        );
+
+        expectWithMessage("Null audio focus name exception")
+                .that(thrown).hasMessageThat().contains("Audio focus entry");
+    }
+
+    private static AudioFocusInfo createAudioFocusInfoForMedia() {
+        return createAudioFocusInfo(USAGE_MEDIA);
+    }
+
+    private static AudioFocusInfo createAudioFocusInfo(int usage) {
+        AudioAttributes.Builder builder = new AudioAttributes.Builder();
+        builder.setUsage(usage);
+
+        return new AudioFocusInfo(builder.build(), MEDIA_APP_UID, MEDIA_CLIENT_ID,
+                MEDIA_PACKAGE_NAME, AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_LOSS,
+                MEDIA_EMPTY_FLAG, SDK_INT);
+    }
+
+    private AudioFocusEntry createAndWriteEntryToParcel(Parcel parcel) {
+        AudioFocusEntry entry = new AudioFocusEntry.Builder(TEST_AUDIO_FOCUS_INFO,
+                TEST_AUDIO_CONTEXT, TEST_VOLUME_GROUP_ID, AudioManager.AUDIOFOCUS_GAIN).build();
+        entry.writeToParcel(parcel, TEST_PARCEL_FLAGS);
+        parcel.setDataPosition(/* position= */ 0);
+        return entry;
+    }
+}