• 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 android.provider.Settings.Global.DEVELOPMENT_SETTINGS_ENABLED;
20 
21 import static androidx.preference.PreferenceFragmentCompat.ARG_PREFERENCE_ROOT;
22 
23 import static com.android.launcher3.BuildConfig.IS_DEBUG_DEVICE;
24 import static com.android.launcher3.BuildConfig.IS_STUDIO_BUILD;
25 import static com.android.launcher3.InvariantDeviceProfile.TYPE_MULTI_DISPLAY;
26 import static com.android.launcher3.InvariantDeviceProfile.TYPE_TABLET;
27 import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
28 
29 import android.app.Activity;
30 import android.content.Intent;
31 import android.content.pm.ActivityInfo;
32 import android.net.Uri;
33 import android.os.Bundle;
34 import android.provider.Settings;
35 import android.text.TextUtils;
36 import android.view.MenuItem;
37 import android.view.View;
38 
39 import androidx.annotation.Nullable;
40 import androidx.annotation.VisibleForTesting;
41 import androidx.core.view.WindowCompat;
42 import androidx.fragment.app.DialogFragment;
43 import androidx.fragment.app.Fragment;
44 import androidx.fragment.app.FragmentActivity;
45 import androidx.fragment.app.FragmentManager;
46 import androidx.preference.Preference;
47 import androidx.preference.PreferenceFragmentCompat;
48 import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback;
49 import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartScreenCallback;
50 import androidx.preference.PreferenceGroup;
51 import androidx.preference.PreferenceGroup.PreferencePositionCallback;
52 import androidx.preference.PreferenceScreen;
53 import androidx.recyclerview.widget.RecyclerView;
54 
55 import com.android.launcher3.BuildConfig;
56 import com.android.launcher3.Flags;
57 import com.android.launcher3.InvariantDeviceProfile;
58 import com.android.launcher3.LauncherFiles;
59 import com.android.launcher3.R;
60 import com.android.launcher3.states.RotationHelper;
61 import com.android.launcher3.util.DisplayController;
62 import com.android.launcher3.util.SettingsCache;
63 
64 /**
65  * Settings activity for Launcher. Currently implements the following setting: Allow rotation
66  */
67 public class SettingsActivity extends FragmentActivity
68         implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback {
69 
70     @VisibleForTesting
71     static final String DEVELOPER_OPTIONS_KEY = "pref_developer_options";
72 
73     public static final String FIXED_LANDSCAPE_MODE = "pref_fixed_landscape_mode";
74 
75     private static final String NOTIFICATION_DOTS_PREFERENCE_KEY = "pref_icon_badging";
76 
77     public static final String EXTRA_FRAGMENT_ARGS = ":settings:fragment_args";
78 
79     // Intent extra to indicate the pref-key to highlighted when opening the settings activity
80     public static final String EXTRA_FRAGMENT_HIGHLIGHT_KEY = ":settings:fragment_args_key";
81     // Intent extra to indicate the pref-key of the root screen when opening the settings activity
82     public static final String EXTRA_FRAGMENT_ROOT_KEY = ARG_PREFERENCE_ROOT;
83 
84     private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600;
85     public static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
86 
87     @Override
onCreate(Bundle savedInstanceState)88     protected void onCreate(Bundle savedInstanceState) {
89         super.onCreate(savedInstanceState);
90         setContentView(R.layout.settings_activity);
91 
92         setActionBar(findViewById(R.id.action_bar));
93         WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
94 
95         Intent intent = getIntent();
96         if (intent.hasExtra(EXTRA_FRAGMENT_ROOT_KEY) || intent.hasExtra(EXTRA_FRAGMENT_ARGS)
97                 || intent.hasExtra(EXTRA_FRAGMENT_HIGHLIGHT_KEY)) {
98             getActionBar().setDisplayHomeAsUpEnabled(true);
99         }
100 
101         if (savedInstanceState == null) {
102             Bundle args = intent.getBundleExtra(EXTRA_FRAGMENT_ARGS);
103             if (args == null) {
104                 args = new Bundle();
105             }
106 
107             String highlight = intent.getStringExtra(EXTRA_FRAGMENT_HIGHLIGHT_KEY);
108             if (!TextUtils.isEmpty(highlight)) {
109                 args.putString(EXTRA_FRAGMENT_HIGHLIGHT_KEY, highlight);
110             }
111             String root = intent.getStringExtra(EXTRA_FRAGMENT_ROOT_KEY);
112             if (!TextUtils.isEmpty(root)) {
113                 args.putString(EXTRA_FRAGMENT_ROOT_KEY, root);
114             }
115 
116             final FragmentManager fm = getSupportFragmentManager();
117             final Fragment f = fm.getFragmentFactory().instantiate(getClassLoader(),
118                     getString(R.string.settings_fragment_name));
119             f.setArguments(args);
120             // Display the fragment as the main content.
121             fm.beginTransaction().replace(R.id.content_frame, f).commit();
122         }
123     }
124 
startPreference(String fragment, Bundle args, String key)125     private boolean startPreference(String fragment, Bundle args, String key) {
126         if (getSupportFragmentManager().isStateSaved()) {
127             // Sometimes onClick can come after onPause because of being posted on the handler.
128             // Skip starting new preferences in that case.
129             return false;
130         }
131         final FragmentManager fm = getSupportFragmentManager();
132         final Fragment f = fm.getFragmentFactory().instantiate(getClassLoader(), fragment);
133         if (f instanceof DialogFragment) {
134             f.setArguments(args);
135             ((DialogFragment) f).show(fm, key);
136         } else {
137             startActivity(new Intent(this, SettingsActivity.class)
138                     .putExtra(EXTRA_FRAGMENT_ARGS, args));
139         }
140         return true;
141     }
142 
143     @Override
onPreferenceStartFragment( PreferenceFragmentCompat preferenceFragment, Preference pref)144     public boolean onPreferenceStartFragment(
145             PreferenceFragmentCompat preferenceFragment, Preference pref) {
146         return startPreference(pref.getFragment(), pref.getExtras(), pref.getKey());
147     }
148 
149     @Override
onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen pref)150     public boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen pref) {
151         Bundle args = new Bundle();
152         args.putString(ARG_PREFERENCE_ROOT, pref.getKey());
153         return startPreference(getString(R.string.settings_fragment_name), args, pref.getKey());
154     }
155 
156     @Override
onOptionsItemSelected(MenuItem item)157     public boolean onOptionsItemSelected(MenuItem item) {
158         if (item.getItemId() == android.R.id.home) {
159             onBackPressed();
160             return true;
161         }
162         return super.onOptionsItemSelected(item);
163     }
164 
165     /**
166      * This fragment shows the launcher preferences.
167      */
168     public static class LauncherSettingsFragment extends PreferenceFragmentCompat implements
169             SettingsCache.OnChangeListener {
170 
171         protected boolean mDeveloperOptionsEnabled = false;
172 
173         private boolean mRestartOnResume = false;
174 
175         private String mHighLightKey;
176 
177         private boolean mPreferenceHighlighted = false;
178 
179         @Override
onCreate(@ullable Bundle savedInstanceState)180         public void onCreate(@Nullable Bundle savedInstanceState) {
181             if (BuildConfig.IS_DEBUG_DEVICE) {
182                 Uri devUri = Settings.Global.getUriFor(DEVELOPMENT_SETTINGS_ENABLED);
183                 SettingsCache settingsCache = SettingsCache.INSTANCE.get(getContext());
184                 mDeveloperOptionsEnabled = settingsCache.getValue(devUri);
185                 settingsCache.register(devUri, this);
186             }
187             super.onCreate(savedInstanceState);
188         }
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_HIGHLIGHT_KEY);
194 
195             if (savedInstanceState != null) {
196                 mPreferenceHighlighted = savedInstanceState.getBoolean(SAVE_HIGHLIGHTED_KEY);
197             }
198 
199             getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY);
200             setPreferencesFromResource(R.xml.launcher_preferences, rootKey);
201 
202             PreferenceScreen screen = getPreferenceScreen();
203             for (int i = screen.getPreferenceCount() - 1; i >= 0; i--) {
204                 Preference preference = screen.getPreference(i);
205                 if (!initPreference(preference)) {
206                     screen.removePreference(preference);
207                 }
208             }
209 
210             // If the target preference is not in the current preference screen, find the parent
211             // preference screen that contains the target preference and set it as the preference
212             // screen.
213             if (Flags.navigateToChildPreference()
214                     && mHighLightKey != null
215                     && !isKeyInPreferenceGroup(mHighLightKey, screen)) {
216                 final PreferenceScreen parentPreferenceScreen =
217                         findParentPreference(screen, mHighLightKey);
218                 if (parentPreferenceScreen != null && getActivity() != null) {
219                     if (!TextUtils.isEmpty(parentPreferenceScreen.getTitle())) {
220                         getActivity().setTitle(parentPreferenceScreen.getTitle());
221                     }
222                     setPreferenceScreen(parentPreferenceScreen);
223                     return;
224                 }
225             }
226 
227             if (getActivity() != null && !TextUtils.isEmpty(getPreferenceScreen().getTitle())) {
228                 getActivity().setTitle(getPreferenceScreen().getTitle());
229             }
230         }
231 
isKeyInPreferenceGroup(String targetKey, PreferenceGroup parent)232         private boolean isKeyInPreferenceGroup(String targetKey, PreferenceGroup parent) {
233             for (int i = 0; i < parent.getPreferenceCount(); i++) {
234                 Preference pref = parent.getPreference(i);
235                 if (pref.getKey() != null && pref.getKey().equals(targetKey)) {
236                     return true;
237                 }
238             }
239             return false;
240         }
241 
242         /**
243          * Finds the parent preference screen for the given target key.
244          *
245          * @param parent    the parent preference screen
246          * @param targetKey the key of the preference to find
247          * @return the parent preference screen that contains the target preference
248          */
249         @Nullable
findParentPreference(PreferenceScreen parent, String targetKey)250         private PreferenceScreen findParentPreference(PreferenceScreen parent, String targetKey) {
251             for (int i = 0; i < parent.getPreferenceCount(); i++) {
252                 Preference pref = parent.getPreference(i);
253                 if (pref instanceof PreferenceScreen) {
254                     PreferenceScreen foundKey = findParentPreference((PreferenceScreen) pref,
255                             targetKey);
256                     if (foundKey != null) {
257                         return foundKey;
258                     }
259                 } else if (pref.getKey() != null && pref.getKey().equals(targetKey)) {
260                     return parent;
261                 }
262             }
263             return null;
264         }
265 
266         @Override
onViewCreated(View view, Bundle savedInstanceState)267         public void onViewCreated(View view, Bundle savedInstanceState) {
268             super.onViewCreated(view, savedInstanceState);
269             View listView = getListView();
270             final int bottomPadding = listView.getPaddingBottom();
271             listView.setOnApplyWindowInsetsListener((v, insets) -> {
272                 v.setPadding(
273                         v.getPaddingLeft(),
274                         v.getPaddingTop(),
275                         v.getPaddingRight(),
276                         bottomPadding + insets.getSystemWindowInsetBottom());
277                 return insets.consumeSystemWindowInsets();
278             });
279 
280             // Overriding Text Direction in the Androidx preference library to support RTL
281             view.setTextDirection(View.TEXT_DIRECTION_LOCALE);
282         }
283 
284         @Override
onSaveInstanceState(Bundle outState)285         public void onSaveInstanceState(Bundle outState) {
286             super.onSaveInstanceState(outState);
287             outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted);
288         }
289 
290         /**
291          * Initializes a preference. This is called for every preference. Returning false here
292          * will remove that preference from the list.
293          */
initPreference(Preference preference)294         protected boolean initPreference(Preference preference) {
295             DisplayController.Info info = DisplayController.INSTANCE.get(getContext()).getInfo();
296             switch (preference.getKey()) {
297                 case NOTIFICATION_DOTS_PREFERENCE_KEY:
298                     return BuildConfig.NOTIFICATION_DOTS_ENABLED;
299                 case ALLOW_ROTATION_PREFERENCE_KEY:
300                     if (Flags.oneGridSpecs()) {
301                         return false;
302                     }
303                     if (info.isTablet(info.realBounds)) {
304                         // Launcher supports rotation by default. No need to show this setting.
305                         return false;
306                     }
307                     // Initialize the UI once
308                     preference.setDefaultValue(RotationHelper.getAllowRotationDefaultValue(info));
309                     return true;
310                 case DEVELOPER_OPTIONS_KEY:
311                     if (IS_STUDIO_BUILD) {
312                         preference.setOrder(0);
313                     }
314                     return mDeveloperOptionsEnabled;
315                 case FIXED_LANDSCAPE_MODE:
316                     if (!Flags.oneGridSpecs()
317                             // adding this condition until fixing b/378972567
318                             || InvariantDeviceProfile.INSTANCE.get(getContext()).deviceType
319                             == TYPE_MULTI_DISPLAY
320                             || InvariantDeviceProfile.INSTANCE.get(getContext()).deviceType
321                             == TYPE_TABLET) {
322                         return false;
323                     }
324                     // When the setting changes rotate the screen accordingly to showcase the result
325                     // of the setting
326                     preference.setOnPreferenceChangeListener(
327                             (pref, newValue) -> {
328                                 getActivity().setRequestedOrientation(
329                                         (boolean) newValue
330                                                 ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
331                                                 : ActivityInfo.SCREEN_ORIENTATION_USER
332                                 );
333                                 return true;
334                             }
335                     );
336                     return !info.isTablet(info.realBounds);
337             }
338             return true;
339         }
340 
341         @Override
onResume()342         public void onResume() {
343             super.onResume();
344 
345             if (isAdded() && !mPreferenceHighlighted) {
346                 PreferenceHighlighter highlighter = createHighlighter();
347                 if (highlighter != null) {
348                     getView().postDelayed(highlighter, DELAY_HIGHLIGHT_DURATION_MILLIS);
349                     mPreferenceHighlighted = true;
350                 }
351             }
352 
353             if (mRestartOnResume) {
354                 recreateActivityNow();
355             }
356         }
357 
358         @Override
onSettingsChanged(boolean isEnabled)359         public void onSettingsChanged(boolean isEnabled) {
360             // Developer options changed, try recreate
361             tryRecreateActivity();
362         }
363 
364         @Override
onDestroy()365         public void onDestroy() {
366             super.onDestroy();
367             if (IS_DEBUG_DEVICE) {
368                 SettingsCache.INSTANCE.get(getContext())
369                         .unregister(Settings.Global.getUriFor(DEVELOPMENT_SETTINGS_ENABLED), this);
370             }
371         }
372 
373         /**
374          * Tries to recreate the preference
375          */
tryRecreateActivity()376         protected void tryRecreateActivity() {
377             if (isResumed()) {
378                 recreateActivityNow();
379             } else {
380                 mRestartOnResume = true;
381             }
382         }
383 
recreateActivityNow()384         private void recreateActivityNow() {
385             Activity activity = getActivity();
386             if (activity != null) {
387                 activity.recreate();
388             }
389         }
390 
createHighlighter()391         private PreferenceHighlighter createHighlighter() {
392             if (TextUtils.isEmpty(mHighLightKey)) {
393                 return null;
394             }
395 
396             PreferenceScreen screen = getPreferenceScreen();
397             if (screen == null) {
398                 return null;
399             }
400 
401             RecyclerView list = getListView();
402             PreferencePositionCallback callback = (PreferencePositionCallback) list.getAdapter();
403             int position = callback.getPreferenceAdapterPosition(mHighLightKey);
404             return position >= 0 ? new PreferenceHighlighter(
405                     list, position, screen.findPreference(mHighLightKey))
406                     : null;
407         }
408     }
409 }
410