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