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