1 /* 2 * Copyright (C) 2014 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.settings.notification; 18 19 import android.app.settings.SettingsEnums; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.os.Bundle; 23 import android.os.Handler; 24 import android.os.Looper; 25 import android.os.Message; 26 import android.os.UserHandle; 27 import android.preference.SeekBarVolumizer; 28 import android.provider.SearchIndexableResource; 29 import android.text.TextUtils; 30 31 import androidx.annotation.VisibleForTesting; 32 import androidx.preference.ListPreference; 33 import androidx.preference.Preference; 34 35 import com.android.settings.R; 36 import com.android.settings.RingtonePreference; 37 import com.android.settings.core.OnActivityResultListener; 38 import com.android.settings.dashboard.DashboardFragment; 39 import com.android.settings.search.BaseSearchIndexProvider; 40 import com.android.settings.sound.HandsFreeProfileOutputPreferenceController; 41 import com.android.settings.widget.PreferenceCategoryController; 42 import com.android.settings.widget.UpdatableListPreferenceDialogFragment; 43 import com.android.settingslib.core.AbstractPreferenceController; 44 import com.android.settingslib.core.instrumentation.Instrumentable; 45 import com.android.settingslib.core.lifecycle.Lifecycle; 46 import com.android.settingslib.search.SearchIndexable; 47 48 import java.util.ArrayList; 49 import java.util.Arrays; 50 import java.util.List; 51 52 @SearchIndexable 53 public class SoundSettings extends DashboardFragment implements OnActivityResultListener { 54 private static final String TAG = "SoundSettings"; 55 56 private static final String SELECTED_PREFERENCE_KEY = "selected_preference"; 57 private static final int REQUEST_CODE = 200; 58 private static final int SAMPLE_CUTOFF = 2000; // manually cap sample playback at 2 seconds 59 60 @VisibleForTesting 61 static final int STOP_SAMPLE = 1; 62 63 @VisibleForTesting 64 final VolumePreferenceCallback mVolumeCallback = new VolumePreferenceCallback(); 65 @VisibleForTesting 66 final Handler mHandler = new Handler(Looper.getMainLooper()) { 67 @Override 68 public void handleMessage(Message msg) { 69 switch (msg.what) { 70 case STOP_SAMPLE: 71 mVolumeCallback.stopSample(); 72 break; 73 } 74 } 75 }; 76 77 private RingtonePreference mRequestPreference; 78 private UpdatableListPreferenceDialogFragment mDialogFragment; 79 private String mHfpOutputControllerKey; 80 81 @Override getMetricsCategory()82 public int getMetricsCategory() { 83 return SettingsEnums.SOUND; 84 } 85 86 @Override onCreate(Bundle savedInstanceState)87 public void onCreate(Bundle savedInstanceState) { 88 super.onCreate(savedInstanceState); 89 if (savedInstanceState != null) { 90 String selectedPreference = savedInstanceState.getString(SELECTED_PREFERENCE_KEY, null); 91 if (!TextUtils.isEmpty(selectedPreference)) { 92 mRequestPreference = (RingtonePreference) findPreference(selectedPreference); 93 } 94 95 UpdatableListPreferenceDialogFragment dialogFragment = 96 (UpdatableListPreferenceDialogFragment) getFragmentManager() 97 .findFragmentByTag(TAG); 98 mDialogFragment = dialogFragment; 99 } 100 } 101 102 @Override getHelpResource()103 public int getHelpResource() { 104 return R.string.help_url_sound; 105 } 106 107 @Override onPause()108 public void onPause() { 109 super.onPause(); 110 mVolumeCallback.stopSample(); 111 } 112 113 @Override onPreferenceTreeClick(Preference preference)114 public boolean onPreferenceTreeClick(Preference preference) { 115 if (preference instanceof RingtonePreference) { 116 mRequestPreference = (RingtonePreference) preference; 117 mRequestPreference.onPrepareRingtonePickerIntent(mRequestPreference.getIntent()); 118 getActivity().startActivityForResultAsUser( 119 mRequestPreference.getIntent(), 120 REQUEST_CODE, 121 null, 122 UserHandle.of(mRequestPreference.getUserId())); 123 return true; 124 } 125 return super.onPreferenceTreeClick(preference); 126 } 127 128 @Override onDisplayPreferenceDialog(Preference preference)129 public void onDisplayPreferenceDialog(Preference preference) { 130 final int metricsCategory; 131 if (mHfpOutputControllerKey.equals(preference.getKey())) { 132 metricsCategory = SettingsEnums.DIALOG_SWITCH_HFP_DEVICES; 133 } else { 134 metricsCategory = Instrumentable.METRICS_CATEGORY_UNKNOWN; 135 } 136 137 mDialogFragment = UpdatableListPreferenceDialogFragment. 138 newInstance(preference.getKey(), metricsCategory); 139 mDialogFragment.setTargetFragment(this, 0); 140 mDialogFragment.show(getFragmentManager(), TAG); 141 } 142 143 @Override getLogTag()144 protected String getLogTag() { 145 return TAG; 146 } 147 148 @Override getPreferenceScreenResId()149 protected int getPreferenceScreenResId() { 150 return R.xml.sound_settings; 151 } 152 153 @Override createPreferenceControllers(Context context)154 protected List<AbstractPreferenceController> createPreferenceControllers(Context context) { 155 return buildPreferenceControllers(context, this, getSettingsLifecycle()); 156 } 157 158 @Override onActivityResult(int requestCode, int resultCode, Intent data)159 public void onActivityResult(int requestCode, int resultCode, Intent data) { 160 if (mRequestPreference != null) { 161 mRequestPreference.onActivityResult(requestCode, resultCode, data); 162 mRequestPreference = null; 163 } 164 } 165 166 @Override onSaveInstanceState(Bundle outState)167 public void onSaveInstanceState(Bundle outState) { 168 super.onSaveInstanceState(outState); 169 if (mRequestPreference != null) { 170 outState.putString(SELECTED_PREFERENCE_KEY, mRequestPreference.getKey()); 171 } 172 } 173 174 @Override onAttach(Context context)175 public void onAttach(Context context) { 176 super.onAttach(context); 177 ArrayList<VolumeSeekBarPreferenceController> volumeControllers = new ArrayList<>(); 178 volumeControllers.add(use(AlarmVolumePreferenceController.class)); 179 volumeControllers.add(use(MediaVolumePreferenceController.class)); 180 volumeControllers.add(use(RingVolumePreferenceController.class)); 181 volumeControllers.add(use(NotificationVolumePreferenceController.class)); 182 volumeControllers.add(use(CallVolumePreferenceController.class)); 183 volumeControllers.add(use(RemoteVolumePreferenceController.class)); 184 185 use(HandsFreeProfileOutputPreferenceController.class).setCallback(listPreference -> 186 onPreferenceDataChanged(listPreference)); 187 mHfpOutputControllerKey = 188 use(HandsFreeProfileOutputPreferenceController.class).getPreferenceKey(); 189 190 for (VolumeSeekBarPreferenceController controller : volumeControllers) { 191 controller.setCallback(mVolumeCallback); 192 getSettingsLifecycle().addObserver(controller); 193 } 194 } 195 196 // === Volumes === 197 198 final class VolumePreferenceCallback implements VolumeSeekBarPreference.Callback { 199 private SeekBarVolumizer mCurrent; 200 201 @Override onSampleStarting(SeekBarVolumizer sbv)202 public void onSampleStarting(SeekBarVolumizer sbv) { 203 if (mCurrent != null && mCurrent != sbv) { 204 mCurrent.stopSample(); 205 } 206 mCurrent = sbv; 207 if (mCurrent != null) { 208 mHandler.removeMessages(STOP_SAMPLE); 209 mHandler.sendEmptyMessageDelayed(STOP_SAMPLE, SAMPLE_CUTOFF); 210 } 211 } 212 213 @Override onStreamValueChanged(int stream, int progress)214 public void onStreamValueChanged(int stream, int progress) { 215 if (mCurrent != null) { 216 mHandler.removeMessages(STOP_SAMPLE); 217 mHandler.sendEmptyMessageDelayed(STOP_SAMPLE, SAMPLE_CUTOFF); 218 } 219 } 220 stopSample()221 public void stopSample() { 222 if (mCurrent != null) { 223 mCurrent.stopSample(); 224 } 225 } 226 } 227 buildPreferenceControllers(Context context, SoundSettings fragment, Lifecycle lifecycle)228 private static List<AbstractPreferenceController> buildPreferenceControllers(Context context, 229 SoundSettings fragment, Lifecycle lifecycle) { 230 final List<AbstractPreferenceController> controllers = new ArrayList<>(); 231 232 // Volumes are added via xml 233 234 // === Phone & notification ringtone === 235 controllers.add(new PhoneRingtonePreferenceController(context)); 236 controllers.add(new AlarmRingtonePreferenceController(context)); 237 controllers.add(new NotificationRingtonePreferenceController(context)); 238 239 // === Work Sound Settings === 240 controllers.add(new WorkSoundPreferenceController(context, fragment, lifecycle)); 241 242 // === Other Sound Settings === 243 final DialPadTonePreferenceController dialPadTonePreferenceController = 244 new DialPadTonePreferenceController(context, fragment, lifecycle); 245 final ScreenLockSoundPreferenceController screenLockSoundPreferenceController = 246 new ScreenLockSoundPreferenceController(context, fragment, lifecycle); 247 final ChargingSoundPreferenceController chargingSoundPreferenceController = 248 new ChargingSoundPreferenceController(context, fragment, lifecycle); 249 final DockingSoundPreferenceController dockingSoundPreferenceController = 250 new DockingSoundPreferenceController(context, fragment, lifecycle); 251 final TouchSoundPreferenceController touchSoundPreferenceController = 252 new TouchSoundPreferenceController(context, fragment, lifecycle); 253 final VibrateOnTouchPreferenceController vibrateOnTouchPreferenceController = 254 new VibrateOnTouchPreferenceController(context, fragment, lifecycle); 255 final DockAudioMediaPreferenceController dockAudioMediaPreferenceController = 256 new DockAudioMediaPreferenceController(context, fragment, lifecycle); 257 final BootSoundPreferenceController bootSoundPreferenceController = 258 new BootSoundPreferenceController(context); 259 final EmergencyTonePreferenceController emergencyTonePreferenceController = 260 new EmergencyTonePreferenceController(context, fragment, lifecycle); 261 262 controllers.add(dialPadTonePreferenceController); 263 controllers.add(screenLockSoundPreferenceController); 264 controllers.add(chargingSoundPreferenceController); 265 controllers.add(dockingSoundPreferenceController); 266 controllers.add(touchSoundPreferenceController); 267 controllers.add(vibrateOnTouchPreferenceController); 268 controllers.add(dockAudioMediaPreferenceController); 269 controllers.add(bootSoundPreferenceController); 270 controllers.add(emergencyTonePreferenceController); 271 controllers.add(new PreferenceCategoryController(context, 272 "other_sounds_and_vibrations_category").setChildren( 273 Arrays.asList(dialPadTonePreferenceController, 274 screenLockSoundPreferenceController, 275 chargingSoundPreferenceController, 276 dockingSoundPreferenceController, 277 touchSoundPreferenceController, 278 vibrateOnTouchPreferenceController, 279 dockAudioMediaPreferenceController, 280 bootSoundPreferenceController, 281 emergencyTonePreferenceController))); 282 283 return controllers; 284 } 285 286 // === Indexing === 287 288 public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 289 new BaseSearchIndexProvider() { 290 291 public List<SearchIndexableResource> getXmlResourcesToIndex( 292 Context context, boolean enabled) { 293 final SearchIndexableResource sir = new SearchIndexableResource(context); 294 sir.xmlResId = R.xml.sound_settings; 295 return Arrays.asList(sir); 296 } 297 298 @Override 299 public List<AbstractPreferenceController> createPreferenceControllers( 300 Context context) { 301 return buildPreferenceControllers(context, null /* fragment */, 302 null /* lifecycle */); 303 } 304 }; 305 306 // === Work Sound Settings === 307 enableWorkSync()308 void enableWorkSync() { 309 final WorkSoundPreferenceController workSoundController = 310 use(WorkSoundPreferenceController.class); 311 if (workSoundController != null) { 312 workSoundController.enableWorkSync(); 313 } 314 } 315 onPreferenceDataChanged(ListPreference preference)316 private void onPreferenceDataChanged(ListPreference preference) { 317 if (mDialogFragment != null) { 318 mDialogFragment.onListPreferenceUpdated(preference); 319 } 320 } 321 }