diff --git a/res/values/strings.xml b/res/values/strings.xml
index 2e98ec57de3..7018565b942 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -8409,7 +8409,7 @@
Use personal profile sounds
- Sounds are the same for work and personal profiles
+ Use the same sounds as your personal profile
Work phone ringtone
@@ -8424,13 +8424,13 @@
Same as personal profile
- Replace sounds?
+ Use personal profile sounds?
- Replace
+ Confirm
- Your personal profile sounds will be used for your work profile
+ Your work profile will use the same sounds as your personal profile
Add custom sound?
diff --git a/res/xml/sound_settings_v2.xml b/res/xml/sound_settings_v2.xml
index 22a3581bc19..b45dd9bec99 100644
--- a/res/xml/sound_settings_v2.xml
+++ b/res/xml/sound_settings_v2.xml
@@ -212,43 +212,10 @@
android:summary="%s"
android:order="-10"/>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:fragment="com.android.settings.notification.SoundWorkSettings"
+ android:order="100"
+ settings:controller="com.android.settings.notification.WorkSoundsPreferenceController"/>
diff --git a/res/xml/sound_work_settings.xml b/res/xml/sound_work_settings.xml
new file mode 100644
index 00000000000..8e48faca773
--- /dev/null
+++ b/res/xml/sound_work_settings.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/com/android/settings/notification/SoundSettings.java b/src/com/android/settings/notification/SoundSettings.java
index fc209b8cff3..7c665d11e0c 100644
--- a/src/com/android/settings/notification/SoundSettings.java
+++ b/src/com/android/settings/notification/SoundSettings.java
@@ -25,6 +25,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.UserHandle;
import android.preference.SeekBarVolumizer;
+import android.provider.SearchIndexableResource;
import android.text.TextUtils;
import android.util.FeatureFlagUtils;
@@ -250,8 +251,11 @@ public class SoundSettings extends DashboardFragment implements OnActivityResult
controllers.add(new AlarmRingtonePreferenceController(context));
controllers.add(new NotificationRingtonePreferenceController(context));
- // === Work Sound Settings ===
- controllers.add(new WorkSoundPreferenceController(context, fragment, lifecycle));
+ if (!FeatureFlagUtils.isEnabled(context, FeatureFlags.SILKY_HOME)) {
+ // TODO(b/174964721): This should be removed when the flag is deprecated.
+ // === Work Sound Settings ===
+ controllers.add(new WorkSoundPreferenceController(context, fragment, lifecycle));
+ }
// === Other Sound Settings ===
final DialPadTonePreferenceController dialPadTonePreferenceController =
@@ -308,15 +312,27 @@ public class SoundSettings extends DashboardFragment implements OnActivityResult
return buildPreferenceControllers(context, null /* fragment */,
null /* lifecycle */);
}
+
+ @Override
+ public List getXmlResourcesToIndex(
+ Context context, boolean enabled) {
+ final SearchIndexableResource sir = new SearchIndexableResource(context);
+ sir.xmlResId = FeatureFlagUtils.isEnabled(context, FeatureFlags.SILKY_HOME)
+ ? R.xml.sound_settings_v2 : R.xml.sound_settings;
+ return Arrays.asList(sir);
+ }
};
// === Work Sound Settings ===
void enableWorkSync() {
- final WorkSoundPreferenceController workSoundController =
- use(WorkSoundPreferenceController.class);
- if (workSoundController != null) {
- workSoundController.enableWorkSync();
+ // TODO(b/174964721): This should be refined when the flag is deprecated.
+ if (!FeatureFlagUtils.isEnabled(getContext(), FeatureFlags.SILKY_HOME)) {
+ final WorkSoundPreferenceController workSoundController =
+ use(WorkSoundPreferenceController.class);
+ if (workSoundController != null) {
+ workSoundController.enableWorkSync();
+ }
}
}
diff --git a/src/com/android/settings/notification/SoundWorkSettings.java b/src/com/android/settings/notification/SoundWorkSettings.java
new file mode 100644
index 00000000000..9fae72d3175
--- /dev/null
+++ b/src/com/android/settings/notification/SoundWorkSettings.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.FeatureFlagUtils;
+
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.RingtonePreference;
+import com.android.settings.core.FeatureFlags;
+import com.android.settings.core.OnActivityResultListener;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.search.SearchIndexable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Sounds settings for work profile. */
+@SearchIndexable
+public class SoundWorkSettings extends DashboardFragment implements OnActivityResultListener {
+
+ private static final String TAG = "SoundWorkSettings";
+ private static final int REQUEST_CODE = 200;
+ private static final String SELECTED_PREFERENCE_KEY = "selected_preference";
+
+ private RingtonePreference mRequestPreference;
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.WORK_PROFILE_SOUNDS;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState != null) {
+ String selectedPreference = savedInstanceState.getString(
+ SELECTED_PREFERENCE_KEY, /* defaultValue= */ null);
+ if (!TextUtils.isEmpty(selectedPreference)) {
+ mRequestPreference = findPreference(selectedPreference);
+ }
+ }
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(Preference preference) {
+ if (preference instanceof RingtonePreference) {
+ writePreferenceClickMetric(preference);
+ mRequestPreference = (RingtonePreference) preference;
+ mRequestPreference.onPrepareRingtonePickerIntent(mRequestPreference.getIntent());
+ getActivity().startActivityForResultAsUser(
+ mRequestPreference.getIntent(),
+ REQUEST_CODE,
+ /* options= */ null,
+ UserHandle.of(mRequestPreference.getUserId()));
+ return true;
+ }
+ return super.onPreferenceTreeClick(preference);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (mRequestPreference != null) {
+ mRequestPreference.onActivityResult(requestCode, resultCode, data);
+ mRequestPreference = null;
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (mRequestPreference != null) {
+ outState.putString(SELECTED_PREFERENCE_KEY, mRequestPreference.getKey());
+ }
+ }
+
+ @Override
+ protected List createPreferenceControllers(Context context) {
+ return buildPreferenceControllers(context, /* fragment= */ this, getSettingsLifecycle());
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.sound_work_settings;
+ }
+
+ private static List buildPreferenceControllers(Context context,
+ SoundWorkSettings fragment, Lifecycle lifecycle) {
+ final List controllers = new ArrayList<>();
+ controllers.add(new SoundWorkSettingsController(context, fragment, lifecycle));
+ return controllers;
+ }
+
+ static final boolean isSupportWorkProfileSound(Context context) {
+ // TODO(b/174964721): Feature flag should be removed when silky home launched.
+ final boolean isSilkyEnabled = FeatureFlagUtils.isEnabled(context,
+ FeatureFlags.SILKY_HOME);
+
+ final AudioHelper audioHelper = new AudioHelper(context);
+ final boolean hasWorkProfile = audioHelper.getManagedProfileId(
+ UserManager.get(context)) != UserHandle.USER_NULL;
+ final boolean shouldShowRingtoneSettings = !audioHelper.isSingleVolume();
+
+ return isSilkyEnabled && hasWorkProfile && shouldShowRingtoneSettings;
+ }
+
+ void enableWorkSync() {
+ final SoundWorkSettingsController soundWorkSettingsController =
+ use(SoundWorkSettingsController.class);
+ if (soundWorkSettingsController != null) {
+ soundWorkSettingsController.enableWorkSync();
+ }
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.sound_work_settings) {
+ @Override
+ protected boolean isPageSearchEnabled(Context context) {
+ return isSupportWorkProfileSound(context);
+ }
+ };
+}
diff --git a/src/com/android/settings/notification/SoundWorkSettingsController.java b/src/com/android/settings/notification/SoundWorkSettingsController.java
new file mode 100644
index 00000000000..d156795b8d3
--- /dev/null
+++ b/src/com/android/settings/notification/SoundWorkSettingsController.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification;
+
+import android.annotation.UserIdInt;
+import android.app.Dialog;
+import android.app.settings.SettingsEnums;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.FragmentManager;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.TwoStatePreference;
+
+import com.android.settings.DefaultRingtonePreference;
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnPause;
+import com.android.settingslib.core.lifecycle.events.OnResume;
+
+/** Controller that manages the Sounds settings relevant preferences for work profile. */
+public class SoundWorkSettingsController extends AbstractPreferenceController
+ implements Preference.OnPreferenceChangeListener, LifecycleObserver, OnResume, OnPause {
+
+ private static final String TAG = "SoundWorkSettingsController";
+ private static final String KEY_WORK_USE_PERSONAL_SOUNDS = "work_use_personal_sounds";
+ private static final String KEY_WORK_PHONE_RINGTONE = "work_ringtone";
+ private static final String KEY_WORK_NOTIFICATION_RINGTONE = "work_notification_ringtone";
+ private static final String KEY_WORK_ALARM_RINGTONE = "work_alarm_ringtone";
+
+ private final boolean mVoiceCapable;
+ private final UserManager mUserManager;
+ private final SoundWorkSettings mParent;
+ private final AudioHelper mHelper;
+
+ private TwoStatePreference mWorkUsePersonalSounds;
+ private Preference mWorkPhoneRingtonePreference;
+ private Preference mWorkNotificationRingtonePreference;
+ private Preference mWorkAlarmRingtonePreference;
+ private PreferenceScreen mScreen;
+
+ @UserIdInt
+ private int mManagedProfileId;
+ private final BroadcastReceiver mManagedProfileReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int userId = ((UserHandle) intent.getExtra(Intent.EXTRA_USER)).getIdentifier();
+ switch (intent.getAction()) {
+ case Intent.ACTION_MANAGED_PROFILE_ADDED: {
+ onManagedProfileAdded(userId);
+ return;
+ }
+ case Intent.ACTION_MANAGED_PROFILE_REMOVED: {
+ onManagedProfileRemoved(userId);
+ return;
+ }
+ }
+ }
+ };
+
+ public SoundWorkSettingsController(Context context, SoundWorkSettings parent,
+ Lifecycle lifecycle) {
+ this(context, parent, lifecycle, new AudioHelper(context));
+ }
+
+ @VisibleForTesting
+ SoundWorkSettingsController(Context context, SoundWorkSettings parent, Lifecycle lifecycle,
+ AudioHelper helper) {
+ super(context);
+ mUserManager = UserManager.get(context);
+ mVoiceCapable = Utils.isVoiceCapable(mContext);
+ mParent = parent;
+ mHelper = helper;
+ if (lifecycle != null) {
+ lifecycle.addObserver(this);
+ }
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mScreen = screen;
+ }
+
+ @Override
+ public void onResume() {
+ IntentFilter managedProfileFilter = new IntentFilter();
+ managedProfileFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
+ managedProfileFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
+ mContext.registerReceiver(mManagedProfileReceiver, managedProfileFilter);
+ mManagedProfileId = mHelper.getManagedProfileId(mUserManager);
+ updateWorkPreferences();
+ }
+
+ @Override
+ public void onPause() {
+ mContext.unregisterReceiver(mManagedProfileReceiver);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return mHelper.getManagedProfileId(mUserManager) != UserHandle.USER_NULL
+ && shouldShowRingtoneSettings();
+ }
+
+ @Override
+ public boolean handlePreferenceTreeClick(Preference preference) {
+ return false;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return null;
+ }
+
+ /**
+ * Updates the summary of work preferences
+ *
+ * This controller listens to changes on the work ringtone preferences, identified by keys
+ * "work_ringtone", "work_notification_ringtone" and "work_alarm_ringtone".
+ */
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ int ringtoneType;
+ if (KEY_WORK_PHONE_RINGTONE.equals(preference.getKey())) {
+ ringtoneType = RingtoneManager.TYPE_RINGTONE;
+ } else if (KEY_WORK_NOTIFICATION_RINGTONE.equals(preference.getKey())) {
+ ringtoneType = RingtoneManager.TYPE_NOTIFICATION;
+ } else if (KEY_WORK_ALARM_RINGTONE.equals(preference.getKey())) {
+ ringtoneType = RingtoneManager.TYPE_ALARM;
+ } else {
+ return true;
+ }
+
+ preference.setSummary(updateRingtoneName(getManagedProfileContext(), ringtoneType));
+ return true;
+ }
+
+ private boolean shouldShowRingtoneSettings() {
+ return !mHelper.isSingleVolume();
+ }
+
+ private CharSequence updateRingtoneName(Context context, int type) {
+ if (context == null || !mHelper.isUserUnlocked(mUserManager, context.getUserId())) {
+ return mContext.getString(R.string.managed_profile_not_available_label);
+ }
+ Uri ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(context, type);
+ return Ringtone.getTitle(context, ringtoneUri, false /* followSettingsUri */,
+ /* allowRemote= */ true);
+ }
+
+ private Context getManagedProfileContext() {
+ if (mManagedProfileId == UserHandle.USER_NULL) {
+ return null;
+ }
+ return mHelper.createPackageContextAsUser(mManagedProfileId);
+ }
+
+ private DefaultRingtonePreference initWorkPreference(PreferenceGroup root, String key) {
+ final DefaultRingtonePreference pref = root.findPreference(key);
+ pref.setOnPreferenceChangeListener(this);
+
+ // Required so that RingtonePickerActivity lists the work profile ringtones
+ pref.setUserId(mManagedProfileId);
+ return pref;
+ }
+
+ private void updateWorkPreferences() {
+ if (!isAvailable()) {
+ return;
+ }
+
+ if (mWorkUsePersonalSounds == null) {
+ mWorkUsePersonalSounds = mScreen.findPreference(KEY_WORK_USE_PERSONAL_SOUNDS);
+ mWorkUsePersonalSounds.setOnPreferenceChangeListener((Preference p, Object value) -> {
+ if ((boolean) value) {
+ SoundWorkSettingsController.UnifyWorkDialogFragment.show(mParent);
+ return false;
+ } else {
+ disableWorkSync();
+ return true;
+ }
+ });
+ }
+
+ if (mWorkPhoneRingtonePreference == null) {
+ mWorkPhoneRingtonePreference = initWorkPreference(mScreen,
+ KEY_WORK_PHONE_RINGTONE);
+ }
+
+ if (mWorkNotificationRingtonePreference == null) {
+ mWorkNotificationRingtonePreference = initWorkPreference(mScreen,
+ KEY_WORK_NOTIFICATION_RINGTONE);
+ }
+
+ if (mWorkAlarmRingtonePreference == null) {
+ mWorkAlarmRingtonePreference = initWorkPreference(mScreen,
+ KEY_WORK_ALARM_RINGTONE);
+ }
+
+ if (!mVoiceCapable) {
+ mWorkPhoneRingtonePreference.setVisible(false);
+ mWorkPhoneRingtonePreference = null;
+ }
+
+ final Context managedProfileContext = getManagedProfileContext();
+ if (Settings.Secure.getIntForUser(managedProfileContext.getContentResolver(),
+ Settings.Secure.SYNC_PARENT_SOUNDS, /* def= */ 0, mManagedProfileId) == 1) {
+ enableWorkSyncSettings();
+ } else {
+ disableWorkSyncSettings();
+ }
+ }
+
+ void enableWorkSync() {
+ RingtoneManager.enableSyncFromParent(getManagedProfileContext());
+ enableWorkSyncSettings();
+ }
+
+ private void enableWorkSyncSettings() {
+ mWorkUsePersonalSounds.setChecked(true);
+
+ if (mWorkPhoneRingtonePreference != null) {
+ mWorkPhoneRingtonePreference.setSummary(R.string.work_sound_same_as_personal);
+ }
+ mWorkNotificationRingtonePreference.setSummary(R.string.work_sound_same_as_personal);
+ mWorkAlarmRingtonePreference.setSummary(R.string.work_sound_same_as_personal);
+ }
+
+ private void disableWorkSync() {
+ RingtoneManager.disableSyncFromParent(getManagedProfileContext());
+ disableWorkSyncSettings();
+ }
+
+ private void disableWorkSyncSettings() {
+ if (mWorkPhoneRingtonePreference != null) {
+ mWorkPhoneRingtonePreference.setEnabled(true);
+ }
+ mWorkNotificationRingtonePreference.setEnabled(true);
+ mWorkAlarmRingtonePreference.setEnabled(true);
+
+ updateWorkRingtoneSummaries();
+ }
+
+ private void updateWorkRingtoneSummaries() {
+ Context managedProfileContext = getManagedProfileContext();
+
+ if (mWorkPhoneRingtonePreference != null) {
+ mWorkPhoneRingtonePreference.setSummary(
+ updateRingtoneName(managedProfileContext, RingtoneManager.TYPE_RINGTONE));
+ }
+ mWorkNotificationRingtonePreference.setSummary(
+ updateRingtoneName(managedProfileContext, RingtoneManager.TYPE_NOTIFICATION));
+ mWorkAlarmRingtonePreference.setSummary(
+ updateRingtoneName(managedProfileContext, RingtoneManager.TYPE_ALARM));
+ }
+
+ /**
+ * Update work preferences if work profile added.
+ * @param profileId the profile identifier.
+ */
+ public void onManagedProfileAdded(@UserIdInt int profileId) {
+ if (mManagedProfileId == UserHandle.USER_NULL) {
+ mManagedProfileId = profileId;
+ updateWorkPreferences();
+ }
+ }
+
+ /**
+ * Update work preferences if work profile removed.
+ * @param profileId the profile identifier.
+ */
+ public void onManagedProfileRemoved(@UserIdInt int profileId) {
+ if (mManagedProfileId == profileId) {
+ mManagedProfileId = mHelper.getManagedProfileId(mUserManager);
+ updateWorkPreferences();
+ }
+ }
+
+ /**
+ * Dialog to confirm with the user if it's ok to use the personal profile sounds as the work
+ * profile sounds.
+ */
+ public static class UnifyWorkDialogFragment extends InstrumentedDialogFragment
+ implements DialogInterface.OnClickListener {
+ private static final String TAG = "UnifyWorkDialogFragment";
+ private static final int REQUEST_CODE = 200;
+
+ /**
+ * Show dialog that allows to use the personal profile sounds as the work profile sounds.
+ * @param parent SoundWorkSettings fragment.
+ */
+ public static void show(SoundWorkSettings parent) {
+ FragmentManager fm = parent.getFragmentManager();
+ if (fm.findFragmentByTag(TAG) == null) {
+ UnifyWorkDialogFragment fragment = new UnifyWorkDialogFragment();
+ fragment.setTargetFragment(parent, REQUEST_CODE);
+ fragment.show(fm, TAG);
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.DIALOG_UNIFY_SOUND_SETTINGS;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.work_sync_dialog_title)
+ .setMessage(R.string.work_sync_dialog_message)
+ .setPositiveButton(R.string.work_sync_dialog_yes,
+ SoundWorkSettingsController.UnifyWorkDialogFragment.this)
+ .setNegativeButton(android.R.string.no, /* listener= */ null)
+ .create();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ SoundWorkSettings soundWorkSettings = (SoundWorkSettings) getTargetFragment();
+ if (soundWorkSettings.isAdded()) {
+ soundWorkSettings.enableWorkSync();
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/notification/WorkSoundPreferenceController.java b/src/com/android/settings/notification/WorkSoundPreferenceController.java
index e23d9ea5b9c..e257feab2ca 100644
--- a/src/com/android/settings/notification/WorkSoundPreferenceController.java
+++ b/src/com/android/settings/notification/WorkSoundPreferenceController.java
@@ -54,6 +54,9 @@ import com.android.settingslib.core.lifecycle.events.OnResume;
import java.util.List;
+/**
+ * TODO(b/183670633): Remove this file when silky flag deprecated.
+ */
public class WorkSoundPreferenceController extends AbstractPreferenceController
implements PreferenceControllerMixin, OnPreferenceChangeListener, LifecycleObserver,
OnResume, OnPause {
diff --git a/src/com/android/settings/notification/WorkSoundsPreferenceController.java b/src/com/android/settings/notification/WorkSoundsPreferenceController.java
new file mode 100644
index 00000000000..f76f8c31cae
--- /dev/null
+++ b/src/com/android/settings/notification/WorkSoundsPreferenceController.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification;
+
+import android.content.Context;
+
+import com.android.settings.core.BasePreferenceController;
+
+/** This controller manages the work profile sounds preference. */
+public class WorkSoundsPreferenceController extends BasePreferenceController {
+
+ public WorkSoundsPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return SoundWorkSettings.isSupportWorkProfileSound(mContext) ? AVAILABLE
+ : DISABLED_FOR_USER;
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/SoundWorkSettingsControllerTest.java b/tests/robotests/src/com/android/settings/notification/SoundWorkSettingsControllerTest.java
new file mode 100644
index 00000000000..ee0a2ec0ba8
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/SoundWorkSettingsControllerTest.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.telephony.TelephonyManager;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.TwoStatePreference;
+
+import com.android.settings.DefaultRingtonePreference;
+import com.android.settings.R;
+import com.android.settings.RingtonePreference;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class SoundWorkSettingsControllerTest {
+
+ private static final String KEY_WORK_USE_PERSONAL_SOUNDS = "work_use_personal_sounds";
+ private static final String KEY_WORK_PHONE_RINGTONE = "work_ringtone";
+ private static final String KEY_WORK_NOTIFICATION_RINGTONE = "work_notification_ringtone";
+ private static final String KEY_WORK_ALARM_RINGTONE = "work_alarm_ringtone";
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private PreferenceScreen mScreen;
+ @Mock
+ private TelephonyManager mTelephonyManager;
+ @Mock
+ private AudioHelper mAudioHelper;
+ @Mock
+ private SoundWorkSettings mFragment;
+
+ private SoundWorkSettingsController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager);
+ when(mTelephonyManager.isVoiceCapable()).thenReturn(true);
+ when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
+ when(mScreen.findPreference(KEY_WORK_USE_PERSONAL_SOUNDS))
+ .thenReturn(mock(TwoStatePreference.class));
+ when(mScreen.findPreference(KEY_WORK_PHONE_RINGTONE))
+ .thenReturn(mock(DefaultRingtonePreference.class));
+ when(mScreen.findPreference(KEY_WORK_NOTIFICATION_RINGTONE))
+ .thenReturn(mock(DefaultRingtonePreference.class));
+ when(mScreen.findPreference(KEY_WORK_ALARM_RINGTONE))
+ .thenReturn(mock(DefaultRingtonePreference.class));
+
+ mController = new SoundWorkSettingsController(mContext, mFragment, null, mAudioHelper);
+ }
+
+ @Test
+ public void isAvailable_managedProfileAndNotSingleVolume_shouldReturnTrue() {
+ when(mAudioHelper.getManagedProfileId(nullable(UserManager.class)))
+ .thenReturn(UserHandle.myUserId());
+ when(mAudioHelper.isSingleVolume()).thenReturn(false);
+
+ assertThat(mController.isAvailable()).isTrue();
+ }
+
+ @Test
+ public void isAvailable_noManagedProfile_shouldReturnFalse() {
+ when(mAudioHelper.getManagedProfileId(nullable(UserManager.class)))
+ .thenReturn(UserHandle.USER_NULL);
+ when(mAudioHelper.isSingleVolume()).thenReturn(false);
+
+ assertThat(mController.isAvailable()).isFalse();
+ }
+
+ @Test
+ public void isAvailable_singleVolume_shouldReturnFalse() {
+ when(mAudioHelper.getManagedProfileId(nullable(UserManager.class)))
+ .thenReturn(UserHandle.myUserId());
+ when(mAudioHelper.isSingleVolume()).thenReturn(true);
+
+ assertThat(mController.isAvailable()).isFalse();
+ }
+
+ @Test
+ public void onPreferenceChange_shouldUpdateSummary() {
+ final Preference preference = mock(Preference.class);
+ when(preference.getKey()).thenReturn(KEY_WORK_PHONE_RINGTONE);
+
+ mController.onPreferenceChange(preference, "hello");
+
+ verify(preference).setSummary(nullable(String.class));
+ }
+
+ @Test
+ public void onResume_noVoiceCapability_shouldHidePhoneRingtone() {
+ when(mTelephonyManager.isVoiceCapable()).thenReturn(false);
+ mController = new SoundWorkSettingsController(mContext, mFragment, null, mAudioHelper);
+
+ when(mAudioHelper.getManagedProfileId(nullable(UserManager.class)))
+ .thenReturn(UserHandle.myUserId());
+ when(mAudioHelper.isUserUnlocked(nullable(UserManager.class), anyInt())).thenReturn(true);
+ when(mAudioHelper.isSingleVolume()).thenReturn(false);
+ when(mAudioHelper.createPackageContextAsUser(anyInt())).thenReturn(mContext);
+
+ // Precondition: work profile is available.
+ assertThat(mController.isAvailable()).isTrue();
+
+ mController.displayPreference(mScreen);
+ mController.onResume();
+
+ verify((Preference) mScreen.findPreference(KEY_WORK_PHONE_RINGTONE)).setVisible(false);
+ }
+
+ @Test
+ public void onResume_availableButLocked_shouldRedactPreferences() {
+ final String notAvailable = "(not available)";
+ when(mContext.getString(R.string.managed_profile_not_available_label))
+ .thenReturn(notAvailable);
+
+ // Given a device with a managed profile:
+ when(mAudioHelper.isSingleVolume()).thenReturn(false);
+ when(mAudioHelper.createPackageContextAsUser(anyInt())).thenReturn(mContext);
+ when(mAudioHelper.getManagedProfileId(nullable(UserManager.class)))
+ .thenReturn(UserHandle.myUserId());
+ when(mAudioHelper.isUserUnlocked(nullable(UserManager.class), anyInt())).thenReturn(false);
+
+ // When resumed:
+ mController.displayPreference(mScreen);
+ mController.onResume();
+
+ // Sound preferences should explain that the profile isn't available yet.
+ verify((Preference) mScreen.findPreference(KEY_WORK_PHONE_RINGTONE))
+ .setSummary(eq(notAvailable));
+ verify((Preference) mScreen.findPreference(KEY_WORK_NOTIFICATION_RINGTONE))
+ .setSummary(eq(notAvailable));
+ verify((Preference) mScreen.findPreference(KEY_WORK_ALARM_RINGTONE))
+ .setSummary(eq(notAvailable));
+ }
+
+ @Test
+ public void onResume_shouldSetUserIdToPreference() {
+ final int managedProfileUserId = 10;
+ when(mAudioHelper.getManagedProfileId(nullable(UserManager.class)))
+ .thenReturn(managedProfileUserId);
+ when(mAudioHelper.isUserUnlocked(nullable(UserManager.class), anyInt())).thenReturn(true);
+ when(mAudioHelper.isSingleVolume()).thenReturn(false);
+ when(mAudioHelper.createPackageContextAsUser(anyInt())).thenReturn(mContext);
+
+ mController.displayPreference(mScreen);
+ mController.onResume();
+
+ verify((RingtonePreference) mScreen.findPreference(KEY_WORK_PHONE_RINGTONE))
+ .setUserId(managedProfileUserId);
+ verify((RingtonePreference) mScreen.findPreference(KEY_WORK_NOTIFICATION_RINGTONE))
+ .setUserId(managedProfileUserId);
+ verify((RingtonePreference) mScreen.findPreference(KEY_WORK_ALARM_RINGTONE))
+ .setUserId(managedProfileUserId);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/SoundWorkSettingsTest.java b/tests/robotests/src/com/android/settings/notification/SoundWorkSettingsTest.java
new file mode 100644
index 00000000000..6f8b784cb3f
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/SoundWorkSettingsTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.UserHandle;
+
+import androidx.fragment.app.FragmentActivity;
+import androidx.preference.Preference;
+
+import com.android.settings.DefaultRingtonePreference;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.util.ReflectionHelpers;
+
+@RunWith(RobolectricTestRunner.class)
+public class SoundWorkSettingsTest {
+
+ @Mock
+ private FragmentActivity mActivity;
+
+ @Mock
+ private MetricsFeatureProvider mMetricsFeatureProvider;
+
+ private FakeFeatureFactory mFeatureFactory;
+ private SoundWorkSettings mFragment;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mFragment = spy(new SoundWorkSettings());
+ when(mFragment.getActivity()).thenReturn(mActivity);
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
+ mMetricsFeatureProvider = mFeatureFactory.getMetricsFeatureProvider();
+ ReflectionHelpers.setField(mFragment, "mMetricsFeatureProvider", mMetricsFeatureProvider);
+ }
+
+ @Test
+ public void onPreferenceTreeClick_isRingtonePreference_shouldStartActivity() {
+ final DefaultRingtonePreference ringtonePreference = mock(DefaultRingtonePreference.class);
+ when(mMetricsFeatureProvider.logClickedPreference(any(Preference.class),
+ anyInt())).thenReturn(true);
+
+ mFragment.onPreferenceTreeClick(ringtonePreference);
+
+ verify(mActivity).startActivityForResultAsUser(any(), anyInt(), any(),
+ any(UserHandle.class));
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/WorkSoundsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/WorkSoundsPreferenceControllerTest.java
new file mode 100644
index 00000000000..2b380311439
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/WorkSoundsPreferenceControllerTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.core.BasePreferenceController.DISABLED_FOR_USER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.UserHandle;
+import android.util.FeatureFlagUtils;
+
+import com.android.settings.core.FeatureFlags;
+import com.android.settings.testutils.shadow.ShadowAudioHelper;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@Config(shadows = ShadowAudioHelper.class)
+@RunWith(RobolectricTestRunner.class)
+public class WorkSoundsPreferenceControllerTest {
+
+ private Context mContext;
+ private WorkSoundsPreferenceController mController;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mController = new WorkSoundsPreferenceController(mContext, "test_key");
+ }
+
+ @After
+ public void tearDown() {
+ ShadowAudioHelper.reset();
+ }
+
+ @Test
+ public void getAvailabilityStatus_supportWorkProfileSound_shouldReturnAvailable() {
+ FeatureFlagUtils.setEnabled(mContext, FeatureFlags.SILKY_HOME, true);
+ ShadowAudioHelper.setIsSingleVolume(false);
+ ShadowAudioHelper.setManagedProfileId(UserHandle.USER_CURRENT);
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_notSupportWorkProfileSound_shouldReturnDisabled() {
+ ShadowAudioHelper.setIsSingleVolume(true);
+ ShadowAudioHelper.setManagedProfileId(UserHandle.USER_NULL);
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_FOR_USER);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAudioHelper.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAudioHelper.java
index 4a519aaa0f6..0899aebb3ff 100644
--- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAudioHelper.java
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAudioHelper.java
@@ -23,17 +23,35 @@ import com.android.settings.notification.AudioHelper;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
@Implements(AudioHelper.class)
public class ShadowAudioHelper {
+ private static boolean sIsSingleVolume = true;
+ private static int sManagedProfileId = UserHandle.USER_CURRENT;
+
+ @Resetter
+ public static void reset() {
+ sIsSingleVolume = true;
+ sManagedProfileId = UserHandle.USER_CURRENT;
+ }
+
+ public static void setIsSingleVolume(boolean isSingleVolume) {
+ sIsSingleVolume = isSingleVolume;
+ }
+
+ public static void setManagedProfileId(int managedProfileId) {
+ sManagedProfileId = managedProfileId;
+ }
+
@Implementation
protected boolean isSingleVolume() {
- return true;
+ return sIsSingleVolume;
}
@Implementation
protected int getManagedProfileId(UserManager um) {
- return UserHandle.USER_CURRENT;
+ return sManagedProfileId;
}
}