Add 'Recent Conversations' to the conversation page

Test: atest
Bug: 171191376
Change-Id: Id7208312dff0cc022c2f16b3872fae1a9dc09ed7
diff --git a/res/drawable/ic_clear.xml b/res/drawable/ic_clear.xml
index 224425f..b2065f4 100644
--- a/res/drawable/ic_clear.xml
+++ b/res/drawable/ic_clear.xml
@@ -18,7 +18,8 @@
     android:viewportWidth="24"
     android:viewportHeight="24"
     android:width="24dp"
-    android:height="24dp">
+    android:height="24dp"
+    android:tint="?android:attr/colorControlNormal">
     <path
         android:pathData="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12Z"
         android:fillColor="#FFFFFF" />
diff --git a/res/layout/conversations_clear_recents.xml b/res/layout/conversations_clear_recents.xml
new file mode 100644
index 0000000..b790439
--- /dev/null
+++ b/res/layout/conversations_clear_recents.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2020 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"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+    <Button
+        android:id="@+id/conversation_settings_clear_recents"
+        style="@style/ActionPrimaryButton"
+        android:layout_marginStart="@dimen/screen_margin_sides"
+        android:layout_marginEnd="@dimen/description_margin_sides"
+        android:layout_marginTop="@dimen/zen_mode_settings_button_margin_vertical"
+        android:layout_marginBottom="@dimen/zen_mode_settings_button_margin_vertical"
+        android:text="@string/conversation_settings_clear_recents"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/preference_widget_clear.xml b/res/layout/preference_widget_clear.xml
new file mode 100644
index 0000000..9703eba
--- /dev/null
+++ b/res/layout/preference_widget_clear.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2020 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.
+  -->
+
+<!-- Settings button -->
+<ImageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/clear_button"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center"
+    android:paddingStart="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:background="?android:attr/selectableItemBackground"
+    android:scaleType="center"
+    android:src="@drawable/ic_clear"
+    android:contentDescription="@string/clear" />
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c3e5d81..ea27114 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -8497,11 +8497,20 @@
     <string name="important_conversations_summary">Show at top of conversation section</string>
 
     <!-- [CHAR LIMIT=100] preference category title -->
-    <string name="other_conversations">Other conversations</string>
+    <string name="other_conversations">Non-priority conversations</string>
 
     <!-- summary for other conversations list -->
     <string name="other_conversations_summary">Conversations you\u2019ve made changes to</string>
 
+    <!-- [CHAR LIMIT=100] preference category title -->
+    <string name="recent_conversations">Recent conversations</string>
+
+    <!-- [CHAR LIMIT=20] button title -->
+    <string name="conversation_settings_clear_recents">Clear recents</string>
+
+    <!-- a11y string -->
+    <string name="clear">Clear</string>
+
     <!-- [CHAR LIMIT=100] Setting to automatically bubble all notifications from favorite conversations -->
     <string name="important_bubble">Bubble priority conversations</string>
 
diff --git a/res/xml/conversation_list_settings.xml b/res/xml/conversation_list_settings.xml
index d528bd7..86b4f81 100644
--- a/res/xml/conversation_list_settings.xml
+++ b/res/xml/conversation_list_settings.xml
@@ -43,12 +43,18 @@
         </PreferenceCategory>
     </PreferenceCategory>
 
-
-    <!--Other conversations added here -->
+    <!-- Non-priority modified conversations added here -->
     <PreferenceCategory
         android:title="@string/other_conversations"
         android:key="other_conversations"
         settings:allowDividerAbove="true"
         settings:allowDividerBelow="false" />
 
+    <!-- Recent conversations added here -->
+    <PreferenceCategory
+        android:title="@string/recent_conversations"
+        android:key="recent_conversations"
+        settings:allowDividerAbove="true"
+        settings:allowDividerBelow="false" />
+
 </PreferenceScreen>
diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java
index e23e4a5..c80ee97 100644
--- a/src/com/android/settings/notification/NotificationBackend.java
+++ b/src/com/android/settings/notification/NotificationBackend.java
@@ -541,6 +541,15 @@
         }
     }
 
+    public void createConversationNotificationChannel(String pkg, int uid,
+            NotificationChannel parent, String conversationId) {
+        try {
+            sINM.createConversationNotificationChannelForPackage(pkg, uid, parent, conversationId);
+        } catch (Exception e) {
+            Log.w(TAG, "Error calling NoMan", e);
+        }
+    }
+
     public ShortcutInfo getConversationInfo(Context context, String pkg, int uid, String id) {
         LauncherApps la = context.getSystemService(LauncherApps.class);
 
diff --git a/src/com/android/settings/notification/app/ConversationListSettings.java b/src/com/android/settings/notification/app/ConversationListSettings.java
index 8f232ca..83fc072 100644
--- a/src/com/android/settings/notification/app/ConversationListSettings.java
+++ b/src/com/android/settings/notification/app/ConversationListSettings.java
@@ -16,8 +16,10 @@
 
 package com.android.settings.notification.app;
 
+import android.app.people.IPeopleManager;
 import android.app.settings.SettingsEnums;
 import android.content.Context;
+import android.os.ServiceManager;
 import android.util.Log;
 
 import com.android.settings.R;
@@ -33,8 +35,15 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     NotificationBackend mBackend = new NotificationBackend();
+    IPeopleManager mPs;
+
     protected List<AbstractPreferenceController> mControllers = new ArrayList<>();
 
+    public ConversationListSettings() {
+        mPs = IPeopleManager.Stub.asInterface(
+                ServiceManager.getService(Context.PEOPLE_SERVICE));
+    }
+
     @Override
     public int getMetricsCategory() {
         return SettingsEnums.NOTIFICATION_CONVERSATION_LIST_SETTINGS;
@@ -53,9 +62,10 @@
     @Override
     protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
         mControllers = new ArrayList<>();
-        mControllers.add(new NoConversationsPreferenceController(context, mBackend));
+        mControllers.add(new NoConversationsPreferenceController(context, mBackend, mPs));
         mControllers.add(new PriorityConversationsPreferenceController(context, mBackend));
         mControllers.add(new AllConversationsPreferenceController(context, mBackend));
+        mControllers.add(new RecentConversationsPreferenceController(context, mBackend, mPs));
         return new ArrayList<>(mControllers);
     }
 }
diff --git a/src/com/android/settings/notification/app/NoConversationsPreferenceController.java b/src/com/android/settings/notification/app/NoConversationsPreferenceController.java
index 40faadc..475d90d 100644
--- a/src/com/android/settings/notification/app/NoConversationsPreferenceController.java
+++ b/src/com/android/settings/notification/app/NoConversationsPreferenceController.java
@@ -16,9 +16,12 @@
 
 package com.android.settings.notification.app;
 
+import android.app.people.IPeopleManager;
 import android.content.Context;
 import android.os.AsyncTask;
+import android.os.RemoteException;
 import android.service.notification.ConversationChannelWrapper;
+import android.util.Log;
 import android.view.View;
 
 import androidx.preference.Preference;
@@ -27,17 +30,18 @@
 import com.android.settings.notification.NotificationBackend;
 import com.android.settingslib.widget.LayoutPreference;
 
-import java.util.List;
-
 public class NoConversationsPreferenceController extends ConversationListPreferenceController {
 
+    private static String TAG = "NoConversationsPC";
     private static final String KEY = "no_conversations";
 
-    private List<ConversationChannelWrapper> mConversations;
+    private IPeopleManager mPs;
+    private int mConversationCount = 0;
 
     public NoConversationsPreferenceController(Context context,
-            NotificationBackend backend) {
+            NotificationBackend backend, IPeopleManager ps) {
         super(context, backend);
+        mPs = ps;
     }
 
     @Override
@@ -67,7 +71,12 @@
         new AsyncTask<Void, Void, Void>() {
             @Override
             protected Void doInBackground(Void... unused) {
-                mConversations = mBackend.getConversations(false).getList();
+                mConversationCount = mBackend.getConversations(false).getList().size();
+                try {
+                    mConversationCount += mPs.getRecentConversations().getList().size();
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Error calling PS", e);
+                }
                 return null;
             }
 
@@ -76,9 +85,9 @@
                 if (mContext == null) {
                     return;
                 }
-                pref.findViewById(R.id.onboarding).setVisibility(mConversations.size() == 0
+                pref.findViewById(R.id.onboarding).setVisibility(mConversationCount == 0
                         ? View.VISIBLE : View.GONE);
-                preference.setVisible(mConversations.size() == 0);
+                preference.setVisible(mConversationCount == 0);
             }
         }.execute();
     }
diff --git a/src/com/android/settings/notification/app/RecentConversationPreference.java b/src/com/android/settings/notification/app/RecentConversationPreference.java
new file mode 100644
index 0000000..49e2c02
--- /dev/null
+++ b/src/com/android/settings/notification/app/RecentConversationPreference.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2020 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.settings.notification.app;
+
+import android.content.Context;
+import android.view.View;
+
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.R;
+import com.android.settingslib.TwoTargetPreference;
+
+import com.google.common.annotations.VisibleForTesting;
+
+public class RecentConversationPreference extends TwoTargetPreference {
+
+    private OnClearClickListener mOnClearClickListener;
+
+    private View mClearView;
+
+    public interface OnClearClickListener {
+        void onClear();
+    }
+
+    public RecentConversationPreference(Context context) {
+        super(context);
+    }
+
+    public void setOnClearClickListener(
+            OnClearClickListener onClearClickListener) {
+        mOnClearClickListener = onClearClickListener;
+    }
+
+    @VisibleForTesting
+    View getClearView() {
+        return mClearView;
+    }
+
+    @Override
+    protected int getSecondTargetResId() {
+        return R.layout.preference_widget_clear;
+    }
+
+    @VisibleForTesting
+    int getClearId() {
+        return R.id.clear_button;
+    }
+
+    @VisibleForTesting
+    boolean hasClearListener() {
+        return mOnClearClickListener != null;
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder view) {
+        super.onBindViewHolder(view);
+        final View widgetFrame = view.findViewById(android.R.id.widget_frame);
+        widgetFrame.setVisibility(mOnClearClickListener != null ? View.VISIBLE : View.GONE);
+        mClearView = view.findViewById(getClearId());
+        mClearView.setOnClickListener(v -> {
+            if (mOnClearClickListener != null) {
+                mOnClearClickListener.onClear();
+            }
+        });
+    }
+
+}
diff --git a/src/com/android/settings/notification/app/RecentConversationsPreferenceController.java b/src/com/android/settings/notification/app/RecentConversationsPreferenceController.java
new file mode 100644
index 0000000..8be6016
--- /dev/null
+++ b/src/com/android/settings/notification/app/RecentConversationsPreferenceController.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2020 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.settings.notification.app;
+
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+
+import android.app.people.ConversationChannel;
+import android.app.people.IPeopleManager;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.content.pm.ShortcutInfo;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Slog;
+import android.widget.Button;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceGroup;
+
+import com.android.settings.R;
+import com.android.settings.applications.AppInfoBase;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.notification.NotificationBackend;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.widget.LayoutPreference;
+
+import java.text.Collator;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class RecentConversationsPreferenceController extends AbstractPreferenceController {
+
+    private static final String TAG = "RecentConversationsPC";
+    private static final String KEY = "recent_conversations";
+    private List<ConversationChannel> mConversations;
+    private final IPeopleManager mPs;
+    private final NotificationBackend mBackend;
+
+    public RecentConversationsPreferenceController(Context context, NotificationBackend backend,
+            IPeopleManager ps) {
+        super(context);
+        mBackend = backend;
+        mPs = ps;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    Preference getClearAll(PreferenceGroup parent) {
+        LayoutPreference pref = new LayoutPreference(
+                mContext, R.layout.conversations_clear_recents);
+        pref.setOrder(1);
+        Button button = pref.findViewById(R.id.conversation_settings_clear_recents);
+        button.setOnClickListener(v -> {
+            try {
+                mPs.removeAllRecentConversations();
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Could not clear recents", e);
+            }
+            updateState(parent);
+        });
+        return pref;
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        PreferenceCategory pref = (PreferenceCategory) preference;
+        // Load conversations
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... unused) {
+                try {
+                    mConversations = mPs.getRecentConversations().getList();
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Could get recents", e);
+                }
+                Collections.sort(mConversations, mConversationComparator);
+                return null;
+            }
+
+            @Override
+            protected void onPostExecute(Void unused) {
+                if (mContext == null) {
+                    return;
+                }
+                populateList(mConversations, pref);
+            }
+        }.execute();
+
+    }
+
+    protected void populateList(List<ConversationChannel> conversations,
+            PreferenceGroup containerGroup) {
+        containerGroup.removeAll();
+        if (conversations != null) {
+            populateConversations(conversations, containerGroup);
+        }
+
+        if (containerGroup.getPreferenceCount() == 0) {
+            containerGroup.setVisible(false);
+        } else {
+            containerGroup.setVisible(true);
+            Preference clearAll = getClearAll(containerGroup);
+            if (clearAll != null) {
+                containerGroup.addPreference(clearAll);
+            }
+        }
+    }
+
+    protected void populateConversations(List<ConversationChannel> conversations,
+            PreferenceGroup containerGroup) {
+        int order = 100;
+        for (ConversationChannel conversation : conversations) {
+            if (conversation.getParentNotificationChannel().getImportance() == IMPORTANCE_NONE
+                    || (conversation.getParentNotificationChannelGroup() != null
+                    && conversation.getParentNotificationChannelGroup().isBlocked())) {
+                continue;
+            }
+            containerGroup.addPreference(
+                    createConversationPref(containerGroup, conversation, order++));
+        }
+    }
+
+    protected Preference createConversationPref(PreferenceGroup parent,
+            final ConversationChannel conversation, int order) {
+        final String pkg = conversation.getShortcutInfo().getPackage();
+        final int uid = conversation.getUid();
+        final String conversationId = conversation.getShortcutInfo().getId();
+        RecentConversationPreference pref = new RecentConversationPreference(mContext);
+
+        if (!conversation.hasActiveNotifications()) {
+            pref.setOnClearClickListener(() -> {
+                try {
+                    mPs.removeRecentConversation(pkg, UserHandle.getUserId(uid), conversationId);
+                    parent.removePreference(pref);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Could not clear recent", e);
+                }
+            });
+        }
+        pref.setOrder(order);
+
+        pref.setTitle(getTitle(conversation));
+        pref.setSummary(getSummary(conversation));
+        pref.setIcon(mBackend.getConversationDrawable(mContext, conversation.getShortcutInfo(),
+                pkg, uid, false));
+        pref.setKey(conversation.getParentNotificationChannel().getId()
+                + ":" + conversationId);
+        pref.setOnPreferenceClickListener(preference -> {
+            mBackend.createConversationNotificationChannel(
+                    pkg, uid,
+                    conversation.getParentNotificationChannel(),
+                    conversationId);
+            getSubSettingLauncher(conversation, pref.getTitle()).launch();
+            return true;
+        });
+
+        return pref;
+    }
+
+    CharSequence getSummary(ConversationChannel conversation) {
+        return conversation.getParentNotificationChannelGroup() == null
+                ? conversation.getParentNotificationChannel().getName()
+                : mContext.getString(R.string.notification_conversation_summary,
+                        conversation.getParentNotificationChannel().getName(),
+                        conversation.getParentNotificationChannelGroup().getName());
+    }
+
+    CharSequence getTitle(ConversationChannel conversation) {
+        ShortcutInfo si = conversation.getShortcutInfo();
+        return si.getLabel();
+    }
+
+    SubSettingLauncher getSubSettingLauncher(ConversationChannel conversation,
+            CharSequence title) {
+        Bundle channelArgs = new Bundle();
+        channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, conversation.getUid());
+        channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME,
+                conversation.getShortcutInfo().getPackage());
+        channelArgs.putString(Settings.EXTRA_CHANNEL_ID,
+                conversation.getParentNotificationChannel().getId());
+        channelArgs.putString(Settings.EXTRA_CONVERSATION_ID,
+                conversation.getShortcutInfo().getId());
+
+        return new SubSettingLauncher(mContext)
+                .setDestination(ChannelNotificationSettings.class.getName())
+                .setArguments(channelArgs)
+                .setExtras(channelArgs)
+                .setUserHandle(UserHandle.getUserHandleForUid(conversation.getUid()))
+                .setTitleText(title)
+                .setSourceMetricsCategory(SettingsEnums.NOTIFICATION_CONVERSATION_LIST_SETTINGS);
+    }
+
+    protected Comparator<ConversationChannel> mConversationComparator =
+            new Comparator<ConversationChannel>() {
+                private final Collator sCollator = Collator.getInstance();
+                @Override
+                public int compare(ConversationChannel o1, ConversationChannel o2) {
+                    int labelComparison = sCollator.compare(o1.getShortcutInfo().getLabel(),
+                            o2.getShortcutInfo().getLabel());
+
+                    if (labelComparison == 0) {
+                        return o1.getParentNotificationChannel().getId().compareTo(
+                                o2.getParentNotificationChannel().getId());
+                    }
+
+                    return labelComparison;
+                }
+            };
+}
diff --git a/tests/robotests/src/com/android/settings/notification/app/RecentConversationsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/app/RecentConversationsPreferenceControllerTest.java
new file mode 100644
index 0000000..660b4e1
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/app/RecentConversationsPreferenceControllerTest.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2020 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.settings.notification.app;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.app.people.ConversationChannel;
+import android.app.people.IPeopleManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.applications.AppInfoBase;
+import com.android.settings.notification.NotificationBackend;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowApplication;
+
+import java.util.ArrayList;
+
+@RunWith(RobolectricTestRunner.class)
+public class RecentConversationsPreferenceControllerTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    @Mock
+    private NotificationBackend mBackend;
+    @Mock
+    private IPeopleManager mPs;
+
+    private RecentConversationsPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowApplication shadowApplication = ShadowApplication.getInstance();
+        mContext = RuntimeEnvironment.application;
+        mController = new RecentConversationsPreferenceController(mContext, mBackend, mPs);
+    }
+
+    @Test
+    public void isAvailable() {
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void testPopulateList_hideIfNoConversations() {
+        PreferenceCategory outerContainer = mock(PreferenceCategory.class);
+
+        mController.populateList(new ArrayList<>(), outerContainer);
+
+        verify(outerContainer).setVisible(false);
+        verify(outerContainer, never()).addPreference(any());
+    }
+
+    @Test
+    public void testPopulateList_validConversations() {
+        final PreferenceManager preferenceManager = new PreferenceManager(mContext);
+        PreferenceScreen ps = preferenceManager.createPreferenceScreen(mContext);
+        PreferenceCategory outerContainer = spy(new PreferenceCategory(mContext));
+        ps.addPreference(outerContainer);
+
+        ConversationChannel ccw = new ConversationChannel(mock(ShortcutInfo.class), 6,
+        new NotificationChannel("hi", "hi", 4),
+        new NotificationChannelGroup("hi", "hi"), 7,
+        true);
+
+        ArrayList<ConversationChannel> list = new ArrayList<>();
+        list.add(ccw);
+
+        mController.populateList(list, outerContainer);
+        // one for the preference, one for the button ro clear all
+        verify(outerContainer, times(2)).addPreference(any());
+    }
+
+    @Test
+    public void populateConversations_blocked() {
+        PreferenceCategory container = mock(PreferenceCategory.class);
+
+        ConversationChannel ccw = new ConversationChannel(mock(ShortcutInfo.class), 6,
+                new NotificationChannel("hi", "hi", 4),
+                new NotificationChannelGroup("hi", "hi"), 7,
+                true);
+
+        ConversationChannel ccw2 = new ConversationChannel(mock(ShortcutInfo.class), 6,
+                new NotificationChannel("hi", "hi", 0),
+                new NotificationChannelGroup("hi", "hi"), 7,
+                true);
+
+        NotificationChannelGroup blockedGroup = new NotificationChannelGroup("hi", "hi");
+        blockedGroup.setBlocked(true);
+        ConversationChannel ccw3 = new ConversationChannel(mock(ShortcutInfo.class), 6,
+                new NotificationChannel("hi", "hi", 4),
+                blockedGroup, 7,
+                true);
+
+        ArrayList<ConversationChannel> list = new ArrayList<>();
+        list.add(ccw);
+        list.add(ccw2);
+        list.add(ccw3);
+
+        mController.populateConversations(list, container);
+
+        verify(container, times(1)).addPreference(any());
+    }
+
+    @Test
+    public void getSummary_withGroup() {
+        ShortcutInfo si = mock(ShortcutInfo.class);
+        when(si.getLabel()).thenReturn("person");
+        ConversationChannel ccw = new ConversationChannel(mock(ShortcutInfo.class), 6,
+                new NotificationChannel("hi", "channel", 4),
+                new NotificationChannelGroup("hi", "group"), 7,
+                true);
+
+        assertThat(mController.getSummary(ccw).toString()).contains(
+                ccw.getParentNotificationChannelGroup().getName());
+        assertThat(mController.getSummary(ccw).toString()).contains(
+                ccw.getParentNotificationChannel().getName());
+    }
+
+    @Test
+    public void getSummary_noGroup() {
+        ShortcutInfo si = mock(ShortcutInfo.class);
+        when(si.getLabel()).thenReturn("person");
+        ConversationChannel ccw = new ConversationChannel(mock(ShortcutInfo.class), 6,
+                new NotificationChannel("hi", "channel", 4),
+                null, 7,
+                true);
+
+        assertThat(mController.getSummary(ccw).toString()).isEqualTo(
+                ccw.getParentNotificationChannel().getName());
+    }
+
+    @Test
+    public void getTitle_withShortcut() {
+        ShortcutInfo si = mock(ShortcutInfo.class);
+        when(si.getLabel()).thenReturn("person");
+        ConversationChannel ccw = new ConversationChannel(si, 6,
+                new NotificationChannel("hi", "channel", 4),
+                new NotificationChannelGroup("hi", "group"), 7,
+                true);
+
+        assertThat(mController.getTitle(ccw).toString()).isEqualTo(si.getLabel());
+    }
+
+    @Test
+    public void testGetSubSettingLauncher() {
+        ShortcutInfo si = mock(ShortcutInfo.class);
+        when(si.getId()).thenReturn("person");
+        when(si.getPackage()).thenReturn("pkg");
+        ConversationChannel ccw = new ConversationChannel(si, 6,
+                new NotificationChannel("hi", "channel", 4),
+                new NotificationChannelGroup("hi", "group"), 7,
+                true);
+
+
+        Intent intent = mController.getSubSettingLauncher(ccw, "title").toIntent();
+
+        Bundle extras = intent.getExtras();
+        assertThat(extras.getString(AppInfoBase.ARG_PACKAGE_NAME)).isEqualTo(
+                ccw.getShortcutInfo().getPackage());
+        assertThat(extras.getInt(AppInfoBase.ARG_PACKAGE_UID)).isEqualTo(ccw.getUid());
+        assertThat(extras.getString(Settings.EXTRA_CHANNEL_ID)).isEqualTo(
+                ccw.getParentNotificationChannel().getId());
+        assertThat(extras.getString(Settings.EXTRA_CONVERSATION_ID)).isEqualTo(
+                ccw.getShortcutInfo().getId());
+    }
+
+    @Test
+    public void testCreatesChannel() {
+        ShortcutInfo si = mock(ShortcutInfo.class);
+        when(si.getId()).thenReturn("person");
+        when(si.getPackage()).thenReturn("pkg");
+        ConversationChannel ccw = new ConversationChannel(si, 6,
+                new NotificationChannel("hi", "channel", 4),
+                new NotificationChannelGroup("hi", "group"), 7,
+                true);
+
+        Preference pref = mController.createConversationPref(new PreferenceCategory(mContext),
+                ccw, 100);
+        try {
+            pref.performClick();
+        } catch (RuntimeException e) {
+            // expected since it tries to launch an activity
+        }
+        verify(mBackend).createConversationNotificationChannel(
+                si.getPackage(), ccw.getUid(), ccw.getParentNotificationChannel(), si.getId());
+    }
+
+    @Test
+    public void testRemoveConversation() throws Exception {
+        ShortcutInfo si = mock(ShortcutInfo.class);
+        when(si.getId()).thenReturn("person");
+        when(si.getPackage()).thenReturn("pkg");
+        ConversationChannel ccw = new ConversationChannel(si, 6,
+                new NotificationChannel("hi", "channel", 4),
+                new NotificationChannelGroup("hi", "group"), 7,
+                false);
+
+        RecentConversationPreference pref =
+                (RecentConversationPreference) mController.createConversationPref(
+                        new PreferenceCategory(mContext), ccw, 100);
+        final View view = View.inflate(mContext, pref.getLayoutResource(), null);
+        PreferenceViewHolder holder = spy(PreferenceViewHolder.createInstanceForTests(view));
+        View delete = View.inflate(mContext, pref.getSecondTargetResId(), null);
+        when(holder.findViewById(pref.getClearId())).thenReturn(delete);
+
+        pref.onBindViewHolder(holder);
+        pref.getClearView().performClick();
+
+        verify(mPs).removeRecentConversation(
+                si.getPackage(), UserHandle.getUserId(ccw.getUid()), si.getId());
+    }
+
+    @Test
+    public void testNonremoveableConversation() throws Exception {
+        ShortcutInfo si = mock(ShortcutInfo.class);
+        when(si.getId()).thenReturn("person");
+        when(si.getPackage()).thenReturn("pkg");
+        ConversationChannel ccw = new ConversationChannel(si, 6,
+                new NotificationChannel("hi", "channel", 4),
+                new NotificationChannelGroup("hi", "group"), 7,
+                true);
+
+        RecentConversationPreference pref =
+                (RecentConversationPreference) mController.createConversationPref(
+                        new PreferenceCategory(mContext), ccw, 100);
+        assertThat(pref.hasClearListener()).isFalse();
+    }
+}