1 /*
2  * Copyright 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.ui.preference;
18 
19 import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
20 import static com.android.car.ui.preference.PreferenceDialogFragment.ARG_KEY;
21 
22 import android.annotation.TargetApi;
23 import android.os.Bundle;
24 import android.view.LayoutInflater;
25 import android.view.View;
26 import android.view.ViewGroup;
27 
28 import androidx.annotation.NonNull;
29 import androidx.annotation.Nullable;
30 import androidx.fragment.app.Fragment;
31 import androidx.preference.DialogPreference;
32 import androidx.preference.Preference;
33 
34 import com.android.car.ui.FocusArea;
35 import com.android.car.ui.R;
36 import com.android.car.ui.baselayout.Insets;
37 import com.android.car.ui.baselayout.InsetsChangedListener;
38 import com.android.car.ui.recyclerview.CarUiContentListItem;
39 import com.android.car.ui.recyclerview.CarUiListItem;
40 import com.android.car.ui.recyclerview.CarUiListItemAdapter;
41 import com.android.car.ui.recyclerview.CarUiRecyclerView;
42 import com.android.car.ui.toolbar.NavButtonMode;
43 import com.android.car.ui.toolbar.Toolbar;
44 import com.android.car.ui.toolbar.ToolbarController;
45 import com.android.car.ui.utils.CarUiUtils;
46 
47 import java.util.ArrayList;
48 import java.util.Collections;
49 import java.util.HashSet;
50 import java.util.List;
51 import java.util.Set;
52 
53 /**
54  * A fragment that provides a layout with a list of options associated with a {@link
55  * CarUiMultiSelectListPreference}.
56  */
57 @TargetApi(MIN_TARGET_API)
58 public class MultiSelectListPreferenceFragment extends Fragment implements InsetsChangedListener {
59 
60     private CarUiMultiSelectListPreference mPreference;
61     private Set<String> mNewValues;
62     private boolean mUseInstantPreferenceChangeCallback;
63 
64     /**
65      * Returns a new instance of {@link MultiSelectListPreferenceFragment} for the {@link
66      * CarUiMultiSelectListPreference} with the given {@code key}.
67      */
68     @NonNull
newInstance(String key)69     static MultiSelectListPreferenceFragment newInstance(String key) {
70         MultiSelectListPreferenceFragment fragment = new MultiSelectListPreferenceFragment();
71         Bundle b = new Bundle(/* capacity= */ 1);
72         b.putString(ARG_KEY, key);
73         fragment.setArguments(b);
74         return fragment;
75     }
76 
77     @Nullable
78     @Override
onCreateView( @onNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)79     public View onCreateView(
80             @NonNull LayoutInflater inflater, @Nullable ViewGroup container,
81             @Nullable Bundle savedInstanceState) {
82         return inflater.inflate(R.layout.car_ui_list_preference, container, false);
83     }
84 
85     @Override
onViewCreated(@onNull View view, @Nullable Bundle savedInstanceState)86     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
87         super.onViewCreated(view, savedInstanceState);
88         final CarUiRecyclerView recyclerView = CarUiUtils.requireViewByRefId(view, R.id.list);
89         mUseInstantPreferenceChangeCallback =
90                 getResources().getBoolean(R.bool.car_ui_preference_list_instant_change_callback);
91         ToolbarController toolbar = null;
92         if (getTargetFragment() instanceof PreferenceFragment) {
93             toolbar = ((PreferenceFragment) getTargetFragment()).getPreferenceToolbar(this);
94         }
95 
96         mPreference = getPreference();
97 
98         recyclerView.setClipToPadding(false);
99         if (toolbar != null) {
100             toolbar.setTitle(mPreference.getTitle());
101             toolbar.setSubtitle("");
102             if (toolbar.isStateSet()) {
103                 toolbar.setState(Toolbar.State.SUBPAGE);
104             } else {
105                 toolbar.setNavButtonMode(NavButtonMode.BACK);
106             }
107             toolbar.setLogo(null);
108             toolbar.setMenuItems(null);
109             toolbar.setTabs(Collections.emptyList());
110         }
111 
112         mNewValues = new HashSet<>(mPreference.getValues());
113         CharSequence[] entries = mPreference.getEntries();
114         CharSequence[] entryValues = mPreference.getEntryValues();
115 
116         if (entries == null || entryValues == null) {
117             throw new IllegalStateException(
118                     "MultiSelectListPreference requires an entries array and an entryValues array"
119                             + ".");
120         }
121 
122         if (entries.length != entryValues.length) {
123             throw new IllegalStateException(
124                     "MultiSelectListPreference entries array length does not match entryValues "
125                             + "array length.");
126         }
127 
128         List<CarUiListItem> listItems = new ArrayList<>();
129         boolean[] selectedItems = mPreference.getSelectedItems();
130 
131         for (int i = 0; i < entries.length; i++) {
132             String entry = entries[i].toString();
133             String entryValue = entryValues[i].toString();
134             CarUiContentListItem item = new CarUiContentListItem(
135                     CarUiContentListItem.Action.CHECK_BOX);
136             item.setTitle(entry);
137             item.setChecked(selectedItems[i]);
138             item.setOnCheckedChangeListener((listItem, isChecked) -> {
139                 if (isChecked) {
140                     mNewValues.add(entryValue);
141                 } else {
142                     mNewValues.remove(entryValue);
143                 }
144 
145                 if (mUseInstantPreferenceChangeCallback) {
146                     updatePreference();
147                 }
148             });
149 
150             listItems.add(item);
151         }
152 
153         CarUiListItemAdapter adapter = new CarUiListItemAdapter(listItems);
154         recyclerView.setAdapter(adapter);
155     }
156 
157     @Override
onStart()158     public void onStart() {
159         super.onStart();
160         if (getTargetFragment() instanceof PreferenceFragment) {
161             Insets insets = ((PreferenceFragment) getTargetFragment()).getPreferenceInsets(this);
162             if (insets != null) {
163                 onCarUiInsetsChanged(insets);
164             }
165         }
166     }
167 
168     @Override
onStop()169     public void onStop() {
170         super.onStop();
171         if (!mUseInstantPreferenceChangeCallback) {
172             updatePreference();
173         }
174     }
175 
updatePreference()176     private void updatePreference() {
177         if (mPreference.callChangeListener(mNewValues)) {
178             mPreference.setValues(mNewValues);
179         }
180     }
181 
getPreference()182     private CarUiMultiSelectListPreference getPreference() {
183         if (getArguments() == null) {
184             throw new IllegalStateException("Preference arguments cannot be null");
185         }
186 
187         String key = getArguments().getString(ARG_KEY);
188         DialogPreference.TargetFragment fragment =
189                 (DialogPreference.TargetFragment) getTargetFragment();
190 
191         if (key == null) {
192             throw new IllegalStateException(
193                     "MultiSelectListPreference key not found in Fragment arguments");
194         }
195 
196         if (fragment == null) {
197             throw new IllegalStateException(
198                     "Target fragment must be registered before displaying "
199                             + "MultiSelectListPreference screen.");
200         }
201 
202         Preference preference = fragment.findPreference(key);
203 
204         if (!(preference instanceof CarUiMultiSelectListPreference)) {
205             throw new IllegalStateException(
206                     "Cannot use MultiSelectListPreferenceFragment with a preference that is "
207                             + "not of type CarUiMultiSelectListPreference");
208         }
209 
210         return (CarUiMultiSelectListPreference) preference;
211     }
212 
213     @Override
onCarUiInsetsChanged(@onNull Insets insets)214     public void onCarUiInsetsChanged(@NonNull Insets insets) {
215         View view = requireView();
216         CarUiUtils.requireViewByRefId(view, R.id.list)
217                 .setPadding(0, insets.getTop(), 0, insets.getBottom());
218         view.setPadding(insets.getLeft(), 0, insets.getRight(), 0);
219         FocusArea focusArea = view.findViewById(R.id.car_ui_focus_area);
220         if (focusArea != null) {
221             focusArea.setHighlightPadding(0, insets.getTop(), 0, insets.getBottom());
222             focusArea.setBoundsOffset(0, insets.getTop(), 0, insets.getBottom());
223         }
224     }
225 }
226