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