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.SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY;
22 import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
23 import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue;
24 import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
25 
26 import android.app.Activity;
27 import android.app.DialogFragment;
28 import android.app.Fragment;
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.content.SharedPreferences;
32 import android.content.pm.PackageManager;
33 import android.os.Bundle;
34 import android.provider.Settings;
35 import android.text.TextUtils;
36 
37 import androidx.annotation.NonNull;
38 import androidx.preference.Preference;
39 import androidx.preference.PreferenceFragment;
40 import androidx.preference.PreferenceFragment.OnPreferenceStartFragmentCallback;
41 import androidx.preference.PreferenceFragment.OnPreferenceStartScreenCallback;
42 import androidx.preference.PreferenceGroup.PreferencePositionCallback;
43 import androidx.preference.PreferenceScreen;
44 import androidx.recyclerview.widget.RecyclerView;
45 
46 import com.android.launcher3.LauncherFiles;
47 import com.android.launcher3.R;
48 import com.android.launcher3.Utilities;
49 import com.android.launcher3.config.FeatureFlags;
50 import com.android.launcher3.graphics.GridOptionsProvider;
51 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
52 import com.android.launcher3.util.SecureSettingsObserver;
53 
54 /**
55  * Settings activity for Launcher. Currently implements the following setting: Allow rotation
56  */
57 public class SettingsActivity extends Activity
58         implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback,
59         SharedPreferences.OnSharedPreferenceChangeListener{
60 
61     private static final String DEVELOPER_OPTIONS_KEY = "pref_developer_options";
62     private static final String FLAGS_PREFERENCE_KEY = "flag_toggler";
63 
64     private static final String NOTIFICATION_DOTS_PREFERENCE_KEY = "pref_icon_badging";
65     /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
66     private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners";
67 
68     public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
69     public static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args";
70     private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600;
71     public static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
72 
73     public static final String GRID_OPTIONS_PREFERENCE_KEY = "pref_grid_options";
74 
75     @Override
onCreate(Bundle savedInstanceState)76     protected void onCreate(Bundle savedInstanceState) {
77         super.onCreate(savedInstanceState);
78 
79         if (savedInstanceState == null) {
80             Bundle args = new Bundle();
81             String prefKey = getIntent().getStringExtra(EXTRA_FRAGMENT_ARG_KEY);
82             if (!TextUtils.isEmpty(prefKey)) {
83                 args.putString(EXTRA_FRAGMENT_ARG_KEY, prefKey);
84             }
85 
86             Fragment f = Fragment.instantiate(
87                     this, getString(R.string.settings_fragment_name), args);
88             // Display the fragment as the main content.
89             getFragmentManager().beginTransaction()
90                     .replace(android.R.id.content, f)
91                     .commit();
92         }
93         Utilities.getPrefs(getApplicationContext()).registerOnSharedPreferenceChangeListener(this);
94     }
95     @Override
onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)96     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
97         if (GRID_OPTIONS_PREFERENCE_KEY.equals(key)) {
98 
99             final ComponentName cn = new ComponentName(getApplicationContext(),
100                     GridOptionsProvider.class);
101             Context c = getApplicationContext();
102             int oldValue = c.getPackageManager().getComponentEnabledSetting(cn);
103             int newValue;
104             if (Utilities.getPrefs(c).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)) {
105                 newValue = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
106             } else {
107                 newValue = PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
108             }
109 
110             if (oldValue != newValue) {
111                 c.getPackageManager().setComponentEnabledSetting(cn, newValue,
112                         PackageManager.DONT_KILL_APP);
113             }
114         }
115     }
116 
startFragment(String fragment, Bundle args, String key)117     private boolean startFragment(String fragment, Bundle args, String key) {
118         if (Utilities.ATLEAST_P && getFragmentManager().isStateSaved()) {
119             // Sometimes onClick can come after onPause because of being posted on the handler.
120             // Skip starting new fragments in that case.
121             return false;
122         }
123         Fragment f = Fragment.instantiate(this, fragment, args);
124         if (f instanceof DialogFragment) {
125             ((DialogFragment) f).show(getFragmentManager(), key);
126         } else {
127             getFragmentManager()
128                     .beginTransaction()
129                     .replace(android.R.id.content, f)
130                     .addToBackStack(key)
131                     .commit();
132         }
133         return true;
134     }
135 
136     @Override
onPreferenceStartFragment( PreferenceFragment preferenceFragment, Preference pref)137     public boolean onPreferenceStartFragment(
138             PreferenceFragment preferenceFragment, Preference pref) {
139         return startFragment(pref.getFragment(), pref.getExtras(), pref.getKey());
140     }
141 
142     @Override
onPreferenceStartScreen(PreferenceFragment caller, PreferenceScreen pref)143     public boolean onPreferenceStartScreen(PreferenceFragment caller, PreferenceScreen pref) {
144         Bundle args = new Bundle();
145         args.putString(PreferenceFragment.ARG_PREFERENCE_ROOT, pref.getKey());
146         return startFragment(getString(R.string.settings_fragment_name), args, pref.getKey());
147     }
148 
149     /**
150      * This fragment shows the launcher preferences.
151      */
152     public static class LauncherSettingsFragment extends PreferenceFragment {
153 
154         private SecureSettingsObserver mNotificationDotsObserver;
155 
156         private String mHighLightKey;
157         private boolean mPreferenceHighlighted = false;
158 
159         @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)160         public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
161             final Bundle args = getArguments();
162             mHighLightKey = args == null ? null : args.getString(EXTRA_FRAGMENT_ARG_KEY);
163             if (rootKey == null && !TextUtils.isEmpty(mHighLightKey)) {
164                 rootKey = getParentKeyForPref(mHighLightKey);
165             }
166 
167             if (savedInstanceState != null) {
168                 mPreferenceHighlighted = savedInstanceState.getBoolean(SAVE_HIGHLIGHTED_KEY);
169             }
170 
171             getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY);
172             setPreferencesFromResource(R.xml.launcher_preferences, rootKey);
173 
174             PreferenceScreen screen = getPreferenceScreen();
175             for (int i = screen.getPreferenceCount() - 1; i >= 0; i--) {
176                 Preference preference = screen.getPreference(i);
177                 if (!initPreference(preference)) {
178                     screen.removePreference(preference);
179                 }
180             }
181         }
182 
183         @Override
onSaveInstanceState(Bundle outState)184         public void onSaveInstanceState(Bundle outState) {
185             super.onSaveInstanceState(outState);
186             outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted);
187         }
188 
getParentKeyForPref(String key)189         protected String getParentKeyForPref(String key) {
190             return null;
191         }
192 
193         /**
194          * Initializes a preference. This is called for every preference. Returning false here
195          * will remove that preference from the list.
196          */
initPreference(Preference preference)197         protected boolean initPreference(Preference preference) {
198             switch (preference.getKey()) {
199                 case NOTIFICATION_DOTS_PREFERENCE_KEY:
200                     if (!Utilities.ATLEAST_OREO ||
201                             !getResources().getBoolean(R.bool.notification_dots_enabled)) {
202                         return false;
203                     }
204 
205                     // Listen to system notification dot settings while this UI is active.
206                     mNotificationDotsObserver = newNotificationSettingsObserver(
207                             getActivity(), (NotificationDotsPreference) preference);
208                     mNotificationDotsObserver.register();
209                     // Also listen if notification permission changes
210                     mNotificationDotsObserver.getResolver().registerContentObserver(
211                             Settings.Secure.getUriFor(NOTIFICATION_ENABLED_LISTENERS), false,
212                             mNotificationDotsObserver);
213                     mNotificationDotsObserver.dispatchOnChange();
214                     return true;
215 
216                 case ADD_ICON_PREFERENCE_KEY:
217                     return Utilities.ATLEAST_OREO;
218 
219                 case ALLOW_ROTATION_PREFERENCE_KEY:
220                     if (getResources().getBoolean(R.bool.allow_rotation)) {
221                         // Launcher supports rotation by default. No need to show this setting.
222                         return false;
223                     }
224                     // Initialize the UI once
225                     preference.setDefaultValue(getAllowRotationDefaultValue());
226                     return true;
227 
228                 case FLAGS_PREFERENCE_KEY:
229                     // Only show flag toggler UI if this build variant implements that.
230                     return FeatureFlags.showFlagTogglerUi(getContext());
231 
232                 case DEVELOPER_OPTIONS_KEY:
233                     // Show if plugins are enabled or flag UI is enabled.
234                     return FeatureFlags.showFlagTogglerUi(getContext()) ||
235                             PluginManagerWrapper.hasPlugins(getContext());
236                 case GRID_OPTIONS_PREFERENCE_KEY:
237                     return Utilities.isDevelopersOptionsEnabled(getContext()) &&
238                             Utilities.IS_DEBUG_DEVICE &&
239                             Utilities.existsStyleWallpapers(getContext());
240             }
241 
242             return true;
243         }
244 
245         @Override
onResume()246         public void onResume() {
247             super.onResume();
248 
249             if (isAdded() && !mPreferenceHighlighted) {
250                 PreferenceHighlighter highlighter = createHighlighter();
251                 if (highlighter != null) {
252                     getView().postDelayed(highlighter, DELAY_HIGHLIGHT_DURATION_MILLIS);
253                     mPreferenceHighlighted = true;
254                 } else {
255                     requestAccessibilityFocus(getListView());
256                 }
257             }
258         }
259 
createHighlighter()260         private PreferenceHighlighter createHighlighter() {
261             if (TextUtils.isEmpty(mHighLightKey)) {
262                 return null;
263             }
264 
265             PreferenceScreen screen = getPreferenceScreen();
266             if (screen == null) {
267                 return null;
268             }
269 
270             RecyclerView list = getListView();
271             PreferencePositionCallback callback = (PreferencePositionCallback) list.getAdapter();
272             int position = callback.getPreferenceAdapterPosition(mHighLightKey);
273             return position >= 0 ? new PreferenceHighlighter(list, position) : null;
274         }
275 
requestAccessibilityFocus(@onNull final RecyclerView rv)276         private void requestAccessibilityFocus(@NonNull final RecyclerView rv) {
277             rv.post(() -> {
278                 if (!rv.hasFocus() && rv.getChildCount() > 0) {
279                     rv.getChildAt(0)
280                             .performAccessibilityAction(ACTION_ACCESSIBILITY_FOCUS, null);
281                 }
282             });
283         }
284 
285         @Override
onDestroy()286         public void onDestroy() {
287             if (mNotificationDotsObserver != null) {
288                 mNotificationDotsObserver.unregister();
289                 mNotificationDotsObserver = null;
290             }
291             super.onDestroy();
292         }
293     }
294 }
295