• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.launcher3.settings;
18 
19 import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
20 
21 import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
22 
23 import android.content.Intent;
24 import android.content.SharedPreferences;
25 import android.os.Bundle;
26 import android.text.TextUtils;
27 import android.view.MenuItem;
28 import android.view.View;
29 
30 import androidx.annotation.NonNull;
31 import androidx.annotation.VisibleForTesting;
32 import androidx.core.view.WindowCompat;
33 import androidx.fragment.app.DialogFragment;
34 import androidx.fragment.app.Fragment;
35 import androidx.fragment.app.FragmentActivity;
36 import androidx.fragment.app.FragmentManager;
37 import androidx.preference.Preference;
38 import androidx.preference.PreferenceFragmentCompat;
39 import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback;
40 import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartScreenCallback;
41 import androidx.preference.PreferenceGroup.PreferencePositionCallback;
42 import androidx.preference.PreferenceScreen;
43 import androidx.recyclerview.widget.RecyclerView;
44 
45 import com.android.launcher3.DeviceProfile;
46 import com.android.launcher3.InvariantDeviceProfile;
47 import com.android.launcher3.LauncherFiles;
48 import com.android.launcher3.R;
49 import com.android.launcher3.Utilities;
50 import com.android.launcher3.config.FeatureFlags;
51 import com.android.launcher3.model.WidgetsModel;
52 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
53 
54 import java.util.Collections;
55 import java.util.List;
56 
57 /**
58  * Settings activity for Launcher. Currently implements the following setting: Allow rotation
59  */
60 public class SettingsActivity extends FragmentActivity
61         implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback,
62         SharedPreferences.OnSharedPreferenceChangeListener{
63 
64     /** List of fragments that can be hosted by this activity. */
65     private static final List<String> VALID_PREFERENCE_FRAGMENTS = Collections.singletonList(
66             DeveloperOptionsFragment.class.getName());
67 
68     private static final String DEVELOPER_OPTIONS_KEY = "pref_developer_options";
69     private static final String FLAGS_PREFERENCE_KEY = "flag_toggler";
70 
71     private static final String NOTIFICATION_DOTS_PREFERENCE_KEY = "pref_icon_badging";
72 
73     public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
74     public static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args";
75     private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600;
76     public static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
77 
78     @VisibleForTesting
79     static final String EXTRA_FRAGMENT = ":settings:fragment";
80     @VisibleForTesting
81     static final String EXTRA_FRAGMENT_ARGS = ":settings:fragment_args";
82 
83     @Override
onCreate(Bundle savedInstanceState)84     protected void onCreate(Bundle savedInstanceState) {
85         super.onCreate(savedInstanceState);
86         setContentView(R.layout.settings_activity);
87         setActionBar(findViewById(R.id.action_bar));
88         WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
89 
90         Intent intent = getIntent();
91         if (intent.hasExtra(EXTRA_FRAGMENT) || intent.hasExtra(EXTRA_FRAGMENT_ARGS)) {
92             getActionBar().setDisplayHomeAsUpEnabled(true);
93         }
94 
95         if (savedInstanceState == null) {
96             Bundle args = intent.getBundleExtra(EXTRA_FRAGMENT_ARGS);
97             if (args == null) {
98                 args = new Bundle();
99             }
100 
101             String prefKey = intent.getStringExtra(EXTRA_FRAGMENT_ARG_KEY);
102             if (!TextUtils.isEmpty(prefKey)) {
103                 args.putString(EXTRA_FRAGMENT_ARG_KEY, prefKey);
104             }
105 
106             final FragmentManager fm = getSupportFragmentManager();
107             final Fragment f = fm.getFragmentFactory().instantiate(getClassLoader(),
108                     getPreferenceFragment());
109             f.setArguments(args);
110             // Display the fragment as the main content.
111             fm.beginTransaction().replace(R.id.content_frame, f).commit();
112         }
113         Utilities.getPrefs(getApplicationContext()).registerOnSharedPreferenceChangeListener(this);
114     }
115 
116     /**
117      * Obtains the preference fragment to instantiate in this activity.
118      *
119      * @return the preference fragment class
120      * @throws IllegalArgumentException if the fragment is unknown to this activity
121      */
getPreferenceFragment()122     private String getPreferenceFragment() {
123         String preferenceFragment = getIntent().getStringExtra(EXTRA_FRAGMENT);
124         String defaultFragment = getString(R.string.settings_fragment_name);
125 
126         if (TextUtils.isEmpty(preferenceFragment)) {
127             return defaultFragment;
128         } else if (!preferenceFragment.equals(defaultFragment)
129                 && !VALID_PREFERENCE_FRAGMENTS.contains(preferenceFragment)) {
130             throw new IllegalArgumentException(
131                     "Invalid fragment for this activity: " + preferenceFragment);
132         } else {
133             return preferenceFragment;
134         }
135     }
136 
137     @Override
onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)138     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { }
139 
startPreference(String fragment, Bundle args, String key)140     private boolean startPreference(String fragment, Bundle args, String key) {
141         if (Utilities.ATLEAST_P && getSupportFragmentManager().isStateSaved()) {
142             // Sometimes onClick can come after onPause because of being posted on the handler.
143             // Skip starting new preferences in that case.
144             return false;
145         }
146         final FragmentManager fm = getSupportFragmentManager();
147         final Fragment f = fm.getFragmentFactory().instantiate(getClassLoader(), fragment);
148         if (f instanceof DialogFragment) {
149             f.setArguments(args);
150             ((DialogFragment) f).show(fm, key);
151         } else {
152             startActivity(new Intent(this, SettingsActivity.class)
153                     .putExtra(EXTRA_FRAGMENT, fragment)
154                     .putExtra(EXTRA_FRAGMENT_ARGS, args));
155         }
156         return true;
157     }
158 
159     @Override
onPreferenceStartFragment( PreferenceFragmentCompat preferenceFragment, Preference pref)160     public boolean onPreferenceStartFragment(
161             PreferenceFragmentCompat preferenceFragment, Preference pref) {
162         return startPreference(pref.getFragment(), pref.getExtras(), pref.getKey());
163     }
164 
165     @Override
onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen pref)166     public boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen pref) {
167         Bundle args = new Bundle();
168         args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, pref.getKey());
169         return startPreference(getString(R.string.settings_fragment_name), args, pref.getKey());
170     }
171 
172     @Override
onOptionsItemSelected(MenuItem item)173     public boolean onOptionsItemSelected(MenuItem item) {
174         if (item.getItemId() == android.R.id.home) {
175             onBackPressed();
176             return true;
177         }
178         return super.onOptionsItemSelected(item);
179     }
180 
181     /**
182      * This fragment shows the launcher preferences.
183      */
184     public static class LauncherSettingsFragment extends PreferenceFragmentCompat {
185 
186         private String mHighLightKey;
187         private boolean mPreferenceHighlighted = false;
188         private Preference mDeveloperOptionPref;
189 
190         @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)191         public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
192             final Bundle args = getArguments();
193             mHighLightKey = args == null ? null : args.getString(EXTRA_FRAGMENT_ARG_KEY);
194             if (rootKey == null && !TextUtils.isEmpty(mHighLightKey)) {
195                 rootKey = getParentKeyForPref(mHighLightKey);
196             }
197 
198             if (savedInstanceState != null) {
199                 mPreferenceHighlighted = savedInstanceState.getBoolean(SAVE_HIGHLIGHTED_KEY);
200             }
201 
202             getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY);
203             setPreferencesFromResource(R.xml.launcher_preferences, rootKey);
204 
205             PreferenceScreen screen = getPreferenceScreen();
206             for (int i = screen.getPreferenceCount() - 1; i >= 0; i--) {
207                 Preference preference = screen.getPreference(i);
208                 if (!initPreference(preference)) {
209                     screen.removePreference(preference);
210                 }
211             }
212 
213             if (getActivity() != null && !TextUtils.isEmpty(getPreferenceScreen().getTitle())) {
214                 getActivity().setTitle(getPreferenceScreen().getTitle());
215             }
216         }
217 
218         @Override
onViewCreated(View view, Bundle savedInstanceState)219         public void onViewCreated(View view, Bundle savedInstanceState) {
220             super.onViewCreated(view, savedInstanceState);
221             View listView = getListView();
222             final int bottomPadding = listView.getPaddingBottom();
223             listView.setOnApplyWindowInsetsListener((v, insets) -> {
224                 v.setPadding(
225                         v.getPaddingLeft(),
226                         v.getPaddingTop(),
227                         v.getPaddingRight(),
228                         bottomPadding + insets.getSystemWindowInsetBottom());
229                 return insets.consumeSystemWindowInsets();
230             });
231         }
232 
233         @Override
onSaveInstanceState(Bundle outState)234         public void onSaveInstanceState(Bundle outState) {
235             super.onSaveInstanceState(outState);
236             outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted);
237         }
238 
getParentKeyForPref(String key)239         protected String getParentKeyForPref(String key) {
240             return null;
241         }
242 
243         /**
244          * Initializes a preference. This is called for every preference. Returning false here
245          * will remove that preference from the list.
246          */
initPreference(Preference preference)247         protected boolean initPreference(Preference preference) {
248             switch (preference.getKey()) {
249                 case NOTIFICATION_DOTS_PREFERENCE_KEY:
250                     return !WidgetsModel.GO_DISABLE_NOTIFICATION_DOTS;
251 
252                 case ALLOW_ROTATION_PREFERENCE_KEY:
253                     DeviceProfile deviceProfile = InvariantDeviceProfile.INSTANCE.get(
254                             getContext()).getDeviceProfile(getContext());
255                     if (deviceProfile.allowRotation) {
256                         // Launcher supports rotation by default. No need to show this setting.
257                         return false;
258                     }
259                     // Initialize the UI once
260                     preference.setDefaultValue(false);
261                     return true;
262 
263                 case FLAGS_PREFERENCE_KEY:
264                     // Only show flag toggler UI if this build variant implements that.
265                     return FeatureFlags.showFlagTogglerUi(getContext());
266 
267                 case DEVELOPER_OPTIONS_KEY:
268                     mDeveloperOptionPref = preference;
269                     return updateDeveloperOption();
270             }
271 
272             return true;
273         }
274 
275         /**
276          * Show if plugins are enabled or flag UI is enabled.
277          * @return True if we should show the preference option.
278          */
updateDeveloperOption()279         private boolean updateDeveloperOption() {
280             boolean showPreference = FeatureFlags.showFlagTogglerUi(getContext())
281                     || PluginManagerWrapper.hasPlugins(getContext());
282             if (mDeveloperOptionPref != null) {
283                 mDeveloperOptionPref.setEnabled(showPreference);
284                 if (showPreference) {
285                     getPreferenceScreen().addPreference(mDeveloperOptionPref);
286                 } else {
287                     getPreferenceScreen().removePreference(mDeveloperOptionPref);
288                 }
289             }
290             return showPreference;
291         }
292 
293         @Override
onResume()294         public void onResume() {
295             super.onResume();
296 
297             updateDeveloperOption();
298 
299             if (isAdded() && !mPreferenceHighlighted) {
300                 PreferenceHighlighter highlighter = createHighlighter();
301                 if (highlighter != null) {
302                     getView().postDelayed(highlighter, DELAY_HIGHLIGHT_DURATION_MILLIS);
303                     mPreferenceHighlighted = true;
304                 } else {
305                     requestAccessibilityFocus(getListView());
306                 }
307             }
308         }
309 
createHighlighter()310         private PreferenceHighlighter createHighlighter() {
311             if (TextUtils.isEmpty(mHighLightKey)) {
312                 return null;
313             }
314 
315             PreferenceScreen screen = getPreferenceScreen();
316             if (screen == null) {
317                 return null;
318             }
319 
320             RecyclerView list = getListView();
321             PreferencePositionCallback callback = (PreferencePositionCallback) list.getAdapter();
322             int position = callback.getPreferenceAdapterPosition(mHighLightKey);
323             return position >= 0 ? new PreferenceHighlighter(
324                     list, position, screen.findPreference(mHighLightKey))
325                     : null;
326         }
327 
requestAccessibilityFocus(@onNull final RecyclerView rv)328         private void requestAccessibilityFocus(@NonNull final RecyclerView rv) {
329             rv.post(() -> {
330                 if (!rv.hasFocus() && rv.getChildCount() > 0) {
331                     rv.getChildAt(0)
332                             .performAccessibilityAction(ACTION_ACCESSIBILITY_FOCUS, null);
333                 }
334             });
335         }
336     }
337 }
338