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