Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 08b4973f authored by Oli Thompson's avatar Oli Thompson
Browse files

Update Work profile settings

Change work apps toggle to a primary toggle
make Xprofile contact search toggle disable/enable when work profile is turned off
Add footer and change strings
Add tests

Test: atest ContactSearchPreferenceControllerTest, atest WorkModePreferenceControllerTest

Bug: 253009702 275538029
Change-Id: I3b2044a5fe3f2aff0748d66e701a3f0d7667ab7a
parent d865ccc2
Loading
Loading
Loading
Loading
+4 −6
Original line number Diff line number Diff line
@@ -5873,12 +5873,8 @@
    <string name="add_account_label">Add account</string>
    <!-- Label for the state of the work profile [CHAR LIMIT=80] -->
    <string name="managed_profile_not_available_label">Work profile isn\u2019t available yet</string>
    <!-- This string is the title of a setting. If a user taps the setting, they can turn their work profile on or off. The work profile is a section of their phone that's managed by their employer. "Work" is an adjective. -->
    <string name="work_mode_label">Work profile</string>
    <!-- This string is located under a setting and describes what the setting does. It's letting a user know whether their work profile is on or off, and they can use the setting to turn it on or off. The work profile is a section of their phone that's managed by their employer. "Work" is an adjective.-->
    <string name="work_mode_on_summary">Managed by your organization</string>
    <!-- This string is located under a setting and describes what the setting does. It's letting a user know whether their work profile is on or off, and they can use the setting to turn it on or off. The work profile is a section of their phone that's managed by their employer. "Work" is an adjective.-->
    <string name="work_mode_off_summary">Apps and notifications are off</string>
    <!-- This string is the title of a setting. If a user taps the setting, they can turn their work apps on or off. The work apps are a group of apps that are managed by the the user's employer. While this setting is off, the user cannot interact with those apps or get notifications from them. "Work" is an adjective. -->
    <string name="work_mode_label">Work apps</string>
    <!-- Button label to remove the work profile [CHAR LIMIT=35] -->
    <string name="remove_managed_profile_label">Remove work profile</string>
    <!-- Data synchronization settings screen, title of setting that controls whether background data should be used [CHAR LIMIT=30] -->
@@ -9779,6 +9775,8 @@
    <string name="cross_profile_calendar_title">Cross-profile calendar</string>
    <!-- [CHAR LIMIT=NONE] Setting description. If the user turns on this setting, they can see their work events on their personal calendar. -->
    <string name="cross_profile_calendar_summary">Show work events on your personal calendar</string>
    <!-- [CHAR_LIMIT_NONE] Footer description. Explains to the user what will happen when work apps are turned off. -->
    <string name="managed_profile_settings_footer">When work apps are off, they’re paused and can’t be accessed or send you notifications</string>
    <!-- Used as title on the automatic storage manager settings. [CHAR LIMIT=60] -->
    <string name="automatic_storage_manager_settings">Manage storage</string>
+6 −2
Original line number Diff line number Diff line
@@ -19,10 +19,9 @@
                  android:key="managed_profile_settings_screen"
                  android:title="@string/managed_profile_settings_title">

    <SwitchPreference
    <com.android.settingslib.widget.MainSwitchPreference
        android:key="work_mode"
        android:title="@string/work_mode_label"
        android:summary="@string/summary_placeholder"
        settings:controller="com.android.settings.accounts.WorkModePreferenceController"/>

    <com.android.settingslib.RestrictedSwitchPreference
@@ -38,4 +37,9 @@
        android:title="@string/cross_profile_calendar_title"
        settings:controller="com.android.settings.accounts.CrossProfileCalendarPreferenceController"/>

    <com.android.settingslib.widget.FooterPreference
        android:title="@string/managed_profile_settings_footer"
        android:key="managed_profile_footer"
        settings:searchable="false"/>

</PreferenceScreen>
 No newline at end of file
+54 −25
Original line number Diff line number Diff line
@@ -20,37 +20,38 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;

import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.TogglePreferenceController;
import com.android.settings.slices.SliceData;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.RestrictedSwitchPreference;

public class ContactSearchPreferenceController extends BasePreferenceController implements
        Preference.OnPreferenceChangeListener {
import org.jetbrains.annotations.NotNull;

    private UserHandle mManagedUser;
public class ContactSearchPreferenceController extends TogglePreferenceController implements
        Preference.OnPreferenceChangeListener, DefaultLifecycleObserver,
        ManagedProfileQuietModeEnabler.QuietModeChangeListener {

    private final ManagedProfileQuietModeEnabler mQuietModeEnabler;
    private final UserHandle mManagedUser;
    private Preference mPreference;

    public ContactSearchPreferenceController(Context context, String key) {
        super(context, key);
        // Set default managed profile for the current user, otherwise isAvailable will be false and
        // the setting won't be searchable.
        UserManager userManager = context.getSystemService(UserManager.class);
        mManagedUser = Utils.getManagedProfile(userManager);
    }

    @VisibleForTesting
    void setManagedUser(UserHandle managedUser) {
        mManagedUser = managedUser;
        mManagedUser = Utils.getManagedProfile(context.getSystemService(UserManager.class));
        mQuietModeEnabler = new ManagedProfileQuietModeEnabler(context, this);
    }

    @Override
    public int getAvailabilityStatus() {
        return (mManagedUser != null) ? AVAILABLE : DISABLED_FOR_USER;
        return mQuietModeEnabler.isAvailable() ? AVAILABLE : DISABLED_FOR_USER;
    }

    @Override
@@ -59,6 +60,7 @@ public class ContactSearchPreferenceController extends BasePreferenceController
        if (preference instanceof RestrictedSwitchPreference) {
            final RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;
            pref.setChecked(isChecked());
            pref.setEnabled(!mQuietModeEnabler.isQuietModeEnabled());
            if (mManagedUser != null) {
                final RestrictedLockUtils.EnforcedAdmin enforcedAdmin =
                        RestrictedLockUtilsInternal.checkIfRemoteContactSearchDisallowed(
@@ -68,26 +70,48 @@ public class ContactSearchPreferenceController extends BasePreferenceController
        }
    }

    private boolean isChecked() {
        if (mManagedUser == null) {
    @Override
    public void displayPreference(PreferenceScreen screen) {
        super.displayPreference(screen);
        mPreference = screen.findPreference(getPreferenceKey());
        updateState(mPreference);
    }

    @Override
    public void onStart(@NotNull LifecycleOwner lifecycleOwner) {
        lifecycleOwner.getLifecycle().addObserver(mQuietModeEnabler);
    }

    @Override
    public void onStop(@NotNull LifecycleOwner lifecycleOwner) {
        lifecycleOwner.getLifecycle().removeObserver(mQuietModeEnabler);
    }

    @Override
    public boolean isChecked() {
        if (mManagedUser == null || mQuietModeEnabler.isQuietModeEnabled()) {
            return false;
        }
        return 0 != Settings.Secure.getIntForUser(mContext.getContentResolver(),
                MANAGED_PROFILE_CONTACT_REMOTE_SEARCH, 0, mManagedUser.getIdentifier());
    }

    private boolean setChecked(boolean isChecked) {
        if (mManagedUser != null) {
    @Override
    public boolean setChecked(boolean isChecked) {
        if (mManagedUser == null || mQuietModeEnabler.isQuietModeEnabled()) {
            return false;
        }
        final int value = isChecked ? 1 : 0;
        Settings.Secure.putIntForUser(mContext.getContentResolver(),
                MANAGED_PROFILE_CONTACT_REMOTE_SEARCH, value, mManagedUser.getIdentifier());
        }
        return true;
    }

    @Override
    public final boolean onPreferenceChange(Preference preference, Object newValue) {
        return setChecked((boolean) newValue);
    public void onQuietModeChanged() {
        if (mPreference != null) {
            updateState(mPreference);
        }
    }

    @Override
@@ -95,4 +119,9 @@ public class ContactSearchPreferenceController extends BasePreferenceController
    public int getSliceType() {
        return SliceData.SliceType.SWITCH;
    }

    @Override
    public int getSliceHighlightMenuRes() {
        return R.string.menu_key_accounts;
    }
}
+122 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.accounts;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;

import com.android.settings.Utils;

import javax.annotation.Nullable;

/**
 * A class that controls the managed profile's quiet mode, listens to quiet mode changes and
 * modifies managed profile settings. The user facing term for quiet mode is "work apps".
 */
final class ManagedProfileQuietModeEnabler implements DefaultLifecycleObserver {

    private static final String TAG = "QuietModeEnabler";
    private final Context mContext;
    private final QuietModeChangeListener mListener;
    @Nullable private final UserHandle mManagedProfile;
    private final UserManager mUserManager;

    public interface QuietModeChangeListener {
        /** Called when quiet mode has changed. */
        void onQuietModeChanged();
    }

    ManagedProfileQuietModeEnabler(Context context, QuietModeChangeListener listener) {
        mContext = context;
        mListener = listener;
        mUserManager = context.getSystemService(UserManager.class);
        mManagedProfile = Utils.getManagedProfile(mUserManager);
    }

    public void setQuietModeEnabled(boolean enabled) {
        if (mManagedProfile != null) {
            mUserManager.requestQuietModeEnabled(enabled, mManagedProfile);
        }
    }

    public boolean isQuietModeEnabled() {
        return mManagedProfile != null && mUserManager.isQuietModeEnabled(mManagedProfile);
    }

    @Override
    public void onStart(@NonNull LifecycleOwner owner) {
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
        intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
        mContext.registerReceiver(mReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED);
    }

    @Override
    public void onStop(@NonNull LifecycleOwner owner) {
        mContext.unregisterReceiver(mReceiver);
    }

    public boolean isAvailable() {
        return (mManagedProfile != null);
    }

    private void refreshQuietMode() {
        if (mListener != null) {
            mListener.onQuietModeChanged();
        }
    }

    /**
     * Receiver that listens to {@link Intent#ACTION_MANAGED_PROFILE_AVAILABLE} and
     * {@link Intent#ACTION_MANAGED_PROFILE_UNAVAILABLE}, and updates the work mode
     */
    @VisibleForTesting
    final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent == null) {
                return;
            }
            String action = intent.getAction();
            Log.v(TAG, "Received broadcast: " + action);

            if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)
                    || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
                int intentUserIdentifier = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
                        UserHandle.USER_NULL);
                if (intentUserIdentifier == mManagedProfile.getIdentifier()) {
                    refreshQuietMode();
                } else {
                    Log.w(TAG, "Managed profile broadcast ID: " + intentUserIdentifier
                            + " does not match managed user: " + mManagedProfile);
                }
            } else {
                Log.w(TAG, "Cannot handle received broadcast: " + intent.getAction());
            }
        }
    };
}
+41 −116
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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
 * 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.
 * 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.accounts;

import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SETTING_OFF_SUMMARY;
import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SETTING_ON_SUMMARY;

import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;

import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;

import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.slices.SliceData;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
import com.android.settings.widget.SettingsMainSwitchPreferenceController;
import com.android.settingslib.widget.MainSwitchPreference;

public class WorkModePreferenceController extends BasePreferenceController implements
        Preference.OnPreferenceChangeListener, LifecycleObserver, OnStart, OnStop {
import org.jetbrains.annotations.NotNull;

    private static final String TAG = "WorkModeController";

    private UserManager mUserManager;
    private UserHandle mManagedUser;
    private DevicePolicyManager mDevicePolicyManager;
public class WorkModePreferenceController extends SettingsMainSwitchPreferenceController
        implements Preference.OnPreferenceChangeListener, DefaultLifecycleObserver,
        ManagedProfileQuietModeEnabler.QuietModeChangeListener {

    private Preference mPreference;
    private IntentFilter mIntentFilter;
    private final ManagedProfileQuietModeEnabler mQuietModeEnabler;

    public WorkModePreferenceController(Context context, String key) {
        super(context, key);
        mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
        mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
        mIntentFilter = new IntentFilter();
        mIntentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
        mIntentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
        // Set default managed profile for the current user, otherwise isAvailable will be false and
        // the setting won't be searchable.
        mManagedUser = Utils.getManagedProfile(mUserManager);
    }

    @VisibleForTesting
    void setManagedUser(UserHandle managedUser) {
        mManagedUser = managedUser;
        mQuietModeEnabler = new ManagedProfileQuietModeEnabler(context, this);
    }

    @Override
    public void onStart() {
        mContext.registerReceiver(mReceiver, mIntentFilter,
                Context.RECEIVER_EXPORTED_UNAUDITED);
    public int getAvailabilityStatus() {
        return (mQuietModeEnabler.isAvailable()) ? AVAILABLE : DISABLED_FOR_USER;
    }

    @Override
    public void onStop() {
        mContext.unregisterReceiver(mReceiver);
    public void onStart(@NotNull LifecycleOwner lifecycleOwner) {
        lifecycleOwner.getLifecycle().addObserver(mQuietModeEnabler);
    }

    @Override
    public int getAvailabilityStatus() {
        return (mManagedUser != null) ? AVAILABLE : DISABLED_FOR_USER;
    public void onStop(@NotNull LifecycleOwner lifecycleOwner) {
        lifecycleOwner.getLifecycle().removeObserver(mQuietModeEnabler);
    }

    @Override
    public void displayPreference(PreferenceScreen screen) {
        super.displayPreference(screen);
        mPreference = screen.findPreference(getPreferenceKey());
    public boolean isChecked() {
        return !mQuietModeEnabler.isQuietModeEnabled();
    }

    @Override
    public CharSequence getSummary() {
        if (isChecked()) {
            return mDevicePolicyManager.getResources().getString(
                    WORK_PROFILE_SETTING_ON_SUMMARY,
                    () -> mContext.getString(R.string.work_mode_on_summary));
        }

        return mDevicePolicyManager.getResources().getString(
                WORK_PROFILE_SETTING_OFF_SUMMARY,
                        () -> mContext.getString(R.string.work_mode_off_summary));
    }

    private boolean isChecked() {
        boolean isWorkModeOn = false;
        if (mUserManager != null && mManagedUser != null) {
            isWorkModeOn = !mUserManager.isQuietModeEnabled(mManagedUser);
        }
        return isWorkModeOn;
    }

    private boolean setChecked(boolean isChecked) {
        if (mUserManager != null && mManagedUser != null) {
            final boolean quietModeEnabled = !isChecked;
            mUserManager.requestQuietModeEnabled(quietModeEnabled, mManagedUser);
        }
    public boolean setChecked(boolean isChecked) {
        mQuietModeEnabler.setQuietModeEnabled(!isChecked);
        return true;
    }

    @Override
    public void updateState(Preference preference) {
        super.updateState(preference);
        if (preference instanceof TwoStatePreference) {
            ((TwoStatePreference) preference).setChecked(isChecked());
        }
    public void onQuietModeChanged() {
        updateState(mSwitchPreference);
    }

    @Override
    public final boolean onPreferenceChange(Preference preference, Object newValue) {
        return setChecked((boolean) newValue);
    @SliceData.SliceType
    public int getSliceType() {
        return SliceData.SliceType.SWITCH;
    }

    /**
     * Receiver that listens to {@link Intent#ACTION_MANAGED_PROFILE_AVAILABLE} and
     * {@link Intent#ACTION_MANAGED_PROFILE_UNAVAILABLE}, and updates the work mode
     */
    @VisibleForTesting
    final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    @Override
        public void onReceive(Context context, Intent intent) {
            if (intent == null) {
                return;
            }
            final String action = intent.getAction();
            Log.v(TAG, "Received broadcast: " + action);

            if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)
                    || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
                if (intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
                        UserHandle.USER_NULL) == mManagedUser.getIdentifier()) {
                    updateState(mPreference);
    public int getSliceHighlightMenuRes() {
        return R.string.menu_key_accounts;
    }
                return;
            }
            Log.w(TAG, "Cannot handle received broadcast: " + intent.getAction());
        }
    };

    @Override
    @SliceData.SliceType
    public int getSliceType() {
        return SliceData.SliceType.SWITCH;
    @VisibleForTesting
    void setPreference(MainSwitchPreference preference) {
        mSwitchPreference = preference;
    }
}
Loading