1 /*
2  * Copyright 2018 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 androidx.leanback.preference;
18 
19 import android.os.Bundle;
20 import android.view.KeyEvent;
21 import android.view.LayoutInflater;
22 import android.view.View;
23 import android.view.View.OnKeyListener;
24 import android.view.ViewGroup;
25 
26 import androidx.fragment.app.Fragment;
27 import androidx.fragment.app.FragmentTransaction;
28 import androidx.preference.EditTextPreference;
29 import androidx.preference.ListPreference;
30 import androidx.preference.MultiSelectListPreference;
31 import androidx.preference.Preference;
32 import androidx.preference.PreferenceFragment;
33 import androidx.preference.PreferenceFragmentCompat;
34 import androidx.preference.PreferenceScreen;
35 
36 import org.jspecify.annotations.NonNull;
37 
38 /**
39  * This fragment provides a container for displaying a {@link LeanbackPreferenceFragmentCompat}
40  *
41  * <p>The following sample code shows a simple leanback preference fragment that is
42  * populated from a resource.  The resource it loads is:</p>
43  *
44  * {@sample samples/SupportPreferenceDemos/src/main/res/xml/preferences.xml preferences}
45  *
46  * <p>The sample implements
47  * {@link PreferenceFragmentCompat.OnPreferenceStartFragmentCallback#onPreferenceStartFragment(
48  * PreferenceFragmentCompat, Preference)},
49  * {@link PreferenceFragmentCompat.OnPreferenceStartScreenCallback#onPreferenceStartScreen(
50  * PreferenceFragmentCompat, PreferenceScreen)},
51  * and {@link #onPreferenceStartInitialScreen()}:</p>
52  *
53  * {@sample samples/SupportPreferenceDemos/src/main/java/com/example/androidx/preference/LeanbackPreferences.java leanback_preferences}
54  */
55 public abstract class LeanbackSettingsFragmentCompat extends Fragment
56         implements PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
57         PreferenceFragmentCompat.OnPreferenceStartScreenCallback,
58         PreferenceFragmentCompat.OnPreferenceDisplayDialogCallback {
59 
60     private static final String PREFERENCE_FRAGMENT_TAG =
61             "androidx.leanback.preference.LeanbackSettingsFragment.PREFERENCE_FRAGMENT";
62 
63     private final OnKeyListener mRootViewOnKeyListener =
64             decorateOnKeyListener(new RootViewOnKeyListener());
65 
66     @Override
onCreateView(@onNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)67     public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
68             Bundle savedInstanceState) {
69         final View v = inflater.inflate(R.layout.leanback_settings_fragment, container, false);
70 
71         return v;
72     }
73 
74     @Override
onViewCreated(@onNull View view, Bundle savedInstanceState)75     public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
76         super.onViewCreated(view, savedInstanceState);
77         if (savedInstanceState == null) {
78             onPreferenceStartInitialScreen();
79         }
80     }
81 
82     @Override
onResume()83     public void onResume() {
84         super.onResume();
85         // Trap back button presses
86         final LeanbackSettingsRootView rootView = (LeanbackSettingsRootView) getView();
87         if (rootView != null) {
88             rootView.setOnBackKeyListener(mRootViewOnKeyListener);
89         }
90     }
91 
92     @Override
onPause()93     public void onPause() {
94         super.onPause();
95         final LeanbackSettingsRootView rootView = (LeanbackSettingsRootView) getView();
96         if (rootView != null) {
97             rootView.setOnBackKeyListener(null);
98         }
99     }
100 
101     @Override
onPreferenceDisplayDialog(@onNull PreferenceFragmentCompat caller, Preference pref)102     public boolean onPreferenceDisplayDialog(@NonNull PreferenceFragmentCompat caller,
103             Preference pref) {
104         if (caller == null) {
105             throw new IllegalArgumentException("Cannot display dialog for preference " + pref
106                     + ", Caller must not be null!");
107         }
108         final Fragment f;
109         if (pref instanceof ListPreference) {
110             final ListPreference listPreference = (ListPreference) pref;
111             f = LeanbackListPreferenceDialogFragmentCompat.newInstanceSingle(
112                     listPreference.getKey());
113             f.setTargetFragment(caller, 0);
114             startPreferenceFragment(f);
115         } else if (pref instanceof MultiSelectListPreference) {
116             MultiSelectListPreference listPreference = (MultiSelectListPreference) pref;
117             f = LeanbackListPreferenceDialogFragmentCompat.newInstanceMulti(
118                     listPreference.getKey());
119             f.setTargetFragment(caller, 0);
120             startPreferenceFragment(f);
121         } else if (pref instanceof EditTextPreference) {
122             f = LeanbackEditTextPreferenceDialogFragmentCompat.newInstance(pref.getKey());
123             f.setTargetFragment(caller, 0);
124             startPreferenceFragment(f);
125         } else {
126             return false;
127         }
128         return true;
129     }
130 
131     /**
132      * Called to instantiate the initial {@link PreferenceFragment}
133      * to be shown in this fragment. Implementations are expected to call
134      * {@link #startPreferenceFragment(Fragment)}.
135      */
onPreferenceStartInitialScreen()136     public abstract void onPreferenceStartInitialScreen();
137 
138     /**
139      * Displays a preference fragment to the user. This method can also be used to display
140      * list-style fragments on top of the stack of preference fragments.
141      *
142      * @param fragment Fragment instance to be added.
143      */
startPreferenceFragment(@onNull Fragment fragment)144     public void startPreferenceFragment(@NonNull Fragment fragment) {
145         final FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
146         final Fragment prevFragment =
147                 getChildFragmentManager().findFragmentByTag(PREFERENCE_FRAGMENT_TAG);
148         if (prevFragment != null) {
149             transaction
150                     .addToBackStack(null)
151                     .replace(R.id.settings_preference_fragment_container, fragment,
152                             PREFERENCE_FRAGMENT_TAG);
153         } else {
154             transaction
155                     .add(R.id.settings_preference_fragment_container, fragment,
156                             PREFERENCE_FRAGMENT_TAG);
157         }
158         transaction.commit();
159     }
160 
161     /**
162      * Displays a fragment to the user, temporarily replacing the contents of this fragment.
163      *
164      * @param fragment Fragment instance to be added.
165      */
startImmersiveFragment(@onNull Fragment fragment)166     public void startImmersiveFragment(@NonNull Fragment fragment) {
167         final FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
168         final Fragment preferenceFragment =
169                 getChildFragmentManager().findFragmentByTag(PREFERENCE_FRAGMENT_TAG);
170         if (preferenceFragment != null && !preferenceFragment.isHidden()) {
171             transaction.remove(preferenceFragment);
172         }
173         transaction
174                 .add(R.id.settings_dialog_container, fragment)
175                 .addToBackStack(null)
176                 .commit();
177     }
178 
179     /**
180      * Modifies or replaces the OnKeyListener automatically set for this fragment.
181      *
182      * <p>The default implementation simply returns the listener.
183      */
184     @NonNull
decorateOnKeyListener(@onNull OnKeyListener onKeyListener)185     protected OnKeyListener decorateOnKeyListener(@NonNull OnKeyListener onKeyListener) {
186         return onKeyListener;
187     }
188 
189     private class RootViewOnKeyListener implements OnKeyListener {
RootViewOnKeyListener()190         RootViewOnKeyListener() {
191         }
192 
193         @Override
onKey(View v, int keyCode, KeyEvent event)194         public boolean onKey(View v, int keyCode, KeyEvent event) {
195             if (keyCode == KeyEvent.KEYCODE_BACK) {
196                 return getChildFragmentManager().popBackStackImmediate();
197             } else {
198                 return false;
199             }
200         }
201     }
202 }
203