Make Safety groups expandable and collapsable

Test: manual

Bug: 241354578
Change-Id: Ice90b37a7cd534277c5ae763f001805fa772dc37
diff --git a/PermissionController/res/drawable-v33/ic_privacy.xml b/PermissionController/res/drawable-v33/ic_privacy.xml
index 60194f4..3e79456 100644
--- a/PermissionController/res/drawable-v33/ic_privacy.xml
+++ b/PermissionController/res/drawable-v33/ic_privacy.xml
@@ -20,8 +20,7 @@
     android:height="20dp"
     android:viewportWidth="16"
     android:viewportHeight="20">
-    <group>
-        <clip-path android:pathData="M0 0H16V20H0V0Z" />
-        <path android:pathData="M0 0V20H16V0" android:fillColor="?android:attr/textColorSecondary" />
-    </group>
+    <path
+        android:pathData="M8,0L0,3V9.09C0,14.14 3.41,18.85 8,20C12.59,18.85 16,14.14 16,9.09V3L8,0ZM14,9.09C14,13.09 11.45,16.79 8,17.92C4.55,16.79 2,13.1 2,9.09V4.39L8,2.14L14,4.39V9.09ZM8.93,9.77L9.5,13H6.5L7.07,9.77C6.43,9.44 6,8.77 6,8C6,6.9 6.9,6 8,6C9.1,6 10,6.9 10,8C10,8.77 9.57,9.44 8.93,9.77Z"
+        android:fillColor="?android:attr/textColorPrimary"/>
 </vector>
diff --git a/PermissionController/res/drawable-v33/ic_safety_group_expand.xml b/PermissionController/res/drawable-v33/ic_safety_group_expand.xml
new file mode 100644
index 0000000..bf0606d
--- /dev/null
+++ b/PermissionController/res/drawable-v33/ic_safety_group_expand.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M0,12C0,5.373 5.373,0 12,0C18.627,0 24,5.373 24,12C24,18.627 18.627,24 12,24C5.373,24 0,18.627 0,12Z"
+      android:fillColor="?attr/colorSurfaceVariant"/>
+  <path
+      android:pathData="M7.607,9.059L6.667,9.999L12,15.332L17.333,9.999L16.393,9.059L12,13.445"
+      android:fillColor="?android:attr/textColorPrimary"/>
+</vector>
diff --git a/PermissionController/res/layout-v33/preference_collapsed_group_entry.xml b/PermissionController/res/layout-v33/preference_collapsed_group_entry.xml
new file mode 100644
index 0000000..d4ad271
--- /dev/null
+++ b/PermissionController/res/layout-v33/preference_collapsed_group_entry.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ 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
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/SafetyCenterEntry">
+
+    <FrameLayout
+        android:id="@+id/icon_frame"
+        style="@style/SafetyCenterEntryIconFrame">
+        <ImageView
+            android:id="@android:id/icon"
+            style="@style/SafetyCenterEntryIcon"/>
+    </FrameLayout>
+
+    <Space
+        android:id="@+id/empty_space"
+        style="@style/SafetyCenterEntryEmptySpace"/>
+
+    <LinearLayout
+        style="@style/SafetyCenterEntryTextContainer">
+
+        <TextView android:id="@android:id/title"
+            style="@style/SafetyCenterEntryTitle" />
+
+        <TextView android:id="@android:id/summary"
+            style="@style/SafetyCenterEntrySummary" />
+
+    </LinearLayout>
+
+    <ImageView android:id="@+id/chevron_icon"
+        android:layout_height="match_parent"
+        style="@style/SafetyCenterExpandedGroupIcon" />
+</LinearLayout>
diff --git a/PermissionController/res/layout-v33/preference_expanded_group_entry.xml b/PermissionController/res/layout-v33/preference_expanded_group_entry.xml
index 95fea69..f34da0d 100644
--- a/PermissionController/res/layout-v33/preference_expanded_group_entry.xml
+++ b/PermissionController/res/layout-v33/preference_expanded_group_entry.xml
@@ -21,7 +21,6 @@
     <TextView android:id="@android:id/title"
         style="@style/SafetyCenterGroupTitle"/>
 
-    <!-- Preference could place its optional widget here. -->
-    <LinearLayout android:id="@android:id/widget_frame"
-        style="@style/SafetyCenterGroupWidgetFrame"/>
+    <ImageView android:id="@+id/chevron_icon"
+        style="@style/SafetyCenterExpandedGroupIcon" />
 </LinearLayout>
diff --git a/PermissionController/res/layout-v33/preference_expanded_group_widget.xml b/PermissionController/res/layout-v33/preference_expanded_group_widget.xml
deleted file mode 100644
index e76cc59..0000000
--- a/PermissionController/res/layout-v33/preference_expanded_group_widget.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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.
-  -->
-<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/expanded_icon"
-    android:src="@drawable/ic_safety_group_collapse"
-    style="@style/SafetyCenterExpandedGroupIcon"/>
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/CollapsableGroupCardHelper.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/CollapsableGroupCardHelper.kt
new file mode 100644
index 0000000..c2c4b92
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/CollapsableGroupCardHelper.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.permissioncontroller.safetycenter.ui
+
+import android.os.Build
+import android.os.Bundle
+import androidx.annotation.RequiresApi
+import androidx.preference.PreferenceGroup
+
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
+internal class CollapsableGroupCardHelper {
+
+    private val expandedGroups = mutableSetOf<CharSequence>()
+
+    private companion object {
+        private const val EXPANDED_ENTRY_GROUPS_SAVED_INSTANCE_STATE_KEY =
+                "expanded_entry_groups_saved_instance_state_key"
+    }
+
+    fun restoreState(state: Bundle?) {
+        state?.getCharSequenceArray(EXPANDED_ENTRY_GROUPS_SAVED_INSTANCE_STATE_KEY)?.let {
+            expandedGroups.clear()
+            expandedGroups.addAll(it)
+        }
+    }
+
+    fun saveState(outState: Bundle) {
+        outState.putCharSequenceArray(
+                EXPANDED_ENTRY_GROUPS_SAVED_INSTANCE_STATE_KEY,
+                expandedGroups.toTypedArray()
+        )
+    }
+
+    fun collapseGroup(groupId: String) {
+        expandedGroups.remove(groupId)
+    }
+
+    fun expandGroup(groupId: String) {
+        expandedGroups.add(groupId)
+    }
+
+    fun updatePreferenceVisibility(group: PreferenceGroup) {
+        val preferenceCount = group.preferenceCount
+        for (i in 0 until preferenceCount) {
+            when (val preference = group.getPreference(i)) {
+                is SafetyGroupHeaderEntryPreference -> {
+                    val shouldShowExpanded = expandedGroups.contains(preference.groupId)
+                    preference.isVisible = preference.isExpanded == shouldShowExpanded
+                }
+                is SafetyEntryPreference -> {
+                    preference.isVisible = preference.groupId == null ||
+                            expandedGroups.contains(preference.groupId)
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/PositionInCardList.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/PositionInCardList.kt
index fbe9fa3..1b70f45 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/PositionInCardList.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/PositionInCardList.kt
@@ -50,9 +50,9 @@
 
     fun getTopMargin(context: Context): Int =
         when (this) {
-            CARD_START, CARD_START_END ->
+            CARD_START, CARD_START_END, CARD_START_LIST_END ->
                 context.resources.getDimensionPixelSize(R.dimen.safety_center_card_margin)
-            LIST_START, LIST_START_CARD_END ->
+            LIST_START, LIST_START_CARD_END, LIST_START_END ->
                 context.resources.getDimensionPixelSize(R.dimen.safety_center_list_margin)
             else -> 0
         }
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java
index 148340f..f5dbe0f 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java
@@ -78,6 +78,8 @@
 
     private SafetyStatusPreference mSafetyStatusPreference;
     private CollapsableIssuesCardHelper mCollapsableIssuesCardHelper;
+    private final CollapsableGroupCardHelper mCollapsableGroupCardHelper =
+            new CollapsableGroupCardHelper();
     private PreferenceGroup mIssuesGroup;
     private PreferenceGroup mEntriesGroup;
     private PreferenceGroup mStaticEntriesGroup;
@@ -160,6 +162,8 @@
                 mIsQuickSettingsFragment, parsedSafetyCenterIntent.getShouldExpandIssuesGroup());
         mCollapsableIssuesCardHelper.restoreState(savedInstanceState);
 
+        mCollapsableGroupCardHelper.restoreState(savedInstanceState);
+
         mSafetyStatusPreference =
                 requireNonNull(getPreferenceScreen().findPreference(SAFETY_STATUS_KEY));
         // TODO: Use real strings here, or set more sensible defaults in the layout
@@ -217,6 +221,7 @@
     public void onSaveInstanceState(@NonNull Bundle outState) {
         super.onSaveInstanceState(outState);
         mCollapsableIssuesCardHelper.saveState(outState);
+        mCollapsableGroupCardHelper.saveState(outState);
     }
 
     @Override
@@ -315,6 +320,8 @@
                 addGroupEntries(context, group, isFirstElement, isLastElement);
             }
         }
+
+        mCollapsableGroupCardHelper.updatePreferenceVisibility(mEntriesGroup);
     }
 
     private void addTopLevelEntry(
@@ -327,6 +334,7 @@
                         context,
                         getTaskIdForEntry(entry),
                         entry,
+                        /* groupId */ null,
                         PositionInCardList.calculate(isFirstElement, isLastElement),
                         mViewModel));
     }
@@ -336,34 +344,66 @@
             SafetyCenterEntryGroup group,
             boolean isFirstCard,
             boolean isLastCard) {
+        // adding collapsed group entry, which will be visible initially
+        mEntriesGroup.addPreference(
+                new SafetyGroupHeaderEntryPreference(
+                        context,
+                        group,
+                        isFirstCard
+                                ? isLastCard ? PositionInCardList.LIST_START_END
+                                : PositionInCardList.LIST_START_CARD_END
+                                : isLastCard ? PositionInCardList.CARD_START_LIST_END
+                                        : PositionInCardList.CARD_START_END,
+                        /* isExpanded */ false,
+                        this::expandGroup
+                )
+        );
+
+        // adding expanded group entry, which will be hidden initially
         mEntriesGroup.addPreference(
                 new SafetyGroupHeaderEntryPreference(
                         context,
                         group,
                         isFirstCard
                                 ? PositionInCardList.LIST_START
-                                : PositionInCardList.CARD_START));
+                                : PositionInCardList.CARD_START,
+                        /* isExpanded */ true,
+                        this::collapseGroup
+                )
+        );
 
+        // adding group entries, but they are will be hidden initially until group is expanded
         List<SafetyCenterEntry> entries = group.getEntries();
         for (int i = 0, last = entries.size() - 1; i <= last; i++) {
             boolean isCardEnd = i == last;
             boolean isListEnd = isLastCard && isCardEnd;
             PositionInCardList positionInCardList =
                     PositionInCardList.calculate(
-                            /* isListStart= */ false,
+                            /* isListStart */ false,
                             isListEnd,
-                            /* isCardStart= */ false,
+                            /* isCardStart */ false,
                             isCardEnd);
             mEntriesGroup.addPreference(
                     new SafetyEntryPreference(
                             context,
                             getTaskIdForEntry(entries.get(i)),
                             entries.get(i),
+                            group.getId(),
                             positionInCardList,
                             mViewModel));
         }
     }
 
+    private void expandGroup(String groupId) {
+        mCollapsableGroupCardHelper.expandGroup(groupId);
+        mCollapsableGroupCardHelper.updatePreferenceVisibility(mEntriesGroup);
+    }
+
+    private void collapseGroup(String groupId) {
+        mCollapsableGroupCardHelper.collapseGroup(groupId);
+        mCollapsableGroupCardHelper.updatePreferenceVisibility(mEntriesGroup);
+    }
+
     private void updateStaticSafetyEntries(
             Context context, List<SafetyCenterStaticEntryGroup> staticEntryGroups) {
         mStaticEntriesGroup.removeAll();
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyEntryPreference.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyEntryPreference.java
index c52a115..320ddcf 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyEntryPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyEntryPreference.java
@@ -46,11 +46,13 @@
     private final PositionInCardList mPosition;
     private final SafetyCenterEntry mEntry;
     private final SafetyCenterViewModel mViewModel;
+    private final CharSequence mGroupId;
 
     public SafetyEntryPreference(
             Context context,
             int taskId,
             SafetyCenterEntry entry,
+            CharSequence groupId,
             PositionInCardList position,
             SafetyCenterViewModel viewModel) {
         super(context);
@@ -58,12 +60,14 @@
         mEntry = entry;
         mPosition = position;
         mViewModel = viewModel;
+        mGroupId = groupId;
 
         setLayoutResource(R.layout.preference_entry);
         setTitle(entry.getTitle());
         setSummary(entry.getSummary());
 
-        setIcon(selectIconResId(mEntry));
+        setIcon(SeverityIconPicker.selectIconResId(
+                mEntry.getSeverityLevel(), mEntry.getSeverityUnspecifiedIconType()));
 
         PendingIntent pendingIntent = entry.getPendingIntent();
         if (pendingIntent != null) {
@@ -116,7 +120,7 @@
         boolean hideIcon =
                 mEntry.getSeverityLevel() == SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED
                         && mEntry.getSeverityUnspecifiedIconType()
-                                == SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON;
+                        == SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON;
         holder.findViewById(R.id.icon_frame).setVisibility(hideIcon ? View.GONE : View.VISIBLE);
         holder.findViewById(R.id.empty_space).setVisibility(hideIcon ? View.VISIBLE : View.GONE);
         enableOrDisableEntry(holder);
@@ -157,23 +161,6 @@
         }
     }
 
-    private static int selectIconResId(SafetyCenterEntry entry) {
-        switch (entry.getSeverityLevel()) {
-            case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN:
-                return R.drawable.ic_safety_null_state;
-            case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED:
-                return selectSeverityUnspecifiedIconResId(entry);
-            case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK:
-                return R.drawable.ic_safety_info;
-            case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION:
-                return R.drawable.ic_safety_recommendation;
-            case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING:
-                return R.drawable.ic_safety_warn;
-        }
-        Log.e(TAG, String.format("Unexpected SafetyCenterEntry.EntrySeverityLevel: %s", entry));
-        return R.drawable.ic_safety_null_state;
-    }
-
     /** We are doing this because we need some entries to look disabled but still be clickable. */
     private void enableOrDisableEntry(@NonNull PreferenceViewHolder holder) {
         holder.itemView.setEnabled(mEntry.getPendingIntent() != null);
@@ -186,23 +173,15 @@
         }
     }
 
-    private static int selectSeverityUnspecifiedIconResId(SafetyCenterEntry entry) {
-        switch (entry.getSeverityUnspecifiedIconType()) {
-            case SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON:
-                return R.drawable.ic_safety_empty;
-            case SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_PRIVACY:
-                return R.drawable.ic_privacy;
-            case SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION:
-                return R.drawable.ic_safety_null_state;
-        }
-        Log.e(TAG, String.format("Unexpected SafetyCenterEntry.SeverityNoneIconType: %s", entry));
-        return R.drawable.ic_safety_null_state;
+    public CharSequence getGroupId() {
+        return mGroupId;
     }
 
     @Override
     public boolean isSameItem(@NonNull Preference other) {
         return other instanceof SafetyEntryPreference
-                && TextUtils.equals(mEntry.getId(), ((SafetyEntryPreference) other).mEntry.getId());
+                && TextUtils.equals(mEntry.getId(), ((SafetyEntryPreference) other).mEntry.getId())
+                && TextUtils.equals(mGroupId, ((SafetyEntryPreference) other).mGroupId);
     }
 
     @Override
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyGroupHeaderEntryPreference.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyGroupHeaderEntryPreference.java
index e0247d0..1fc52f6 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyGroupHeaderEntryPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyGroupHeaderEntryPreference.java
@@ -24,6 +24,7 @@
 import android.text.TextUtils;
 import android.view.View;
 import android.view.ViewGroup.MarginLayoutParams;
+import android.widget.ImageView;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
@@ -32,33 +33,60 @@
 
 import com.android.permissioncontroller.R;
 
-/** A preference that displays a visual representation of a {@link SafetyCenterEntry}. */
+import java.util.function.Consumer;
+
+/**
+ * A preference that displays a visual representation of a header for
+ * {@link SafetyCenterEntryGroup}.
+ */
 @RequiresApi(TIRAMISU)
 public class SafetyGroupHeaderEntryPreference extends Preference implements ComparablePreference {
 
     private static final String TAG = SafetyGroupHeaderEntryPreference.class.getSimpleName();
 
-    private final String mId;
+    private final SafetyCenterEntryGroup mGroup;
     private final PositionInCardList mPosition;
+    private final boolean mIsExpanded;
 
     public SafetyGroupHeaderEntryPreference(
-            Context context, SafetyCenterEntryGroup group, PositionInCardList position) {
+            Context context,
+            SafetyCenterEntryGroup group,
+            PositionInCardList position,
+            boolean isExpanded,
+            Consumer<String> onClick) {
         super(context);
-        mId = group.getId();
+        mGroup = group;
         mPosition = position;
-        setLayoutResource(R.layout.preference_expanded_group_entry);
-        setWidgetLayoutResource(R.layout.preference_expanded_group_widget);
+        mIsExpanded = isExpanded;
+        setLayoutResource(
+                isExpanded
+                        ? R.layout.preference_expanded_group_entry
+                        : R.layout.preference_collapsed_group_entry);
+
         setTitle(group.getTitle());
 
-        // TODO(b/222126985): make back selectable to return the Ripple effect
-        setSelectable(false);
+        if (!isExpanded) {
+            setSummary(group.getSummary());
+            setIcon(
+                    SeverityIconPicker.selectIconResId(
+                            group.getSeverityLevel(), group.getSeverityUnspecifiedIconType()));
+        }
+
         setOnPreferenceClickListener(
                 unused -> {
-                    // TODO(b/222126985): implement collapsing UX
+                    onClick.accept(group.getId());
                     return true;
                 });
     }
 
+    public String getGroupId() {
+        return mGroup != null ? mGroup.getId() : null;
+    }
+
+    public boolean isExpanded() {
+        return mIsExpanded;
+    }
+
     @Override
     public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
@@ -71,22 +99,38 @@
             holder.itemView.setLayoutParams(params);
         }
 
-        // TODO(b/222126985): show a proper icon based on current state
-        holder.findViewById(R.id.expanded_icon).setVisibility(View.GONE);
+        if (!mIsExpanded) {
+            boolean hideIcon =
+                    mGroup.getSeverityLevel() == SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED
+                            && mGroup.getSeverityUnspecifiedIconType()
+                            == SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON;
+            holder.findViewById(R.id.icon_frame).setVisibility(hideIcon ? View.GONE : View.VISIBLE);
+            holder.findViewById(R.id.empty_space)
+                    .setVisibility(hideIcon ? View.VISIBLE : View.GONE);
+        }
+
+        ImageView chevronIcon = (ImageView) holder.findViewById(R.id.chevron_icon);
+        chevronIcon.setImageResource(
+                mIsExpanded
+                        ? R.drawable.ic_safety_group_collapse
+                        : R.drawable.ic_safety_group_expand);
     }
 
     @Override
     public boolean isSameItem(@NonNull Preference other) {
-        return mId != null
+        return mGroup != null
                 && other instanceof SafetyGroupHeaderEntryPreference
-                && TextUtils.equals(mId, ((SafetyGroupHeaderEntryPreference) other).mId);
+                && TextUtils.equals(
+                getGroupId(), ((SafetyGroupHeaderEntryPreference) other).getGroupId());
     }
 
     @Override
     public boolean hasSameContents(@NonNull Preference other) {
         if (other instanceof SafetyGroupHeaderEntryPreference) {
             SafetyGroupHeaderEntryPreference o = (SafetyGroupHeaderEntryPreference) other;
-            return TextUtils.equals(getTitle(), o.getTitle()) && mPosition == o.mPosition;
+            return TextUtils.equals(getTitle(), o.getTitle())
+                    && mPosition == o.mPosition
+                    && mIsExpanded == o.mIsExpanded;
         }
         return false;
     }
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SeverityIconPicker.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SeverityIconPicker.java
new file mode 100644
index 0000000..3c75309
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SeverityIconPicker.java
@@ -0,0 +1,63 @@
+/*
+ * 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.permissioncontroller.safetycenter.ui;
+
+import android.safetycenter.SafetyCenterEntry;
+import android.util.Log;
+
+import com.android.permissioncontroller.R;
+
+class SeverityIconPicker {
+
+    private static final String TAG = SeverityIconPicker.class.getSimpleName();
+
+    static int selectIconResId(int severityLevel, int severityUnspecifiedIconType) {
+        switch (severityLevel) {
+            case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN:
+                return R.drawable.ic_safety_null_state;
+            case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED:
+                return selectSeverityUnspecifiedIconResId(severityUnspecifiedIconType);
+            case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK:
+                return R.drawable.ic_safety_info;
+            case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION:
+                return R.drawable.ic_safety_recommendation;
+            case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING:
+                return R.drawable.ic_safety_warn;
+        }
+        Log.e(TAG,
+                String.format(
+                        "Unexpected SafetyCenterEntry.EntrySeverityLevel: %s", severityLevel));
+        return R.drawable.ic_safety_null_state;
+    }
+
+    private static int selectSeverityUnspecifiedIconResId(int severityUnspecifiedIconType) {
+        switch (severityUnspecifiedIconType) {
+            case SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON:
+                return R.drawable.ic_safety_empty;
+            case SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_PRIVACY:
+                return R.drawable.ic_privacy;
+            case SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION:
+                return R.drawable.ic_safety_null_state;
+        }
+        Log.e(TAG,
+                String.format(
+                        "Unexpected SafetyCenterEntry.SeverityNoneIconType: %s",
+                        severityUnspecifiedIconType));
+        return R.drawable.ic_safety_null_state;
+    }
+
+}