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