1 /*
2  * Copyright (C) 2021 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.annotation.UserIdInt;
20 import android.app.Dialog;
21 import android.app.settings.SettingsEnums;
22 import android.content.BroadcastReceiver;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.media.Ringtone;
28 import android.media.RingtoneManager;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.os.UserHandle;
32 import android.os.UserManager;
33 import android.provider.Settings;
34 
35 import androidx.annotation.VisibleForTesting;
36 import androidx.appcompat.app.AlertDialog;
37 import androidx.fragment.app.FragmentManager;
38 import androidx.preference.Preference;
39 import androidx.preference.PreferenceGroup;
40 import androidx.preference.PreferenceScreen;
41 import androidx.preference.TwoStatePreference;
42 
43 import com.android.settings.DefaultRingtonePreference;
44 import com.android.settings.R;
45 import com.android.settings.Utils;
46 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
47 import com.android.settingslib.core.AbstractPreferenceController;
48 import com.android.settingslib.core.lifecycle.Lifecycle;
49 import com.android.settingslib.core.lifecycle.LifecycleObserver;
50 import com.android.settingslib.core.lifecycle.events.OnPause;
51 import com.android.settingslib.core.lifecycle.events.OnResume;
52 
53 /** Controller that manages the Sounds settings relevant preferences for work profile. */
54 public class SoundWorkSettingsController extends AbstractPreferenceController
55         implements Preference.OnPreferenceChangeListener, LifecycleObserver, OnResume, OnPause {
56 
57     private static final String TAG = "SoundWorkSettingsController";
58     private static final String KEY_WORK_USE_PERSONAL_SOUNDS = "work_use_personal_sounds";
59     private static final String KEY_WORK_PHONE_RINGTONE = "work_ringtone";
60     private static final String KEY_WORK_NOTIFICATION_RINGTONE = "work_notification_ringtone";
61     private static final String KEY_WORK_ALARM_RINGTONE = "work_alarm_ringtone";
62 
63     private final boolean mVoiceCapable;
64     private final UserManager mUserManager;
65     private final SoundWorkSettings mParent;
66     private final AudioHelper mHelper;
67 
68     private TwoStatePreference mWorkUsePersonalSounds;
69     private Preference mWorkPhoneRingtonePreference;
70     private Preference mWorkNotificationRingtonePreference;
71     private Preference mWorkAlarmRingtonePreference;
72     private PreferenceScreen mScreen;
73 
74     @UserIdInt
75     private int mManagedProfileId;
76     private final BroadcastReceiver mManagedProfileReceiver = new BroadcastReceiver() {
77         @Override
78         public void onReceive(Context context, Intent intent) {
79             final int userId = ((UserHandle) intent.getExtra(Intent.EXTRA_USER)).getIdentifier();
80             switch (intent.getAction()) {
81                 case Intent.ACTION_MANAGED_PROFILE_ADDED: {
82                     onManagedProfileAdded(userId);
83                     return;
84                 }
85                 case Intent.ACTION_MANAGED_PROFILE_REMOVED: {
86                     onManagedProfileRemoved(userId);
87                     return;
88                 }
89             }
90         }
91     };
92 
SoundWorkSettingsController(Context context, SoundWorkSettings parent, Lifecycle lifecycle)93     public SoundWorkSettingsController(Context context, SoundWorkSettings parent,
94             Lifecycle lifecycle) {
95         this(context, parent, lifecycle, new AudioHelper(context));
96     }
97 
98     @VisibleForTesting
SoundWorkSettingsController(Context context, SoundWorkSettings parent, Lifecycle lifecycle, AudioHelper helper)99     SoundWorkSettingsController(Context context, SoundWorkSettings parent, Lifecycle lifecycle,
100             AudioHelper helper) {
101         super(context);
102         mUserManager = UserManager.get(context);
103         mVoiceCapable = Utils.isVoiceCapable(mContext);
104         mParent = parent;
105         mHelper = helper;
106         if (lifecycle != null) {
107             lifecycle.addObserver(this);
108         }
109     }
110 
111     @Override
displayPreference(PreferenceScreen screen)112     public void displayPreference(PreferenceScreen screen) {
113         super.displayPreference(screen);
114         mScreen = screen;
115     }
116 
117     @Override
onResume()118     public void onResume() {
119         IntentFilter managedProfileFilter = new IntentFilter();
120         managedProfileFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
121         managedProfileFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
122         mContext.registerReceiver(mManagedProfileReceiver, managedProfileFilter);
123         mManagedProfileId = mHelper.getManagedProfileId(mUserManager);
124         updateWorkPreferences();
125     }
126 
127     @Override
onPause()128     public void onPause() {
129         mContext.unregisterReceiver(mManagedProfileReceiver);
130     }
131 
132     @Override
isAvailable()133     public boolean isAvailable() {
134         return mHelper.getManagedProfileId(mUserManager) != UserHandle.USER_NULL
135                 && shouldShowRingtoneSettings();
136     }
137 
138     @Override
handlePreferenceTreeClick(Preference preference)139     public boolean handlePreferenceTreeClick(Preference preference) {
140         return false;
141     }
142 
143     @Override
getPreferenceKey()144     public String getPreferenceKey() {
145         return null;
146     }
147 
148     /**
149      * Updates the summary of work preferences
150      *
151      * This controller listens to changes on the work ringtone preferences, identified by keys
152      * "work_ringtone", "work_notification_ringtone" and "work_alarm_ringtone".
153      */
154     @Override
onPreferenceChange(Preference preference, Object newValue)155     public boolean onPreferenceChange(Preference preference, Object newValue) {
156         int ringtoneType;
157         if (KEY_WORK_PHONE_RINGTONE.equals(preference.getKey())) {
158             ringtoneType = RingtoneManager.TYPE_RINGTONE;
159         } else if (KEY_WORK_NOTIFICATION_RINGTONE.equals(preference.getKey())) {
160             ringtoneType = RingtoneManager.TYPE_NOTIFICATION;
161         } else if (KEY_WORK_ALARM_RINGTONE.equals(preference.getKey())) {
162             ringtoneType = RingtoneManager.TYPE_ALARM;
163         } else {
164             return true;
165         }
166 
167         preference.setSummary(updateRingtoneName(getManagedProfileContext(), ringtoneType));
168         return true;
169     }
170 
shouldShowRingtoneSettings()171     private boolean shouldShowRingtoneSettings() {
172         return !mHelper.isSingleVolume();
173     }
174 
updateRingtoneName(Context context, int type)175     private CharSequence updateRingtoneName(Context context, int type) {
176         if (context == null || !mHelper.isUserUnlocked(mUserManager, context.getUserId())) {
177             return mContext.getString(R.string.managed_profile_not_available_label);
178         }
179         Uri ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(context, type);
180         return Ringtone.getTitle(context, ringtoneUri, false /* followSettingsUri */,
181                 /* allowRemote= */ true);
182     }
183 
getManagedProfileContext()184     private Context getManagedProfileContext() {
185         if (mManagedProfileId == UserHandle.USER_NULL) {
186             return null;
187         }
188         return mHelper.createPackageContextAsUser(mManagedProfileId);
189     }
190 
initWorkPreference(PreferenceGroup root, String key)191     private DefaultRingtonePreference initWorkPreference(PreferenceGroup root, String key) {
192         final DefaultRingtonePreference pref = root.findPreference(key);
193         pref.setOnPreferenceChangeListener(this);
194 
195         // Required so that RingtonePickerActivity lists the work profile ringtones
196         pref.setUserId(mManagedProfileId);
197         return pref;
198     }
199 
updateWorkPreferences()200     private void updateWorkPreferences() {
201         if (!isAvailable()) {
202             return;
203         }
204 
205         if (mWorkUsePersonalSounds == null) {
206             mWorkUsePersonalSounds = mScreen.findPreference(KEY_WORK_USE_PERSONAL_SOUNDS);
207             mWorkUsePersonalSounds.setOnPreferenceChangeListener((Preference p, Object value) -> {
208                 if ((boolean) value) {
209                     SoundWorkSettingsController.UnifyWorkDialogFragment.show(mParent);
210                     return false;
211                 } else {
212                     disableWorkSync();
213                     return true;
214                 }
215             });
216         }
217 
218         if (mWorkPhoneRingtonePreference == null) {
219             mWorkPhoneRingtonePreference = initWorkPreference(mScreen,
220                     KEY_WORK_PHONE_RINGTONE);
221         }
222 
223         if (mWorkNotificationRingtonePreference == null) {
224             mWorkNotificationRingtonePreference = initWorkPreference(mScreen,
225                     KEY_WORK_NOTIFICATION_RINGTONE);
226         }
227 
228         if (mWorkAlarmRingtonePreference == null) {
229             mWorkAlarmRingtonePreference = initWorkPreference(mScreen,
230                     KEY_WORK_ALARM_RINGTONE);
231         }
232 
233         if (!mVoiceCapable) {
234             mWorkPhoneRingtonePreference.setVisible(false);
235             mWorkPhoneRingtonePreference = null;
236         }
237 
238         final Context managedProfileContext = getManagedProfileContext();
239         if (Settings.Secure.getIntForUser(managedProfileContext.getContentResolver(),
240                 Settings.Secure.SYNC_PARENT_SOUNDS, /* def= */ 0, mManagedProfileId) == 1) {
241             enableWorkSyncSettings();
242         } else {
243             disableWorkSyncSettings();
244         }
245     }
246 
enableWorkSync()247     void enableWorkSync() {
248         RingtoneManager.enableSyncFromParent(getManagedProfileContext());
249         enableWorkSyncSettings();
250     }
251 
enableWorkSyncSettings()252     private void enableWorkSyncSettings() {
253         mWorkUsePersonalSounds.setChecked(true);
254 
255         if (mWorkPhoneRingtonePreference != null) {
256             mWorkPhoneRingtonePreference.setSummary(R.string.work_sound_same_as_personal);
257         }
258         mWorkNotificationRingtonePreference.setSummary(R.string.work_sound_same_as_personal);
259         mWorkAlarmRingtonePreference.setSummary(R.string.work_sound_same_as_personal);
260     }
261 
disableWorkSync()262     private void disableWorkSync() {
263         RingtoneManager.disableSyncFromParent(getManagedProfileContext());
264         disableWorkSyncSettings();
265     }
266 
disableWorkSyncSettings()267     private void disableWorkSyncSettings() {
268         if (mWorkPhoneRingtonePreference != null) {
269             mWorkPhoneRingtonePreference.setEnabled(true);
270         }
271         mWorkNotificationRingtonePreference.setEnabled(true);
272         mWorkAlarmRingtonePreference.setEnabled(true);
273 
274         updateWorkRingtoneSummaries();
275     }
276 
updateWorkRingtoneSummaries()277     private void updateWorkRingtoneSummaries() {
278         Context managedProfileContext = getManagedProfileContext();
279 
280         if (mWorkPhoneRingtonePreference != null) {
281             mWorkPhoneRingtonePreference.setSummary(
282                     updateRingtoneName(managedProfileContext, RingtoneManager.TYPE_RINGTONE));
283         }
284         mWorkNotificationRingtonePreference.setSummary(
285                 updateRingtoneName(managedProfileContext, RingtoneManager.TYPE_NOTIFICATION));
286         mWorkAlarmRingtonePreference.setSummary(
287                 updateRingtoneName(managedProfileContext, RingtoneManager.TYPE_ALARM));
288     }
289 
290     /**
291      * Update work preferences if work profile added.
292      * @param profileId the profile identifier.
293      */
onManagedProfileAdded(@serIdInt int profileId)294     public void onManagedProfileAdded(@UserIdInt int profileId) {
295         if (mManagedProfileId == UserHandle.USER_NULL) {
296             mManagedProfileId = profileId;
297             updateWorkPreferences();
298         }
299     }
300 
301     /**
302      * Update work preferences if work profile removed.
303      * @param profileId the profile identifier.
304      */
onManagedProfileRemoved(@serIdInt int profileId)305     public void onManagedProfileRemoved(@UserIdInt int profileId) {
306         if (mManagedProfileId == profileId) {
307             mManagedProfileId = mHelper.getManagedProfileId(mUserManager);
308             updateWorkPreferences();
309         }
310     }
311 
312     /**
313      * Dialog to confirm with the user if it's ok to use the personal profile sounds as the work
314      * profile sounds.
315      */
316     public static class UnifyWorkDialogFragment extends InstrumentedDialogFragment
317             implements DialogInterface.OnClickListener {
318         private static final String TAG = "UnifyWorkDialogFragment";
319         private static final int REQUEST_CODE = 200;
320 
321         /**
322          * Show dialog that allows to use the personal profile sounds as the work profile sounds.
323          * @param parent SoundWorkSettings fragment.
324          */
show(SoundWorkSettings parent)325         public static void show(SoundWorkSettings parent) {
326             FragmentManager fm = parent.getFragmentManager();
327             if (fm.findFragmentByTag(TAG) == null) {
328                 UnifyWorkDialogFragment fragment = new UnifyWorkDialogFragment();
329                 fragment.setTargetFragment(parent, REQUEST_CODE);
330                 fragment.show(fm, TAG);
331             }
332         }
333 
334         @Override
getMetricsCategory()335         public int getMetricsCategory() {
336             return SettingsEnums.DIALOG_UNIFY_SOUND_SETTINGS;
337         }
338 
339         @Override
onCreateDialog(Bundle savedInstanceState)340         public Dialog onCreateDialog(Bundle savedInstanceState) {
341             return new AlertDialog.Builder(getActivity())
342                     .setTitle(R.string.work_sync_dialog_title)
343                     .setMessage(R.string.work_sync_dialog_message)
344                     .setPositiveButton(R.string.work_sync_dialog_yes,
345                             SoundWorkSettingsController.UnifyWorkDialogFragment.this)
346                     .setNegativeButton(android.R.string.no, /* listener= */ null)
347                     .create();
348         }
349 
350         @Override
onClick(DialogInterface dialog, int which)351         public void onClick(DialogInterface dialog, int which) {
352             SoundWorkSettings soundWorkSettings = (SoundWorkSettings) getTargetFragment();
353             if (soundWorkSettings.isAdded()) {
354                 soundWorkSettings.enableWorkSync();
355             }
356         }
357     }
358 }
359