Allow only CarUiListItems in dialogs

We exposed setAdapter(ListAdapter), which allows apps to use
any custom views they want in dialogs. Deprecate those functions,
and add a setAdapter(CarUiListItemAdapter), which only allows approved,
chassis-customizable layouts to be used in dialogs.

Bug: 147614686
Test: Manually
Change-Id: Id31dae8d51cb64390d4c5417ee8861fd5a567018
diff --git a/car-ui-lib/src/com/android/car/ui/AlertDialogBuilder.java b/car-ui-lib/src/com/android/car/ui/AlertDialogBuilder.java
index 76b9dc6..0cbda42 100644
--- a/car-ui-lib/src/com/android/car/ui/AlertDialogBuilder.java
+++ b/car-ui-lib/src/com/android/car/ui/AlertDialogBuilder.java
@@ -36,6 +36,9 @@
 import androidx.annotation.DrawableRes;
 import androidx.annotation.StringRes;
 
+import com.android.car.ui.recyclerview.CarUiListItemAdapter;
+import com.android.car.ui.recyclerview.RecyclerViewToListAdapter;
+
 /**
  * Wrapper for AlertDialog.Builder
  */
@@ -324,14 +327,11 @@
     }
 
     /**
-     * Set a list of items, which are supplied by the given {@link ListAdapter}, to be
-     * displayed in the dialog as the content, you will be notified of the
-     * selected item via the supplied listener.
+     * This was not supposed to be in the Chassis API because it allows custom views.
      *
-     * @param adapter The {@link ListAdapter} to supply the list of items
-     * @param listener The listener that will be called when an item is clicked.
-     * @return This Builder object to allow for chaining of calls to set methods
+     * @deprecated Use {@link #setAdapter(CarUiListItemAdapter)} instead.
      */
+    @Deprecated
     public AlertDialogBuilder setAdapter(final ListAdapter adapter,
             final DialogInterface.OnClickListener listener) {
         mBuilder.setAdapter(adapter, listener);
@@ -339,6 +339,16 @@
     }
 
     /**
+     * Display all the {@link com.android.car.ui.recyclerview.CarUiListItem CarUiListItems} in a
+     * {@link CarUiListItemAdapter}. You should set click listeners on the CarUiListItems as
+     * opposed to a callback in this function.
+     */
+    public AlertDialogBuilder setAdapter(final CarUiListItemAdapter adapter) {
+        mBuilder.setAdapter(new RecyclerViewToListAdapter<>(adapter), null);
+        return this;
+    }
+
+    /**
      * Set a list of items, which are supplied by the given {@link Cursor}, to be
      * displayed in the dialog as the content, you will be notified of the
      * selected item via the supplied listener.
@@ -488,18 +498,11 @@
     }
 
     /**
-     * Set a list of items to be displayed in the dialog as the content, you will be notified of
-     * the selected item via the supplied listener. The list will have a check mark displayed to
-     * the right of the text for the checked item. Clicking on an item in the list will not
-     * dismiss the dialog. Clicking on a button will dismiss the dialog.
+     * This was not supposed to be in the Chassis API because it allows custom views.
      *
-     * @param adapter The {@link ListAdapter} to supply the list of items
-     * @param checkedItem specifies which item is checked. If -1 no items are checked.
-     * @param listener notified when an item on the list is clicked. The dialog will not be
-     * dismissed when an item is clicked. It will only be dismissed if clicked on a
-     * button, if no buttons are supplied it's up to the user to dismiss the dialog.
-     * @return This Builder object to allow for chaining of calls to set methods
+     * @deprecated Use {@link #setAdapter(CarUiListItemAdapter)} instead.
      */
+    @Deprecated
     public AlertDialogBuilder setSingleChoiceItems(ListAdapter adapter, int checkedItem,
             final DialogInterface.OnClickListener listener) {
         mBuilder.setSingleChoiceItems(adapter, checkedItem, listener);
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItemAdapter.java b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItemAdapter.java
index 1b71411..ddc5aec 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItemAdapter.java
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiListItemAdapter.java
@@ -42,7 +42,7 @@
  * <li> Implements {@link CarUiRecyclerView.ItemCap} - defaults to unlimited item count.
  * </ul>
  */
-public class CarUiListItemAdapter extends
+public final class CarUiListItemAdapter extends
         RecyclerView.Adapter<RecyclerView.ViewHolder> implements
         CarUiRecyclerView.ItemCap {
 
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/RecyclerViewToListAdapter.java b/car-ui-lib/src/com/android/car/ui/recyclerview/RecyclerViewToListAdapter.java
new file mode 100644
index 0000000..0ad30e4
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/RecyclerViewToListAdapter.java
@@ -0,0 +1,147 @@
+/*
+ * 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.car.ui.recyclerview;
+
+import android.database.DataSetObservable;
+import android.database.DataSetObserver;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListAdapter;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This class implements {@link ListAdapter} using a {@link RecyclerView.Adapter} as its
+ * backing data.
+ *
+ * @param <T> The ViewHolder type of the Adapter we're converting.
+ */
+public class RecyclerViewToListAdapter<T extends RecyclerView.ViewHolder> implements ListAdapter {
+
+    private RecyclerView.Adapter<T> mRecyclerViewAdapter;
+    private DataSetObservable mDataSetObservable = new DataSetObservable();
+    private Map<View, T> mViewsToViewHolders = new HashMap<>();
+
+    public RecyclerViewToListAdapter(RecyclerView.Adapter<T> adapter) {
+        mRecyclerViewAdapter = adapter;
+        mRecyclerViewAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
+            @Override
+            public void onChanged() {
+                mDataSetObservable.notifyChanged();
+            }
+
+            @Override
+            public void onItemRangeChanged(int positionStart, int itemCount) {
+                mDataSetObservable.notifyChanged();
+            }
+
+            @Override
+            public void onItemRangeInserted(int positionStart, int itemCount) {
+                mDataSetObservable.notifyChanged();
+            }
+
+            @Override
+            public void onItemRangeRemoved(int positionStart, int itemCount) {
+                mDataSetObservable.notifyChanged();
+            }
+
+            @Override
+            public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+                mDataSetObservable.notifyChanged();
+            }
+        });
+    }
+
+    @Override
+    public void registerDataSetObserver(DataSetObserver observer) {
+        mDataSetObservable.registerObserver(observer);
+    }
+
+    @Override
+    public void unregisterDataSetObserver(DataSetObserver observer) {
+        mDataSetObservable.unregisterObserver(observer);
+    }
+
+    @Override
+    public int getCount() {
+        return mRecyclerViewAdapter.getItemCount();
+    }
+
+    @Override
+    public Object getItem(int position) {
+        return null;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public boolean hasStableIds() {
+        return false;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        // If convertView is null, we need to create a ViewHolder. But if it's not,
+        // we can skip straight to bindViewHolder(), reusing the old viewHolder.
+        T holder;
+        if (convertView == null || !mViewsToViewHolders.containsKey(convertView)) {
+            holder = mRecyclerViewAdapter.createViewHolder(parent, getItemViewType(position));
+            mViewsToViewHolders.put(holder.itemView, holder);
+        } else {
+            holder = mViewsToViewHolders.get(convertView);
+        }
+        mRecyclerViewAdapter.bindViewHolder(holder, position);
+        return holder.itemView;
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return mRecyclerViewAdapter.getItemViewType(position);
+    }
+
+    @Override
+    public int getViewTypeCount() {
+        Set<Integer> types = new HashSet<>();
+        for (int i = 0; i < mRecyclerViewAdapter.getItemCount(); i++) {
+            types.add(mRecyclerViewAdapter.getItemViewType(i));
+        }
+        return types.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return getCount() == 0;
+    }
+
+    @Override
+    public boolean areAllItemsEnabled() {
+        return true;
+    }
+
+    @Override
+    public boolean isEnabled(int position) {
+        return true;
+    }
+}