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
17package com.android.settings.widget;
18
19import android.content.Context;
20import android.graphics.drawable.Drawable;
21import android.os.Bundle;
22import android.os.UserHandle;
23import android.os.UserManager;
24import android.support.annotation.VisibleForTesting;
25import android.support.v7.preference.Preference;
26import android.support.v7.preference.PreferenceScreen;
27import android.text.TextUtils;
28import android.util.ArrayMap;
29import android.view.LayoutInflater;
30import android.view.View;
31import android.view.ViewGroup;
32
33import com.android.settings.R;
34import com.android.settings.Utils;
35import com.android.settings.core.InstrumentedPreferenceFragment;
36
37import java.util.List;
38import java.util.Map;
39
40public abstract class RadioButtonPickerFragment extends InstrumentedPreferenceFragment implements
41        RadioButtonPreference.OnClickListener {
42
43    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
44    static final String EXTRA_FOR_WORK = "for_work";
45
46    private final Map<String, CandidateInfo> mCandidates = new ArrayMap<>();
47
48    protected UserManager mUserManager;
49    protected int mUserId;
50
51    @Override
52    public void onAttach(Context context) {
53        super.onAttach(context);
54        mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
55        final Bundle arguments = getArguments();
56
57        boolean mForWork = false;
58        if (arguments != null) {
59            mForWork = arguments.getBoolean(EXTRA_FOR_WORK);
60        }
61        final UserHandle managedProfile = Utils.getManagedProfile(mUserManager);
62        mUserId = mForWork && managedProfile != null
63                ? managedProfile.getIdentifier()
64                : UserHandle.myUserId();
65    }
66
67    @Override
68    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
69        super.onCreatePreferences(savedInstanceState, rootKey);
70        addPreferencesFromResource(R.xml.placeholder_prefs);
71        updateCandidates();
72    }
73
74    @Override
75    public View onCreateView(LayoutInflater inflater, ViewGroup container,
76            Bundle savedInstanceState) {
77        final View view = super.onCreateView(inflater, container, savedInstanceState);
78        setHasOptionsMenu(true);
79        return view;
80    }
81
82    @Override
83    public void onRadioButtonClicked(RadioButtonPreference selected) {
84        final String selectedKey = selected.getKey();
85        onRadioButtonConfirmed(selectedKey);
86    }
87
88    /**
89     * Called after the user tries to select an item.
90     */
91    protected void onSelectionPerformed(boolean success) {
92    }
93
94    /**
95     * Whether the UI should show a "None" item selection.
96     */
97    protected boolean shouldShowItemNone() {
98        return false;
99    }
100
101    protected CandidateInfo getCandidate(String key) {
102        return mCandidates.get(key);
103    }
104
105    protected void onRadioButtonConfirmed(String selectedKey) {
106        final boolean success = setDefaultKey(selectedKey);
107        if (success) {
108            updateCheckedState(selectedKey);
109        }
110        onSelectionPerformed(success);
111    }
112
113    /**
114     * A chance for subclasses to bind additional things to the preference.
115     */
116    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
117    public void bindPreferenceExtra(RadioButtonPreference pref,
118            String key, CandidateInfo info, String defaultKey, String systemDefaultKey) {
119    }
120
121    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
122    public void updateCandidates() {
123        mCandidates.clear();
124        final List<? extends CandidateInfo> candidateList = getCandidates();
125        if (candidateList != null) {
126            for (CandidateInfo info : candidateList) {
127                mCandidates.put(info.getKey(), info);
128            }
129        }
130        final String defaultKey = getDefaultKey();
131        final String systemDefaultKey = getSystemDefaultKey();
132        final PreferenceScreen screen = getPreferenceScreen();
133        screen.removeAll();
134        if (shouldShowItemNone()) {
135            final RadioButtonPreference nonePref = new RadioButtonPreference(getPrefContext());
136            nonePref.setIcon(R.drawable.ic_remove_circle);
137            nonePref.setTitle(R.string.app_list_preference_none);
138            nonePref.setChecked(TextUtils.isEmpty(defaultKey));
139            nonePref.setOnClickListener(this);
140            screen.addPreference(nonePref);
141        }
142        for (Map.Entry<String, CandidateInfo> app : mCandidates.entrySet()) {
143            RadioButtonPreference pref = new RadioButtonPreference(getPrefContext());
144            bindPreference(pref, app.getKey(), app.getValue(), defaultKey);
145            bindPreferenceExtra(pref, app.getKey(), app.getValue(), defaultKey, systemDefaultKey);
146            screen.addPreference(pref);
147        }
148        mayCheckOnlyRadioButton();
149    }
150
151    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
152    public RadioButtonPreference bindPreference(RadioButtonPreference pref,
153            String key, CandidateInfo info, String defaultKey) {
154        pref.setTitle(info.loadLabel());
155        pref.setIcon(info.loadIcon());
156        pref.setKey(key);
157        if (TextUtils.equals(defaultKey, key)) {
158            pref.setChecked(true);
159        }
160        pref.setEnabled(info.enabled);
161        pref.setOnClickListener(this);
162        return pref;
163    }
164
165    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
166    public void updateCheckedState(String selectedKey) {
167        final PreferenceScreen screen = getPreferenceScreen();
168        if (screen != null) {
169            final int count = screen.getPreferenceCount();
170            for (int i = 0; i < count; i++) {
171                final Preference pref = screen.getPreference(i);
172                if (pref instanceof RadioButtonPreference) {
173                    final RadioButtonPreference radioPref = (RadioButtonPreference) pref;
174                    final boolean newCheckedState = TextUtils.equals(pref.getKey(), selectedKey);
175                    if (radioPref.isChecked() != newCheckedState) {
176                        radioPref.setChecked(TextUtils.equals(pref.getKey(), selectedKey));
177                    }
178                }
179            }
180        }
181    }
182
183    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
184    public void mayCheckOnlyRadioButton() {
185        final PreferenceScreen screen = getPreferenceScreen();
186        // If there is only 1 thing on screen, select it.
187        if (screen != null && screen.getPreferenceCount() == 1) {
188            final Preference onlyPref = screen.getPreference(0);
189            if (onlyPref instanceof RadioButtonPreference) {
190                ((RadioButtonPreference) onlyPref).setChecked(true);
191            }
192        }
193    }
194
195    protected abstract List<? extends CandidateInfo> getCandidates();
196
197    protected abstract String getDefaultKey();
198
199    protected abstract boolean setDefaultKey(String key);
200
201    protected String getSystemDefaultKey() {
202        return null;
203    }
204
205    public static abstract class CandidateInfo {
206
207        public final boolean enabled;
208
209        public CandidateInfo(boolean enabled) {
210            this.enabled = enabled;
211        }
212
213        public abstract CharSequence loadLabel();
214
215        public abstract Drawable loadIcon();
216
217        public abstract String getKey();
218    }
219
220}
221