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 }