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