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.text.TextUtils; 29 30 import androidx.annotation.VisibleForTesting; 31 import androidx.preference.ListPreference; 32 import androidx.preference.Preference; 33 34 import com.android.settings.R; 35 import com.android.settings.RingtonePreference; 36 import com.android.settings.core.OnActivityResultListener; 37 import com.android.settings.dashboard.DashboardFragment; 38 import com.android.settings.search.BaseSearchIndexProvider; 39 import com.android.settings.sound.HandsFreeProfileOutputPreferenceController; 40 import com.android.settings.widget.PreferenceCategoryController; 41 import com.android.settingslib.core.AbstractPreferenceController; 42 import com.android.settingslib.core.instrumentation.Instrumentable; 43 import com.android.settingslib.core.lifecycle.Lifecycle; 44 import com.android.settingslib.search.SearchIndexable; 45 import com.android.settingslib.widget.UpdatableListPreferenceDialogFragment; 46 47 import java.util.ArrayList; 48 import java.util.Arrays; 49 import java.util.List; 50 51 @SearchIndexable 52 public class SoundSettings extends DashboardFragment implements OnActivityResultListener { 53 private static final String TAG = "SoundSettings"; 54 55 private static final String SELECTED_PREFERENCE_KEY = "selected_preference"; 56 private static final int REQUEST_CODE = 200; 57 private static final int SAMPLE_CUTOFF = 2000; // manually cap sample playback at 2 seconds 58 59 @VisibleForTesting 60 static final int STOP_SAMPLE = 1; 61 62 @VisibleForTesting 63 final VolumePreferenceCallback mVolumeCallback = new VolumePreferenceCallback(); 64 @VisibleForTesting 65 final Handler mHandler = new Handler(Looper.getMainLooper()) { 66 @Override 67 public void handleMessage(Message msg) { 68 switch (msg.what) { 69 case STOP_SAMPLE: 70 mVolumeCallback.stopSample(); 71 break; 72 } 73 } 74 }; 75 76 private RingtonePreference mRequestPreference; 77 private UpdatableListPreferenceDialogFragment mDialogFragment; 78 private String mHfpOutputControllerKey; 79 80 @Override getMetricsCategory()81 public int getMetricsCategory() { 82 return SettingsEnums.SOUND; 83 } 84 85 @Override onCreate(Bundle savedInstanceState)86 public void onCreate(Bundle savedInstanceState) { 87 super.onCreate(savedInstanceState); 88 if (savedInstanceState != null) { 89 String selectedPreference = savedInstanceState.getString(SELECTED_PREFERENCE_KEY, null); 90 if (!TextUtils.isEmpty(selectedPreference)) { 91 mRequestPreference = (RingtonePreference) findPreference(selectedPreference); 92 } 93 94 UpdatableListPreferenceDialogFragment dialogFragment = 95 (UpdatableListPreferenceDialogFragment) getFragmentManager() 96 .findFragmentByTag(TAG); 97 mDialogFragment = dialogFragment; 98 } 99 } 100 101 @Override getHelpResource()102 public int getHelpResource() { 103 return R.string.help_url_sound; 104 } 105 106 @Override onPause()107 public void onPause() { 108 super.onPause(); 109 mVolumeCallback.stopSample(); 110 } 111 112 @Override onPreferenceTreeClick(Preference preference)113 public boolean onPreferenceTreeClick(Preference preference) { 114 if (preference instanceof RingtonePreference) { 115 writePreferenceClickMetric(preference); 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 184 use(HandsFreeProfileOutputPreferenceController.class).setCallback(listPreference -> 185 onPreferenceDataChanged(listPreference)); 186 mHfpOutputControllerKey = 187 use(HandsFreeProfileOutputPreferenceController.class).getPreferenceKey(); 188 189 for (VolumeSeekBarPreferenceController controller : volumeControllers) { 190 controller.setCallback(mVolumeCallback); 191 getSettingsLifecycle().addObserver(controller); 192 } 193 } 194 195 // === Volumes === 196 197 final class VolumePreferenceCallback implements VolumeSeekBarPreference.Callback { 198 private SeekBarVolumizer mCurrent; 199 200 @Override onSampleStarting(SeekBarVolumizer sbv)201 public void onSampleStarting(SeekBarVolumizer sbv) { 202 if (mCurrent != null && mCurrent != sbv) { 203 mCurrent.stopSample(); 204 } 205 mCurrent = sbv; 206 if (mCurrent != null) { 207 mHandler.removeMessages(STOP_SAMPLE); 208 mHandler.sendEmptyMessageDelayed(STOP_SAMPLE, SAMPLE_CUTOFF); 209 } 210 } 211 212 @Override onStreamValueChanged(int stream, int progress)213 public void onStreamValueChanged(int stream, int progress) { 214 if (mCurrent != null) { 215 mHandler.removeMessages(STOP_SAMPLE); 216 mHandler.sendEmptyMessageDelayed(STOP_SAMPLE, SAMPLE_CUTOFF); 217 } 218 } 219 stopSample()220 public void stopSample() { 221 if (mCurrent != null) { 222 mCurrent.stopSample(); 223 } 224 } 225 } 226 buildPreferenceControllers(Context context, SoundSettings fragment, Lifecycle lifecycle)227 private static List<AbstractPreferenceController> buildPreferenceControllers(Context context, 228 SoundSettings fragment, Lifecycle lifecycle) { 229 final List<AbstractPreferenceController> controllers = new ArrayList<>(); 230 231 // Volumes are added via xml 232 233 // === Phone & notification ringtone === 234 controllers.add(new PhoneRingtonePreferenceController(context)); 235 controllers.add(new AlarmRingtonePreferenceController(context)); 236 controllers.add(new NotificationRingtonePreferenceController(context)); 237 238 // === Work Sound Settings === 239 controllers.add(new WorkSoundPreferenceController(context, fragment, lifecycle)); 240 241 // === Other Sound Settings === 242 final DialPadTonePreferenceController dialPadTonePreferenceController = 243 new DialPadTonePreferenceController(context, fragment, lifecycle); 244 final ScreenLockSoundPreferenceController screenLockSoundPreferenceController = 245 new ScreenLockSoundPreferenceController(context, fragment, lifecycle); 246 final ChargingSoundPreferenceController chargingSoundPreferenceController = 247 new ChargingSoundPreferenceController(context, fragment, lifecycle); 248 final DockingSoundPreferenceController dockingSoundPreferenceController = 249 new DockingSoundPreferenceController(context, fragment, lifecycle); 250 final TouchSoundPreferenceController touchSoundPreferenceController = 251 new TouchSoundPreferenceController(context, fragment, lifecycle); 252 final VibrateOnTouchPreferenceController vibrateOnTouchPreferenceController = 253 new VibrateOnTouchPreferenceController(context, fragment, lifecycle); 254 final DockAudioMediaPreferenceController dockAudioMediaPreferenceController = 255 new DockAudioMediaPreferenceController(context, fragment, lifecycle); 256 final BootSoundPreferenceController bootSoundPreferenceController = 257 new BootSoundPreferenceController(context); 258 final EmergencyTonePreferenceController emergencyTonePreferenceController = 259 new EmergencyTonePreferenceController(context, fragment, lifecycle); 260 261 controllers.add(dialPadTonePreferenceController); 262 controllers.add(screenLockSoundPreferenceController); 263 controllers.add(chargingSoundPreferenceController); 264 controllers.add(dockingSoundPreferenceController); 265 controllers.add(touchSoundPreferenceController); 266 controllers.add(vibrateOnTouchPreferenceController); 267 controllers.add(dockAudioMediaPreferenceController); 268 controllers.add(bootSoundPreferenceController); 269 controllers.add(emergencyTonePreferenceController); 270 controllers.add(new PreferenceCategoryController(context, 271 "other_sounds_and_vibrations_category").setChildren( 272 Arrays.asList(dialPadTonePreferenceController, 273 screenLockSoundPreferenceController, 274 chargingSoundPreferenceController, 275 dockingSoundPreferenceController, 276 touchSoundPreferenceController, 277 vibrateOnTouchPreferenceController, 278 dockAudioMediaPreferenceController, 279 bootSoundPreferenceController, 280 emergencyTonePreferenceController))); 281 282 return controllers; 283 } 284 285 // === Indexing === 286 287 public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 288 new BaseSearchIndexProvider(R.xml.sound_settings) { 289 290 @Override 291 public List<AbstractPreferenceController> createPreferenceControllers( 292 Context context) { 293 return buildPreferenceControllers(context, null /* fragment */, 294 null /* lifecycle */); 295 } 296 }; 297 298 // === Work Sound Settings === 299 enableWorkSync()300 void enableWorkSync() { 301 final WorkSoundPreferenceController workSoundController = 302 use(WorkSoundPreferenceController.class); 303 if (workSoundController != null) { 304 workSoundController.enableWorkSync(); 305 } 306 } 307 onPreferenceDataChanged(ListPreference preference)308 private void onPreferenceDataChanged(ListPreference preference) { 309 if (mDialogFragment != null) { 310 mDialogFragment.onListPreferenceUpdated(preference); 311 } 312 } 313 }