1 /*
2  * Copyright (C) 2017 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.settings.widget;
18 
19 import android.content.Context;
20 import android.os.Bundle;
21 import android.os.UserHandle;
22 import android.os.UserManager;
23 import android.text.TextUtils;
24 import android.util.ArrayMap;
25 import android.util.Log;
26 import android.view.LayoutInflater;
27 import android.view.View;
28 import android.view.ViewGroup;
29 
30 import androidx.annotation.LayoutRes;
31 import androidx.annotation.VisibleForTesting;
32 import androidx.preference.Preference;
33 import androidx.preference.PreferenceScreen;
34 
35 import com.android.settings.R;
36 import com.android.settings.Utils;
37 import com.android.settings.core.InstrumentedPreferenceFragment;
38 import com.android.settings.core.PreferenceXmlParserUtils;
39 import com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag;
40 import com.android.settingslib.widget.CandidateInfo;
41 import com.android.settingslib.widget.RadioButtonPreference;
42 
43 import org.xmlpull.v1.XmlPullParserException;
44 
45 import java.io.IOException;
46 import java.util.List;
47 import java.util.Map;
48 
49 public abstract class RadioButtonPickerFragment extends InstrumentedPreferenceFragment implements
50         RadioButtonPreference.OnClickListener {
51 
52     @VisibleForTesting
53     static final String EXTRA_FOR_WORK = "for_work";
54     private static final String TAG = "RadioButtonPckrFrgmt";
55     @VisibleForTesting
56     boolean mAppendStaticPreferences = false;
57 
58     private final Map<String, CandidateInfo> mCandidates = new ArrayMap<>();
59 
60     protected UserManager mUserManager;
61     protected int mUserId;
62     private int mIllustrationId;
63     private int mIllustrationPreviewId;
64     private VideoPreference mVideoPreference;
65 
66     @Override
onAttach(Context context)67     public void onAttach(Context context) {
68         super.onAttach(context);
69         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
70         final Bundle arguments = getArguments();
71 
72         boolean mForWork = false;
73         if (arguments != null) {
74             mForWork = arguments.getBoolean(EXTRA_FOR_WORK);
75         }
76         final UserHandle managedProfile = Utils.getManagedProfile(mUserManager);
77         mUserId = mForWork && managedProfile != null
78                 ? managedProfile.getIdentifier()
79                 : UserHandle.myUserId();
80     }
81 
82     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)83     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
84         super.onCreatePreferences(savedInstanceState, rootKey);
85         try {
86             // Check if the xml specifies if static preferences should go on the top or bottom
87             final List<Bundle> metadata = PreferenceXmlParserUtils.extractMetadata(getContext(),
88                 getPreferenceScreenResId(),
89                 MetadataFlag.FLAG_INCLUDE_PREF_SCREEN |
90                 MetadataFlag.FLAG_NEED_PREF_APPEND);
91             mAppendStaticPreferences = metadata.get(0)
92                     .getBoolean(PreferenceXmlParserUtils.METADATA_APPEND);
93         } catch (IOException e) {
94             Log.e(TAG, "Error trying to open xml file", e);
95         } catch (XmlPullParserException e) {
96             Log.e(TAG, "Error parsing xml", e);
97         }
98         updateCandidates();
99     }
100 
101     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)102     public View onCreateView(LayoutInflater inflater, ViewGroup container,
103             Bundle savedInstanceState) {
104         final View view = super.onCreateView(inflater, container, savedInstanceState);
105         setHasOptionsMenu(true);
106         return view;
107     }
108 
109     @Override
getPreferenceScreenResId()110     protected abstract int getPreferenceScreenResId();
111 
112     @Override
onRadioButtonClicked(RadioButtonPreference selected)113     public void onRadioButtonClicked(RadioButtonPreference selected) {
114         final String selectedKey = selected.getKey();
115         onRadioButtonConfirmed(selectedKey);
116     }
117 
118     /**
119      * Called after the user tries to select an item.
120      */
onSelectionPerformed(boolean success)121     protected void onSelectionPerformed(boolean success) {
122     }
123 
124     /**
125      * Whether the UI should show a "None" item selection.
126      */
shouldShowItemNone()127     protected boolean shouldShowItemNone() {
128         return false;
129     }
130 
131     /**
132      * Populate any static preferences, independent of the radio buttons.
133      * These might be used to provide extra information about the choices.
134      **/
addStaticPreferences(PreferenceScreen screen)135     protected void addStaticPreferences(PreferenceScreen screen) {
136     }
137 
getCandidate(String key)138     protected CandidateInfo getCandidate(String key) {
139         return mCandidates.get(key);
140     }
141 
onRadioButtonConfirmed(String selectedKey)142     protected void onRadioButtonConfirmed(String selectedKey) {
143         final boolean success = setDefaultKey(selectedKey);
144         if (success) {
145             updateCheckedState(selectedKey);
146         }
147         onSelectionPerformed(success);
148     }
149 
150     /**
151      * A chance for subclasses to bind additional things to the preference.
152      */
bindPreferenceExtra(RadioButtonPreference pref, String key, CandidateInfo info, String defaultKey, String systemDefaultKey)153     public void bindPreferenceExtra(RadioButtonPreference pref,
154             String key, CandidateInfo info, String defaultKey, String systemDefaultKey) {
155     }
156 
updateCandidates()157     public void updateCandidates() {
158         mCandidates.clear();
159         final List<? extends CandidateInfo> candidateList = getCandidates();
160         if (candidateList != null) {
161             for (CandidateInfo info : candidateList) {
162                 mCandidates.put(info.getKey(), info);
163             }
164         }
165         final String defaultKey = getDefaultKey();
166         final String systemDefaultKey = getSystemDefaultKey();
167         final PreferenceScreen screen = getPreferenceScreen();
168         screen.removeAll();
169         if (mIllustrationId != 0) {
170             addIllustration(screen);
171         }
172         if (!mAppendStaticPreferences) {
173             addStaticPreferences(screen);
174         }
175 
176         final int customLayoutResId = getRadioButtonPreferenceCustomLayoutResId();
177         if (shouldShowItemNone()) {
178             final RadioButtonPreference nonePref = new RadioButtonPreference(getPrefContext());
179             if (customLayoutResId > 0) {
180                 nonePref.setLayoutResource(customLayoutResId);
181             }
182             nonePref.setIcon(R.drawable.ic_remove_circle);
183             nonePref.setTitle(R.string.app_list_preference_none);
184             nonePref.setChecked(TextUtils.isEmpty(defaultKey));
185             nonePref.setOnClickListener(this);
186             screen.addPreference(nonePref);
187         }
188         if (candidateList != null) {
189             for (CandidateInfo info : candidateList) {
190                 RadioButtonPreference pref = new RadioButtonPreference(getPrefContext());
191                 if (customLayoutResId > 0) {
192                     pref.setLayoutResource(customLayoutResId);
193                 }
194                 bindPreference(pref, info.getKey(), info, defaultKey);
195                 bindPreferenceExtra(pref, info.getKey(), info, defaultKey, systemDefaultKey);
196                 screen.addPreference(pref);
197             }
198         }
199         mayCheckOnlyRadioButton();
200         if (mAppendStaticPreferences) {
201             addStaticPreferences(screen);
202         }
203     }
204 
bindPreference(RadioButtonPreference pref, String key, CandidateInfo info, String defaultKey)205     public RadioButtonPreference bindPreference(RadioButtonPreference pref,
206             String key, CandidateInfo info, String defaultKey) {
207         pref.setTitle(info.loadLabel());
208         Utils.setSafeIcon(pref, info.loadIcon());
209         pref.setKey(key);
210         if (TextUtils.equals(defaultKey, key)) {
211             pref.setChecked(true);
212         }
213         pref.setEnabled(info.enabled);
214         pref.setOnClickListener(this);
215         return pref;
216     }
217 
updateCheckedState(String selectedKey)218     public void updateCheckedState(String selectedKey) {
219         final PreferenceScreen screen = getPreferenceScreen();
220         if (screen != null) {
221             final int count = screen.getPreferenceCount();
222             for (int i = 0; i < count; i++) {
223                 final Preference pref = screen.getPreference(i);
224                 if (pref instanceof RadioButtonPreference) {
225                     final RadioButtonPreference radioPref = (RadioButtonPreference) pref;
226                     final boolean newCheckedState = TextUtils.equals(pref.getKey(), selectedKey);
227                     if (radioPref.isChecked() != newCheckedState) {
228                         radioPref.setChecked(TextUtils.equals(pref.getKey(), selectedKey));
229                     }
230                 }
231             }
232         }
233     }
234 
mayCheckOnlyRadioButton()235     public void mayCheckOnlyRadioButton() {
236         final PreferenceScreen screen = getPreferenceScreen();
237         // If there is only 1 thing on screen, select it.
238         if (screen != null && screen.getPreferenceCount() == 1) {
239             final Preference onlyPref = screen.getPreference(0);
240             if (onlyPref instanceof RadioButtonPreference) {
241                 ((RadioButtonPreference) onlyPref).setChecked(true);
242             }
243         }
244     }
245 
246     /**
247      * Allows you to set an illustration at the top of this screen. Set the illustration id to 0
248      * if you want to remove the illustration.
249      * @param illustrationId The res id for the raw of the illustration.
250      * @param previewId The res id for the drawable of the illustration
251      */
setIllustration(int illustrationId, int previewId)252     protected void setIllustration(int illustrationId, int previewId) {
253         mIllustrationId = illustrationId;
254         mIllustrationPreviewId = previewId;
255     }
256 
addIllustration(PreferenceScreen screen)257     private void addIllustration(PreferenceScreen screen) {
258         mVideoPreference = new VideoPreference(getContext());
259         mVideoPreference.setVideo(mIllustrationId, mIllustrationPreviewId);
260         screen.addPreference(mVideoPreference);
261     }
262 
getCandidates()263     protected abstract List<? extends CandidateInfo> getCandidates();
264 
getDefaultKey()265     protected abstract String getDefaultKey();
266 
setDefaultKey(String key)267     protected abstract boolean setDefaultKey(String key);
268 
getSystemDefaultKey()269     protected String getSystemDefaultKey() {
270         return null;
271     }
272 
273     /**
274      * Provides a custom layout for each candidate row.
275      */
276     @LayoutRes
getRadioButtonPreferenceCustomLayoutResId()277     protected int getRadioButtonPreferenceCustomLayoutResId() {
278         return 0;
279     }
280 
281 }
282