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