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