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