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; 18 19 import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY; 20 import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue; 21 22 import android.annotation.TargetApi; 23 import android.app.Activity; 24 import android.app.AlertDialog; 25 import android.app.Dialog; 26 import android.app.DialogFragment; 27 import android.app.FragmentManager; 28 import android.content.ComponentName; 29 import android.content.ContentResolver; 30 import android.content.Context; 31 import android.content.DialogInterface; 32 import android.content.Intent; 33 import android.os.Build; 34 import android.os.Bundle; 35 import android.preference.ListPreference; 36 import android.preference.Preference; 37 import android.preference.PreferenceFragment; 38 import android.preference.PreferenceScreen; 39 import android.provider.Settings; 40 import android.text.TextUtils; 41 import android.view.View; 42 import android.widget.Adapter; 43 import android.widget.ListView; 44 45 import com.android.launcher3.graphics.IconShapeOverride; 46 import com.android.launcher3.notification.NotificationListener; 47 import com.android.launcher3.util.ListViewHighlighter; 48 import com.android.launcher3.util.SettingsObserver; 49 import com.android.launcher3.views.ButtonPreference; 50 51 import java.util.Objects; 52 53 /** 54 * Settings activity for Launcher. Currently implements the following setting: Allow rotation 55 */ 56 public class SettingsActivity extends Activity { 57 58 private static final String ICON_BADGING_PREFERENCE_KEY = "pref_icon_badging"; 59 /** Hidden field Settings.Secure.NOTIFICATION_BADGING */ 60 public static final String NOTIFICATION_BADGING = "notification_badging"; 61 /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */ 62 private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners"; 63 64 private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; 65 private static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args"; 66 private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600; 67 private static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted"; 68 69 @Override onCreate(Bundle savedInstanceState)70 protected void onCreate(Bundle savedInstanceState) { 71 super.onCreate(savedInstanceState); 72 73 if (savedInstanceState == null) { 74 // Display the fragment as the main content. 75 getFragmentManager().beginTransaction() 76 .replace(android.R.id.content, getNewFragment()) 77 .commit(); 78 } 79 } 80 getNewFragment()81 protected PreferenceFragment getNewFragment() { 82 return new LauncherSettingsFragment(); 83 } 84 85 /** 86 * This fragment shows the launcher preferences. 87 */ 88 public static class LauncherSettingsFragment extends PreferenceFragment { 89 90 private IconBadgingObserver mIconBadgingObserver; 91 92 private String mPreferenceKey; 93 private boolean mPreferenceHighlighted = false; 94 95 @Override onCreate(Bundle savedInstanceState)96 public void onCreate(Bundle savedInstanceState) { 97 super.onCreate(savedInstanceState); 98 if (savedInstanceState != null) { 99 mPreferenceHighlighted = savedInstanceState.getBoolean(SAVE_HIGHLIGHTED_KEY); 100 } 101 102 getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY); 103 addPreferencesFromResource(R.xml.launcher_preferences); 104 105 ContentResolver resolver = getActivity().getContentResolver(); 106 107 ButtonPreference iconBadgingPref = 108 (ButtonPreference) findPreference(ICON_BADGING_PREFERENCE_KEY); 109 if (!Utilities.ATLEAST_OREO) { 110 getPreferenceScreen().removePreference( 111 findPreference(SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY)); 112 getPreferenceScreen().removePreference(iconBadgingPref); 113 } else if (!getResources().getBoolean(R.bool.notification_badging_enabled)) { 114 getPreferenceScreen().removePreference(iconBadgingPref); 115 } else { 116 // Listen to system notification badge settings while this UI is active. 117 mIconBadgingObserver = new IconBadgingObserver( 118 iconBadgingPref, resolver, getFragmentManager()); 119 mIconBadgingObserver.register(NOTIFICATION_BADGING, NOTIFICATION_ENABLED_LISTENERS); 120 } 121 122 Preference iconShapeOverride = findPreference(IconShapeOverride.KEY_PREFERENCE); 123 if (iconShapeOverride != null) { 124 if (IconShapeOverride.isSupported(getActivity())) { 125 IconShapeOverride.handlePreferenceUi((ListPreference) iconShapeOverride); 126 } else { 127 getPreferenceScreen().removePreference(iconShapeOverride); 128 } 129 } 130 131 // Setup allow rotation preference 132 Preference rotationPref = findPreference(ALLOW_ROTATION_PREFERENCE_KEY); 133 if (getResources().getBoolean(R.bool.allow_rotation)) { 134 // Launcher supports rotation by default. No need to show this setting. 135 getPreferenceScreen().removePreference(rotationPref); 136 } else { 137 // Initialize the UI once 138 rotationPref.setDefaultValue(getAllowRotationDefaultValue()); 139 } 140 } 141 142 @Override onSaveInstanceState(Bundle outState)143 public void onSaveInstanceState(Bundle outState) { 144 super.onSaveInstanceState(outState); 145 outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted); 146 } 147 148 @Override onResume()149 public void onResume() { 150 super.onResume(); 151 152 Intent intent = getActivity().getIntent(); 153 mPreferenceKey = intent.getStringExtra(EXTRA_FRAGMENT_ARG_KEY); 154 if (isAdded() && !mPreferenceHighlighted && !TextUtils.isEmpty(mPreferenceKey)) { 155 getView().postDelayed(this::highlightPreference, DELAY_HIGHLIGHT_DURATION_MILLIS); 156 } 157 } 158 highlightPreference()159 private void highlightPreference() { 160 Preference pref = findPreference(mPreferenceKey); 161 if (pref == null || getPreferenceScreen() == null) { 162 return; 163 } 164 PreferenceScreen screen = getPreferenceScreen(); 165 if (Utilities.ATLEAST_OREO) { 166 screen = selectPreferenceRecursive(pref, screen); 167 } 168 if (screen == null) { 169 return; 170 } 171 172 View root = screen.getDialog() != null 173 ? screen.getDialog().getWindow().getDecorView() : getView(); 174 ListView list = root.findViewById(android.R.id.list); 175 if (list == null || list.getAdapter() == null) { 176 return; 177 } 178 Adapter adapter = list.getAdapter(); 179 180 // Find the position 181 int position = -1; 182 for (int i = adapter.getCount() - 1; i >= 0; i--) { 183 if (pref == adapter.getItem(i)) { 184 position = i; 185 break; 186 } 187 } 188 new ListViewHighlighter(list, position); 189 mPreferenceHighlighted = true; 190 } 191 192 @Override onDestroy()193 public void onDestroy() { 194 if (mIconBadgingObserver != null) { 195 mIconBadgingObserver.unregister(); 196 mIconBadgingObserver = null; 197 } 198 super.onDestroy(); 199 } 200 201 @TargetApi(Build.VERSION_CODES.O) selectPreferenceRecursive( Preference pref, PreferenceScreen topParent)202 private PreferenceScreen selectPreferenceRecursive( 203 Preference pref, PreferenceScreen topParent) { 204 if (!(pref.getParent() instanceof PreferenceScreen)) { 205 return null; 206 } 207 208 PreferenceScreen parent = (PreferenceScreen) pref.getParent(); 209 if (Objects.equals(parent.getKey(), topParent.getKey())) { 210 return parent; 211 } else if (selectPreferenceRecursive(parent, topParent) != null) { 212 ((PreferenceScreen) parent.getParent()) 213 .onItemClick(null, null, parent.getOrder(), 0); 214 return parent; 215 } else { 216 return null; 217 } 218 } 219 } 220 221 /** 222 * Content observer which listens for system badging setting changes, 223 * and updates the launcher badging setting subtext accordingly. 224 */ 225 private static class IconBadgingObserver extends SettingsObserver.Secure 226 implements Preference.OnPreferenceClickListener { 227 228 private final ButtonPreference mBadgingPref; 229 private final ContentResolver mResolver; 230 private final FragmentManager mFragmentManager; 231 IconBadgingObserver(ButtonPreference badgingPref, ContentResolver resolver, FragmentManager fragmentManager)232 public IconBadgingObserver(ButtonPreference badgingPref, ContentResolver resolver, 233 FragmentManager fragmentManager) { 234 super(resolver); 235 mBadgingPref = badgingPref; 236 mResolver = resolver; 237 mFragmentManager = fragmentManager; 238 } 239 240 @Override onSettingChanged(boolean enabled)241 public void onSettingChanged(boolean enabled) { 242 int summary = enabled ? R.string.icon_badging_desc_on : R.string.icon_badging_desc_off; 243 244 boolean serviceEnabled = true; 245 if (enabled) { 246 // Check if the listener is enabled or not. 247 String enabledListeners = 248 Settings.Secure.getString(mResolver, NOTIFICATION_ENABLED_LISTENERS); 249 ComponentName myListener = 250 new ComponentName(mBadgingPref.getContext(), NotificationListener.class); 251 serviceEnabled = enabledListeners != null && 252 (enabledListeners.contains(myListener.flattenToString()) || 253 enabledListeners.contains(myListener.flattenToShortString())); 254 if (!serviceEnabled) { 255 summary = R.string.title_missing_notification_access; 256 } 257 } 258 mBadgingPref.setWidgetFrameVisible(!serviceEnabled); 259 mBadgingPref.setOnPreferenceClickListener(serviceEnabled ? null : this); 260 mBadgingPref.setSummary(summary); 261 262 } 263 264 @Override onPreferenceClick(Preference preference)265 public boolean onPreferenceClick(Preference preference) { 266 new NotificationAccessConfirmation().show(mFragmentManager, "notification_access"); 267 return true; 268 } 269 } 270 271 public static class NotificationAccessConfirmation 272 extends DialogFragment implements DialogInterface.OnClickListener { 273 274 @Override onCreateDialog(Bundle savedInstanceState)275 public Dialog onCreateDialog(Bundle savedInstanceState) { 276 final Context context = getActivity(); 277 String msg = context.getString(R.string.msg_missing_notification_access, 278 context.getString(R.string.derived_app_name)); 279 return new AlertDialog.Builder(context) 280 .setTitle(R.string.title_missing_notification_access) 281 .setMessage(msg) 282 .setNegativeButton(android.R.string.cancel, null) 283 .setPositiveButton(R.string.title_change_settings, this) 284 .create(); 285 } 286 287 @Override onClick(DialogInterface dialogInterface, int i)288 public void onClick(DialogInterface dialogInterface, int i) { 289 ComponentName cn = new ComponentName(getActivity(), NotificationListener.class); 290 Bundle showFragmentArgs = new Bundle(); 291 showFragmentArgs.putString(EXTRA_FRAGMENT_ARG_KEY, cn.flattenToString()); 292 293 Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS) 294 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 295 .putExtra(EXTRA_FRAGMENT_ARG_KEY, cn.flattenToString()) 296 .putExtra(EXTRA_SHOW_FRAGMENT_ARGS, showFragmentArgs); 297 getActivity().startActivity(intent); 298 } 299 } 300 } 301