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.states.RotationHelper; 53 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; 54 55 import java.util.Collections; 56 import java.util.List; 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 SharedPreferences.OnSharedPreferenceChangeListener{ 64 65 /** List of fragments that can be hosted by this activity. */ 66 private static final List<String> VALID_PREFERENCE_FRAGMENTS = 67 !Utilities.IS_DEBUG_DEVICE ? Collections.emptyList() 68 : Collections.singletonList(DeveloperOptionsFragment.class.getName()); 69 70 private static final String DEVELOPER_OPTIONS_KEY = "pref_developer_options"; 71 private static final String FLAGS_PREFERENCE_KEY = "flag_toggler"; 72 73 private static final String NOTIFICATION_DOTS_PREFERENCE_KEY = "pref_icon_badging"; 74 75 public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; 76 public static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args"; 77 private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600; 78 public static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted"; 79 80 @VisibleForTesting 81 static final String EXTRA_FRAGMENT = ":settings:fragment"; 82 @VisibleForTesting 83 static final String EXTRA_FRAGMENT_ARGS = ":settings:fragment_args"; 84 85 @Override onCreate(Bundle savedInstanceState)86 protected void onCreate(Bundle savedInstanceState) { 87 super.onCreate(savedInstanceState); 88 setContentView(R.layout.settings_activity); 89 setActionBar(findViewById(R.id.action_bar)); 90 WindowCompat.setDecorFitsSystemWindows(getWindow(), false); 91 92 Intent intent = getIntent(); 93 if (intent.hasExtra(EXTRA_FRAGMENT) || intent.hasExtra(EXTRA_FRAGMENT_ARGS)) { 94 getActionBar().setDisplayHomeAsUpEnabled(true); 95 } 96 97 if (savedInstanceState == null) { 98 Bundle args = intent.getBundleExtra(EXTRA_FRAGMENT_ARGS); 99 if (args == null) { 100 args = new Bundle(); 101 } 102 103 String prefKey = intent.getStringExtra(EXTRA_FRAGMENT_ARG_KEY); 104 if (!TextUtils.isEmpty(prefKey)) { 105 args.putString(EXTRA_FRAGMENT_ARG_KEY, prefKey); 106 } 107 108 final FragmentManager fm = getSupportFragmentManager(); 109 final Fragment f = fm.getFragmentFactory().instantiate(getClassLoader(), 110 getPreferenceFragment()); 111 f.setArguments(args); 112 // Display the fragment as the main content. 113 fm.beginTransaction().replace(R.id.content_frame, f).commit(); 114 } 115 Utilities.getPrefs(getApplicationContext()).registerOnSharedPreferenceChangeListener(this); 116 } 117 118 /** 119 * Obtains the preference fragment to instantiate in this activity. 120 * 121 * @return the preference fragment class 122 * @throws IllegalArgumentException if the fragment is unknown to this activity 123 */ getPreferenceFragment()124 private String getPreferenceFragment() { 125 String preferenceFragment = getIntent().getStringExtra(EXTRA_FRAGMENT); 126 String defaultFragment = getString(R.string.settings_fragment_name); 127 128 if (TextUtils.isEmpty(preferenceFragment)) { 129 return defaultFragment; 130 } else if (!preferenceFragment.equals(defaultFragment) 131 && !VALID_PREFERENCE_FRAGMENTS.contains(preferenceFragment)) { 132 throw new IllegalArgumentException( 133 "Invalid fragment for this activity: " + preferenceFragment); 134 } else { 135 return preferenceFragment; 136 } 137 } 138 139 @Override onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)140 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { } 141 startPreference(String fragment, Bundle args, String key)142 private boolean startPreference(String fragment, Bundle args, String key) { 143 if (Utilities.ATLEAST_P && getSupportFragmentManager().isStateSaved()) { 144 // Sometimes onClick can come after onPause because of being posted on the handler. 145 // Skip starting new preferences in that case. 146 return false; 147 } 148 final FragmentManager fm = getSupportFragmentManager(); 149 final Fragment f = fm.getFragmentFactory().instantiate(getClassLoader(), fragment); 150 if (f instanceof DialogFragment) { 151 f.setArguments(args); 152 ((DialogFragment) f).show(fm, key); 153 } else { 154 startActivity(new Intent(this, SettingsActivity.class) 155 .putExtra(EXTRA_FRAGMENT, fragment) 156 .putExtra(EXTRA_FRAGMENT_ARGS, args)); 157 } 158 return true; 159 } 160 161 @Override onPreferenceStartFragment( PreferenceFragmentCompat preferenceFragment, Preference pref)162 public boolean onPreferenceStartFragment( 163 PreferenceFragmentCompat preferenceFragment, Preference pref) { 164 return startPreference(pref.getFragment(), pref.getExtras(), pref.getKey()); 165 } 166 167 @Override onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen pref)168 public boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen pref) { 169 Bundle args = new Bundle(); 170 args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, pref.getKey()); 171 return startPreference(getString(R.string.settings_fragment_name), args, pref.getKey()); 172 } 173 174 @Override onOptionsItemSelected(MenuItem item)175 public boolean onOptionsItemSelected(MenuItem item) { 176 if (item.getItemId() == android.R.id.home) { 177 onBackPressed(); 178 return true; 179 } 180 return super.onOptionsItemSelected(item); 181 } 182 183 /** 184 * This fragment shows the launcher preferences. 185 */ 186 public static class LauncherSettingsFragment extends PreferenceFragmentCompat { 187 188 private String mHighLightKey; 189 private boolean mPreferenceHighlighted = false; 190 private Preference mDeveloperOptionPref; 191 192 @Override onCreatePreferences(Bundle savedInstanceState, String rootKey)193 public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 194 final Bundle args = getArguments(); 195 mHighLightKey = args == null ? null : args.getString(EXTRA_FRAGMENT_ARG_KEY); 196 if (rootKey == null && !TextUtils.isEmpty(mHighLightKey)) { 197 rootKey = getParentKeyForPref(mHighLightKey); 198 } 199 200 if (savedInstanceState != null) { 201 mPreferenceHighlighted = savedInstanceState.getBoolean(SAVE_HIGHLIGHTED_KEY); 202 } 203 204 getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY); 205 setPreferencesFromResource(R.xml.launcher_preferences, rootKey); 206 207 PreferenceScreen screen = getPreferenceScreen(); 208 for (int i = screen.getPreferenceCount() - 1; i >= 0; i--) { 209 Preference preference = screen.getPreference(i); 210 if (!initPreference(preference)) { 211 screen.removePreference(preference); 212 } 213 } 214 215 if (getActivity() != null && !TextUtils.isEmpty(getPreferenceScreen().getTitle())) { 216 getActivity().setTitle(getPreferenceScreen().getTitle()); 217 } 218 } 219 220 @Override onViewCreated(View view, Bundle savedInstanceState)221 public void onViewCreated(View view, Bundle savedInstanceState) { 222 super.onViewCreated(view, savedInstanceState); 223 View listView = getListView(); 224 final int bottomPadding = listView.getPaddingBottom(); 225 listView.setOnApplyWindowInsetsListener((v, insets) -> { 226 v.setPadding( 227 v.getPaddingLeft(), 228 v.getPaddingTop(), 229 v.getPaddingRight(), 230 bottomPadding + insets.getSystemWindowInsetBottom()); 231 return insets.consumeSystemWindowInsets(); 232 }); 233 } 234 235 @Override onSaveInstanceState(Bundle outState)236 public void onSaveInstanceState(Bundle outState) { 237 super.onSaveInstanceState(outState); 238 outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted); 239 } 240 getParentKeyForPref(String key)241 protected String getParentKeyForPref(String key) { 242 return null; 243 } 244 245 /** 246 * Initializes a preference. This is called for every preference. Returning false here 247 * will remove that preference from the list. 248 */ initPreference(Preference preference)249 protected boolean initPreference(Preference preference) { 250 switch (preference.getKey()) { 251 case NOTIFICATION_DOTS_PREFERENCE_KEY: 252 return !WidgetsModel.GO_DISABLE_NOTIFICATION_DOTS; 253 254 case ALLOW_ROTATION_PREFERENCE_KEY: 255 DeviceProfile deviceProfile = InvariantDeviceProfile.INSTANCE.get( 256 getContext()).getDeviceProfile(getContext()); 257 if (deviceProfile.isTablet) { 258 // Launcher supports rotation by default. No need to show this setting. 259 return false; 260 } 261 // Initialize the UI once 262 preference.setDefaultValue( 263 RotationHelper.getAllowRotationDefaultValue(deviceProfile)); 264 return true; 265 266 case FLAGS_PREFERENCE_KEY: 267 // Only show flag toggler UI if this build variant implements that. 268 return FeatureFlags.showFlagTogglerUi(getContext()); 269 270 case DEVELOPER_OPTIONS_KEY: 271 mDeveloperOptionPref = preference; 272 return updateDeveloperOption(); 273 } 274 275 return true; 276 } 277 278 /** 279 * Show if plugins are enabled or flag UI is enabled. 280 * @return True if we should show the preference option. 281 */ updateDeveloperOption()282 private boolean updateDeveloperOption() { 283 boolean showPreference = FeatureFlags.showFlagTogglerUi(getContext()) 284 || PluginManagerWrapper.hasPlugins(getContext()); 285 if (mDeveloperOptionPref != null) { 286 mDeveloperOptionPref.setEnabled(showPreference); 287 if (showPreference) { 288 getPreferenceScreen().addPreference(mDeveloperOptionPref); 289 } else { 290 getPreferenceScreen().removePreference(mDeveloperOptionPref); 291 } 292 } 293 return showPreference; 294 } 295 296 @Override onResume()297 public void onResume() { 298 super.onResume(); 299 300 updateDeveloperOption(); 301 302 if (isAdded() && !mPreferenceHighlighted) { 303 PreferenceHighlighter highlighter = createHighlighter(); 304 if (highlighter != null) { 305 getView().postDelayed(highlighter, DELAY_HIGHLIGHT_DURATION_MILLIS); 306 mPreferenceHighlighted = true; 307 } else { 308 requestAccessibilityFocus(getListView()); 309 } 310 } 311 } 312 createHighlighter()313 private PreferenceHighlighter createHighlighter() { 314 if (TextUtils.isEmpty(mHighLightKey)) { 315 return null; 316 } 317 318 PreferenceScreen screen = getPreferenceScreen(); 319 if (screen == null) { 320 return null; 321 } 322 323 RecyclerView list = getListView(); 324 PreferencePositionCallback callback = (PreferencePositionCallback) list.getAdapter(); 325 int position = callback.getPreferenceAdapterPosition(mHighLightKey); 326 return position >= 0 ? new PreferenceHighlighter( 327 list, position, screen.findPreference(mHighLightKey)) 328 : null; 329 } 330 requestAccessibilityFocus(@onNull final RecyclerView rv)331 private void requestAccessibilityFocus(@NonNull final RecyclerView rv) { 332 rv.post(() -> { 333 if (!rv.hasFocus() && rv.getChildCount() > 0) { 334 rv.getChildAt(0) 335 .performAccessibilityAction(ACTION_ACCESSIBILITY_FOCUS, null); 336 } 337 }); 338 } 339 } 340 } 341