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