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;
+ }
+}