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.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
26 
27 import android.app.Activity;
28 import android.content.Intent;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.provider.Settings;
32 import android.text.TextUtils;
33 import android.view.MenuItem;
34 import android.view.View;
35 
36 import androidx.annotation.Nullable;
37 import androidx.annotation.VisibleForTesting;
38 import androidx.core.view.WindowCompat;
39 import androidx.fragment.app.DialogFragment;
40 import androidx.fragment.app.Fragment;
41 import androidx.fragment.app.FragmentActivity;
42 import androidx.fragment.app.FragmentManager;
43 import androidx.preference.Preference;
44 import androidx.preference.PreferenceFragmentCompat;
45 import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback;
46 import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartScreenCallback;
47 import androidx.preference.PreferenceGroup.PreferencePositionCallback;
48 import androidx.preference.PreferenceScreen;
49 import androidx.recyclerview.widget.RecyclerView;
50 
51 import com.android.launcher3.BuildConfig;
52 import com.android.launcher3.LauncherFiles;
53 import com.android.launcher3.R;
54 import com.android.launcher3.states.RotationHelper;
55 import com.android.launcher3.util.DisplayController;
56 import com.android.launcher3.util.SettingsCache;
57 
58 /**
59  * Settings activity for Launcher. Currently implements the following setting: Allow rotation
60  */
61 public class SettingsActivity extends FragmentActivity
62         implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback {
63 
64     @VisibleForTesting
65     static final String DEVELOPER_OPTIONS_KEY = "pref_developer_options";
66 
67     private static final String NOTIFICATION_DOTS_PREFERENCE_KEY = "pref_icon_badging";
68 
69     public static final String EXTRA_FRAGMENT_ARGS = ":settings:fragment_args";
70 
71     // Intent extra to indicate the pref-key to highlighted when opening the settings activity
72     public static final String EXTRA_FRAGMENT_HIGHLIGHT_KEY = ":settings:fragment_args_key";
73     // Intent extra to indicate the pref-key of the root screen when opening the settings activity
74     public static final String EXTRA_FRAGMENT_ROOT_KEY = ARG_PREFERENCE_ROOT;
75 
76     private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600;
77     public static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
78 
79     @Override
onCreate(Bundle savedInstanceState)80     protected void onCreate(Bundle savedInstanceState) {
81         super.onCreate(savedInstanceState);
82         setContentView(R.layout.settings_activity);
83 
84         setActionBar(findViewById(R.id.action_bar));
85         WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
86 
87         Intent intent = getIntent();
88         if (intent.hasExtra(EXTRA_FRAGMENT_ROOT_KEY) || intent.hasExtra(EXTRA_FRAGMENT_ARGS)
89                 || intent.hasExtra(EXTRA_FRAGMENT_HIGHLIGHT_KEY)) {
90             getActionBar().setDisplayHomeAsUpEnabled(true);
91         }
92 
93         if (savedInstanceState == null) {
94             Bundle args = intent.getBundleExtra(EXTRA_FRAGMENT_ARGS);
95             if (args == null) {
96                 args = new Bundle();
97             }
98 
99             String highlight = intent.getStringExtra(EXTRA_FRAGMENT_HIGHLIGHT_KEY);
100             if (!TextUtils.isEmpty(highlight)) {
101                 args.putString(EXTRA_FRAGMENT_HIGHLIGHT_KEY, highlight);
102             }
103             String root = intent.getStringExtra(EXTRA_FRAGMENT_ROOT_KEY);
104             if (!TextUtils.isEmpty(root)) {
105                 args.putString(EXTRA_FRAGMENT_ROOT_KEY, root);
106             }
107 
108             final FragmentManager fm = getSupportFragmentManager();
109             final Fragment f = fm.getFragmentFactory().instantiate(getClassLoader(),
110                     getString(R.string.settings_fragment_name));
111             f.setArguments(args);
112             // Display the fragment as the main content.
113             fm.beginTransaction().replace(R.id.content_frame, f).commit();
114         }
115     }
116 
startPreference(String fragment, Bundle args, String key)117     private boolean startPreference(String fragment, Bundle args, String key) {
118         if (getSupportFragmentManager().isStateSaved()) {
119             // Sometimes onClick can come after onPause because of being posted on the handler.
120             // Skip starting new preferences in that case.
121             return false;
122         }
123         final FragmentManager fm = getSupportFragmentManager();
124         final Fragment f = fm.getFragmentFactory().instantiate(getClassLoader(), fragment);
125         if (f instanceof DialogFragment) {
126             f.setArguments(args);
127             ((DialogFragment) f).show(fm, key);
128         } else {
129             startActivity(new Intent(this, SettingsActivity.class)
130                     .putExtra(EXTRA_FRAGMENT_ARGS, args));
131         }
132         return true;
133     }
134 
135     @Override
onPreferenceStartFragment( PreferenceFragmentCompat preferenceFragment, Preference pref)136     public boolean onPreferenceStartFragment(
137             PreferenceFragmentCompat preferenceFragment, Preference pref) {
138         return startPreference(pref.getFragment(), pref.getExtras(), pref.getKey());
139     }
140 
141     @Override
onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen pref)142     public boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen pref) {
143         Bundle args = new Bundle();
144         args.putString(ARG_PREFERENCE_ROOT, pref.getKey());
145         return startPreference(getString(R.string.settings_fragment_name), args, pref.getKey());
146     }
147 
148     @Override
onOptionsItemSelected(MenuItem item)149     public boolean onOptionsItemSelected(MenuItem item) {
150         if (item.getItemId() == android.R.id.home) {
151             onBackPressed();
152             return true;
153         }
154         return super.onOptionsItemSelected(item);
155     }
156 
157     /**
158      * This fragment shows the launcher preferences.
159      */
160     public static class LauncherSettingsFragment extends PreferenceFragmentCompat implements
161             SettingsCache.OnChangeListener {
162 
163         protected boolean mDeveloperOptionsEnabled = false;
164 
165         private boolean mRestartOnResume = false;
166 
167         private String mHighLightKey;
168         private boolean mPreferenceHighlighted = false;
169 
170         @Override
onCreate(@ullable Bundle savedInstanceState)171         public void onCreate(@Nullable Bundle savedInstanceState) {
172             if (BuildConfig.IS_DEBUG_DEVICE) {
173                 Uri devUri = Settings.Global.getUriFor(DEVELOPMENT_SETTINGS_ENABLED);
174                 SettingsCache settingsCache = SettingsCache.INSTANCE.get(getContext());
175                 mDeveloperOptionsEnabled = settingsCache.getValue(devUri);
176                 settingsCache.register(devUri, this);
177             }
178             super.onCreate(savedInstanceState);
179         }
180 
181         @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)182         public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
183             final Bundle args = getArguments();
184             mHighLightKey = args == null ? null : args.getString(EXTRA_FRAGMENT_HIGHLIGHT_KEY);
185 
186             if (savedInstanceState != null) {
187                 mPreferenceHighlighted = savedInstanceState.getBoolean(SAVE_HIGHLIGHTED_KEY);
188             }
189 
190             getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY);
191             setPreferencesFromResource(R.xml.launcher_preferences, rootKey);
192 
193             PreferenceScreen screen = getPreferenceScreen();
194             for (int i = screen.getPreferenceCount() - 1; i >= 0; i--) {
195                 Preference preference = screen.getPreference(i);
196                 if (!initPreference(preference)) {
197                     screen.removePreference(preference);
198                 }
199             }
200 
201             if (getActivity() != null && !TextUtils.isEmpty(getPreferenceScreen().getTitle())) {
202                 getActivity().setTitle(getPreferenceScreen().getTitle());
203             }
204         }
205 
206         @Override
onViewCreated(View view, Bundle savedInstanceState)207         public void onViewCreated(View view, Bundle savedInstanceState) {
208             super.onViewCreated(view, savedInstanceState);
209             View listView = getListView();
210             final int bottomPadding = listView.getPaddingBottom();
211             listView.setOnApplyWindowInsetsListener((v, insets) -> {
212                 v.setPadding(
213                         v.getPaddingLeft(),
214                         v.getPaddingTop(),
215                         v.getPaddingRight(),
216                         bottomPadding + insets.getSystemWindowInsetBottom());
217                 return insets.consumeSystemWindowInsets();
218             });
219 
220             // Overriding Text Direction in the Androidx preference library to support RTL
221             view.setTextDirection(View.TEXT_DIRECTION_LOCALE);
222         }
223 
224         @Override
onSaveInstanceState(Bundle outState)225         public void onSaveInstanceState(Bundle outState) {
226             super.onSaveInstanceState(outState);
227             outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted);
228         }
229 
230         /**
231          * Initializes a preference. This is called for every preference. Returning false here
232          * will remove that preference from the list.
233          */
initPreference(Preference preference)234         protected boolean initPreference(Preference preference) {
235             switch (preference.getKey()) {
236                 case NOTIFICATION_DOTS_PREFERENCE_KEY:
237                     return BuildConfig.NOTIFICATION_DOTS_ENABLED;
238 
239                 case ALLOW_ROTATION_PREFERENCE_KEY:
240                     DisplayController.Info info =
241                             DisplayController.INSTANCE.get(getContext()).getInfo();
242                     if (info.isTablet(info.realBounds)) {
243                         // Launcher supports rotation by default. No need to show this setting.
244                         return false;
245                     }
246                     // Initialize the UI once
247                     preference.setDefaultValue(RotationHelper.getAllowRotationDefaultValue(info));
248                     return true;
249 
250                 case DEVELOPER_OPTIONS_KEY:
251                     if (IS_STUDIO_BUILD) {
252                         preference.setOrder(0);
253                     }
254                     return mDeveloperOptionsEnabled;
255             }
256 
257             return true;
258         }
259 
260         @Override
onResume()261         public void onResume() {
262             super.onResume();
263 
264             if (isAdded() && !mPreferenceHighlighted) {
265                 PreferenceHighlighter highlighter = createHighlighter();
266                 if (highlighter != null) {
267                     getView().postDelayed(highlighter, DELAY_HIGHLIGHT_DURATION_MILLIS);
268                     mPreferenceHighlighted = true;
269                 }
270             }
271 
272             if (mRestartOnResume) {
273                 recreateActivityNow();
274             }
275         }
276 
277         @Override
onSettingsChanged(boolean isEnabled)278         public void onSettingsChanged(boolean isEnabled) {
279             // Developer options changed, try recreate
280             tryRecreateActivity();
281         }
282 
283         @Override
onDestroy()284         public void onDestroy() {
285             super.onDestroy();
286             if (IS_DEBUG_DEVICE) {
287                 SettingsCache.INSTANCE.get(getContext())
288                         .unregister(Settings.Global.getUriFor(DEVELOPMENT_SETTINGS_ENABLED), this);
289             }
290         }
291 
292         /**
293          * Tries to recreate the preference
294          */
tryRecreateActivity()295         protected void tryRecreateActivity() {
296             if (isResumed()) {
297                 recreateActivityNow();
298             } else {
299                 mRestartOnResume = true;
300             }
301         }
302 
recreateActivityNow()303         private void recreateActivityNow() {
304             Activity activity = getActivity();
305             if (activity != null) {
306                 activity.recreate();
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     }
328 }
329