/* * Copyright (C) 2017 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.widget; import android.annotation.AnyRes; import android.content.Context; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.annotation.LayoutRes; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.Utils; import com.android.settings.core.PreferenceXmlParserUtils; import com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag; import com.android.settingslib.widget.CandidateInfo; import com.android.settingslib.widget.IllustrationPreference; import com.android.settingslib.widget.SelectorWithWidgetPreference; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.List; import java.util.Map; /** * A fragment to handle general radio button picker */ public abstract class RadioButtonPickerFragment extends SettingsPreferenceFragment implements SelectorWithWidgetPreference.OnClickListener { @VisibleForTesting static final String EXTRA_FOR_WORK = "for_work"; private static final String TAG = "RadioButtonPckrFrgmt"; @VisibleForTesting boolean mAppendStaticPreferences = false; private final Map mCandidates = new ArrayMap<>(); protected UserManager mUserManager; protected int mUserId; private int mIllustrationId; private int mIllustrationPreviewId; private IllustrationType mIllustrationType; @Override public void onAttach(Context context) { super.onAttach(context); mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); final Bundle arguments = getArguments(); boolean mForWork = false; if (arguments != null) { mForWork = arguments.getBoolean(EXTRA_FOR_WORK); } final UserHandle managedProfile = Utils.getManagedProfile(mUserManager); mUserId = mForWork && managedProfile != null ? managedProfile.getIdentifier() : UserHandle.myUserId(); } @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { super.onCreatePreferences(savedInstanceState, rootKey); try { // Check if the xml specifies if static preferences should go on the top or bottom final List metadata = PreferenceXmlParserUtils.extractMetadata(getContext(), getPreferenceScreenResId(), MetadataFlag.FLAG_INCLUDE_PREF_SCREEN | MetadataFlag.FLAG_NEED_PREF_APPEND); mAppendStaticPreferences = metadata.get(0) .getBoolean(PreferenceXmlParserUtils.METADATA_APPEND); } catch (IOException e) { Log.e(TAG, "Error trying to open xml file", e); } catch (XmlPullParserException e) { Log.e(TAG, "Error parsing xml", e); } updateCandidates(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View view = super.onCreateView(inflater, container, savedInstanceState); setHasOptionsMenu(true); return view; } @Override protected abstract int getPreferenceScreenResId(); @Override public void onRadioButtonClicked(SelectorWithWidgetPreference selected) { final String selectedKey = selected.getKey(); onRadioButtonConfirmed(selectedKey); } /** * Called after the user tries to select an item. */ protected void onSelectionPerformed(boolean success) { } /** * Whether the UI should show a "None" item selection. */ protected boolean shouldShowItemNone() { return false; } /** * Populate any static preferences, independent of the radio buttons. * These might be used to provide extra information about the choices. **/ protected void addStaticPreferences(PreferenceScreen screen) { } protected CandidateInfo getCandidate(String key) { return mCandidates.get(key); } protected void onRadioButtonConfirmed(String selectedKey) { final boolean success = setDefaultKey(selectedKey); if (success) { updateCheckedState(selectedKey); } onSelectionPerformed(success); } /** * A chance for subclasses to bind additional things to the preference. */ public void bindPreferenceExtra(SelectorWithWidgetPreference pref, String key, CandidateInfo info, String defaultKey, String systemDefaultKey) { } /** * A chance for subclasses to create a custom preference instance. */ protected SelectorWithWidgetPreference createPreference() { return new SelectorWithWidgetPreference(getPrefContext()); } public void updateCandidates() { mCandidates.clear(); final List candidateList = getCandidates(); if (candidateList != null) { for (CandidateInfo info : candidateList) { mCandidates.put(info.getKey(), info); } } final String defaultKey = getDefaultKey(); final String systemDefaultKey = getSystemDefaultKey(); final PreferenceScreen screen = getPreferenceScreen(); screen.removeAll(); if (mIllustrationId != 0) { addIllustration(screen); } if (!mAppendStaticPreferences) { addStaticPreferences(screen); } final int customLayoutResId = getRadioButtonPreferenceCustomLayoutResId(); if (shouldShowItemNone()) { final SelectorWithWidgetPreference nonePref = new SelectorWithWidgetPreference(getPrefContext()); if (customLayoutResId > 0) { nonePref.setLayoutResource(customLayoutResId); } nonePref.setIcon(R.drawable.ic_remove_circle); nonePref.setTitle(R.string.app_list_preference_none); nonePref.setChecked(TextUtils.isEmpty(defaultKey)); nonePref.setOnClickListener(this); screen.addPreference(nonePref); } if (candidateList != null) { for (CandidateInfo info : candidateList) { SelectorWithWidgetPreference pref = createPreference(); if (customLayoutResId > 0) { pref.setLayoutResource(customLayoutResId); } bindPreference(pref, info.getKey(), info, defaultKey); bindPreferenceExtra(pref, info.getKey(), info, defaultKey, systemDefaultKey); screen.addPreference(pref); } } mayCheckOnlyRadioButton(); if (mAppendStaticPreferences) { addStaticPreferences(screen); } } public SelectorWithWidgetPreference bindPreference(SelectorWithWidgetPreference pref, String key, CandidateInfo info, String defaultKey) { pref.setTitle(info.loadLabel()); pref.setIcon(Utils.getSafeIcon(info.loadIcon())); pref.setKey(key); if (TextUtils.equals(defaultKey, key)) { pref.setChecked(true); } pref.setEnabled(info.enabled); pref.setOnClickListener(this); return pref; } public void updateCheckedState(String selectedKey) { final PreferenceScreen screen = getPreferenceScreen(); if (screen != null) { final int count = screen.getPreferenceCount(); for (int i = 0; i < count; i++) { final Preference pref = screen.getPreference(i); if (pref instanceof SelectorWithWidgetPreference) { final SelectorWithWidgetPreference radioPref = (SelectorWithWidgetPreference) pref; final boolean newCheckedState = TextUtils.equals(pref.getKey(), selectedKey); if (radioPref.isChecked() != newCheckedState) { radioPref.setChecked(TextUtils.equals(pref.getKey(), selectedKey)); } } } } } public void mayCheckOnlyRadioButton() { final PreferenceScreen screen = getPreferenceScreen(); // If there is only 1 thing on screen, select it. if (screen != null && screen.getPreferenceCount() == 1) { final Preference onlyPref = screen.getPreference(0); if (onlyPref instanceof SelectorWithWidgetPreference) { ((SelectorWithWidgetPreference) onlyPref).setChecked(true); } } } /** * Allows you to set an illustration at the top of this screen. Set the illustration id to 0 * if you want to remove the illustration. * * @param illustrationId The res id for the raw of the illustration. * @param previewId The res id for the drawable of the illustration. * @param illustrationType The illustration type for the raw of the illustration. */ protected void setIllustration(@AnyRes int illustrationId, @AnyRes int previewId, IllustrationType illustrationType) { mIllustrationId = illustrationId; mIllustrationPreviewId = previewId; mIllustrationType = illustrationType; } /** * Allows you to set an illustration at the top of this screen. Set the illustration id to 0 * if you want to remove the illustration. * * @param illustrationId The res id for the raw of the illustration. * @param illustrationType The illustration type for the raw of the illustration. */ protected void setIllustration(@AnyRes int illustrationId, IllustrationType illustrationType) { setIllustration(illustrationId, 0, illustrationType); } private void addIllustration(PreferenceScreen screen) { switch (mIllustrationType) { case LOTTIE_ANIMATION: IllustrationPreference illustrationPreference = new IllustrationPreference( getContext()); illustrationPreference.setLottieAnimationResId(mIllustrationId); screen.addPreference(illustrationPreference); break; default: throw new IllegalArgumentException( "Invalid illustration type: " + mIllustrationType); } } protected abstract List getCandidates(); protected abstract String getDefaultKey(); protected abstract boolean setDefaultKey(String key); protected String getSystemDefaultKey() { return null; } /** * Provides a custom layout for each candidate row. */ @LayoutRes protected int getRadioButtonPreferenceCustomLayoutResId() { return 0; } protected enum IllustrationType { LOTTIE_ANIMATION } }