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

Commit b17439b4 authored by Jeffrey Boone's avatar Jeffrey Boone
Browse files

Significant updates to the Profile framework

Needed By: Additional updates to the Profile framework
packages/apps/Settings project
Related Change ID: I9db0e576f5ecd1a9eb7f1efb16c61603268d1297

These changes include:
- Profiles are now referred to by UUIDs instead of name values.
- Changes to profiles now require the android.permission.WRITE_SETTINGS
permission.
- When a new profile has been selected, the
android.intent.action.PROFILE_SELECTED
broadcast intent is sent with extra string data for the name, UUID,
lastName, and lastUuid (lastName and lastUuid representing the last
active profile, before the switch).
- Calling identities are cleared for actions that may require
additional permissions. Specifically, this includes the broadcast
intent and the persist() function.
- Various tweaks for the upcoming Status Bar Indicator, as requested
by Sven Dawitz.

Change-Id: I9f5ba03896ac2af5405fdb2075889247913771ad
parent 7fc2bb7f
Loading
Loading
Loading
Loading
+13 −6
Original line number Diff line number Diff line
@@ -19,16 +19,24 @@ package android.app;

import android.app.Profile;
import android.app.NotificationGroup;
import android.os.ParcelUuid;

/** {@hide} */
interface IProfileManager
{
    void setActiveProfile(in String profileName);
    boolean setActiveProfile(in ParcelUuid profileParcelUuid);
    boolean setActiveProfileByName(String profileName);
    Profile getActiveProfile();
    void addProfile(in Profile profile);
    void removeProfile(in Profile profile);
    Profile getProfile(String profileName);

    boolean addProfile(in Profile profile);
    boolean removeProfile(in Profile profile);

    Profile getProfile(in ParcelUuid profileParcelUuid);
    Profile getProfileByName(String profileName);
    Profile[] getProfiles();
    boolean profileExists(in ParcelUuid profileUuid);
    boolean profileExistsByName(String profileName);

    void persist();

    NotificationGroup[] getNotificationGroups();
@@ -37,4 +45,3 @@ interface IProfileManager
    NotificationGroup getNotificationGroupForPackage(in String pkg);
    NotificationGroup getNotificationGroup(in String name);
}
+75 −9
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.content.res.XmlResourceParser;
import android.media.AudioManager;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.ParcelUuid;
import android.text.TextUtils;
import android.util.Log;

@@ -31,15 +32,20 @@ import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class Profile implements Parcelable {

    private String mName;

    private UUID mUuid;

    private Map<String, ProfileGroup> profileGroups = new HashMap<String, ProfileGroup>();

    private ProfileGroup mDefaultGroup;

    private boolean mStatusBarIndicator = false;

    private static final String TAG = "Profile";

    private Map<Integer, StreamSettings> streams = new HashMap<Integer, StreamSettings>();
@@ -59,6 +65,13 @@ public class Profile implements Parcelable {
    /** @hide */
    public Profile(String name) {
        this.mName = name;
        // Generate a new UUID, since one was not specified.
        this.mUuid = UUID.randomUUID();
    }

    public Profile(String name, UUID uuid) {
        this.mName = name;
        this.mUuid = uuid;
    }

    private Profile(Parcel in) {
@@ -117,6 +130,8 @@ public class Profile implements Parcelable {
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mName);
        new ParcelUuid(mUuid).writeToParcel(dest, 0);
        dest.writeInt(mStatusBarIndicator ? 1 : 0);
        dest.writeParcelableArray(
                profileGroups.values().toArray(new Parcelable[profileGroups.size()]), flags);
        dest.writeParcelableArray(
@@ -126,6 +141,8 @@ public class Profile implements Parcelable {
    /** @hide */
    public void readFromParcel(Parcel in) {
        mName = in.readString();
        mUuid = ParcelUuid.CREATOR.createFromParcel(in).getUuid();
        mStatusBarIndicator = (in.readInt() == 1);
        for (Parcelable group : in.readParcelableArray(null)) {
            ProfileGroup grp = (ProfileGroup) group;
            profileGroups.put(grp.getName(), grp);
@@ -148,6 +165,19 @@ public class Profile implements Parcelable {
        this.mName = name;
    }

    public UUID getUuid() {
        if (this.mUuid == null) this.mUuid = UUID.randomUUID();
        return this.mUuid;
    }

    public boolean getStatusBarIndicator() {
        return mStatusBarIndicator;
    }

    public void setStatusBarIndicator(boolean newStatusBarIndicator) {
        mStatusBarIndicator = newStatusBarIndicator;
    }

    /** @hide */
    public Notification processNotification(String groupName, Notification notification) {
        ProfileGroup profileGroupSettings = groupName == null ? mDefaultGroup : profileGroups
@@ -165,7 +195,16 @@ public class Profile implements Parcelable {

    /** @hide */
    public void getXmlString(StringBuilder builder) {
        builder.append("<profile name=\"" + TextUtils.htmlEncode(getName()) + "\">\n");
        builder.append("<profile name=\"");
        builder.append(TextUtils.htmlEncode(getName()));
        builder.append("\" uuid=\"");
        builder.append(TextUtils.htmlEncode(getUuid().toString()));
        builder.append("\">\n");

        builder.append("<statusbar>");
        builder.append(getStatusBarIndicator() ? "yes" : "no");
        builder.append("</statusbar>\n");

        for (ProfileGroup pGroup : profileGroups.values()) {
            pGroup.getXmlString(builder);
        }
@@ -177,30 +216,57 @@ public class Profile implements Parcelable {

    /** @hide */
    public static String getAttrResString(XmlPullParser xpp, Context context) {
        String attr = null;
        return Profile.getAttrResString(xpp, context, "name");
    }

    /** @hide */
    public static String getAttrResString(XmlPullParser xpp, Context context, String attrib) {
        String response = null;
        if (attrib == null) attrib = "name";
        if (xpp instanceof XmlResourceParser && context != null) {
            XmlResourceParser xrp = (XmlResourceParser) xpp;
            int resId = xrp.getAttributeResourceValue(null, "name", 0);
            int resId = xrp.getAttributeResourceValue(null, attrib, 0);
            if (resId != 0) {
                attr = context.getResources().getString(resId);
                response = context.getResources().getString(resId);
            } else {
                attr = xrp.getAttributeValue(null, "name");
                response = xrp.getAttributeValue(null, attrib);
            }
        } else {
            attr = xpp.getAttributeValue(null, "name");
            response = xpp.getAttributeValue(null, attrib);
        }
        return attr;
        return response;
    }

    /** @hide */
    public static Profile fromXml(XmlPullParser xpp, Context context)
            throws XmlPullParserException, IOException {
        String attr = getAttrResString(xpp, context);
        Profile profile = new Profile(attr);
        String profileName = getAttrResString(xpp, context, "name");
        UUID profileUuid = UUID.randomUUID();
        try {
            profileUuid = UUID.fromString(xpp.getAttributeValue(null, "uuid"));
        } catch (NullPointerException e) {
            Log.w(TAG,
                    "Null Pointer - UUID not found for "
                    + profileName
                    + ".  New UUID generated: "
                    + profileUuid.toString()
                    );
        } catch (IllegalArgumentException e) {
            Log.w(TAG,
                    "UUID not recognized for "
                    + profileName
                    + ".  New UUID generated: "
                    + profileUuid.toString()
                    );
        }
        Profile profile = new Profile(profileName, profileUuid);
        int event = xpp.next();
        while (event != XmlPullParser.END_TAG) {
            if (event == XmlPullParser.START_TAG) {
                String name = xpp.getName();
                if (name.equals("statusbar")) {
                    profile.setStatusBarIndicator(xpp.nextText() == "yes");
                }
                if (name.equals("profileGroup")) {
                    ProfileGroup pg = ProfileGroup.fromXml(xpp, context);
                    profile.addProfileGroup(pg);
+45 −2
Original line number Diff line number Diff line
@@ -16,9 +16,12 @@

package android.app;

import java.util.UUID;

import android.content.Context;
import android.os.Handler;
import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
@@ -50,9 +53,19 @@ public class ProfileManager
        mContext = context;
    }

    @Deprecated
    public void setActiveProfile(String profileName) {
        try {
            getService().setActiveProfile(profileName);
            getService().setActiveProfileByName(profileName);
            getService().persist();
        } catch (RemoteException e) {
            Log.e(TAG, e.getLocalizedMessage(), e);
        }
    }

    public void setActiveProfile(UUID profileUuid) {
        try {
            getService().setActiveProfile(new ParcelUuid(profileUuid));
            getService().persist();
        } catch (RemoteException e) {
            Log.e(TAG, e.getLocalizedMessage(), e);
@@ -87,9 +100,19 @@ public class ProfileManager
        }
    }

    @Deprecated
    public Profile getProfile(String profileName){
        try {
            return getService().getProfile(profileName);
            return getService().getProfileByName(profileName);
        } catch (RemoteException e) {
            Log.e(TAG, e.getLocalizedMessage(), e);
        }
        return null;
    }

    public Profile getProfile(UUID profileUuid) {
        try {
            return getService().getProfile(new ParcelUuid(profileUuid));
        } catch (RemoteException e) {
            Log.e(TAG, e.getLocalizedMessage(), e);
        }
@@ -120,6 +143,26 @@ public class ProfileManager
        return null;
    }

    public boolean profileExists(String profileName) {
        try {
            return getService().profileExistsByName(profileName);
        } catch (RemoteException e) {
            Log.e(TAG, e.getLocalizedMessage(), e);
            // To be on the safe side, we'll return "true", to prevent duplicate profiles from being created.
            return true;
        }
    }

    public boolean profileExists(UUID profileUuid) {
        try {
            return getService().profileExists(new ParcelUuid(profileUuid));
        } catch (RemoteException e) {
            Log.e(TAG, e.getLocalizedMessage(), e);
            // To be on the safe side, we'll return "true", to prevent duplicate profiles from being created.
            return true;
        }
    }

    /** @hide */
    public NotificationGroup[] getNotificationGroups(){
        try {
+5 −3
Original line number Diff line number Diff line
@@ -58,6 +58,7 @@ import android.widget.ImageView;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.UUID;

/**
 * Helper to show the global actions dialog.  Each item is an {@link Action} that
@@ -423,12 +424,13 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac
        final ProfileManager profileManager = (ProfileManager)mContext.getSystemService(Context.PROFILE_SERVICE);

        final Profile[] profiles = profileManager.getProfiles();
        String activeProfile = profileManager.getActiveProfile().getName();
        UUID activeProfile = profileManager.getActiveProfile().getUuid();
        final CharSequence[] names = new CharSequence[profiles.length];

        int i=0;
        int checkedItem = 0;
        for(Profile profile : profiles){
            if(profile.getName().equals(activeProfile)){
            if(profile.getUuid().equals(activeProfile)){
                checkedItem = i;
                mChosenProfile = profile;
            }
@@ -448,7 +450,7 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac
                .setPositiveButton(com.android.internal.R.string.yes,
                        new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                profileManager.setActiveProfile(mChosenProfile.getName());
                                profileManager.setActiveProfile(mChosenProfile.getUuid());
                            }
                        })
                .setNegativeButton(com.android.internal.R.string.no,
+177 −36
Original line number Diff line number Diff line
@@ -24,25 +24,41 @@ import android.app.IProfileManager;
import android.app.NotificationGroup;
import android.app.Profile;
import android.content.Context;
import android.content.Intent;
import android.content.res.XmlResourceParser;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import android.os.ParcelUuid;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/** {@hide} */
public class ProfileManagerService extends IProfileManager.Stub {
    // Enable the below for detailed logging of this class
    private static final boolean LOCAL_LOGV = false;
    /**
     * <p>Broadcast Action: A new profile has been selected. This can be triggered by the user
     * or by calls to the ProfileManagerService / Profile.</p>
     * @hide
     */
    public static final String INTENT_ACTION_PROFILE_SELECTED = "android.intent.action.PROFILE_SELECTED";

    public static final String PERMISSION_CHANGE_SETTINGS = "android.permission.WRITE_SETTINGS";

    private static final String PROFILE_FILENAME = "/data/system/profiles.xml";

    private static final String TAG = "ProfileService";

    private Map<String, Profile> mProfiles = new HashMap<String, Profile>();
    private Map<UUID, Profile> mProfiles = new HashMap<UUID, Profile>();

    // Match UUIDs and names, used for reverse compatibility
    private Map<String, UUID> mProfileNames = new HashMap<String, UUID>();

    private Map<String, NotificationGroup> mGroups = new HashMap<String, NotificationGroup>();

@@ -71,39 +87,121 @@ public class ProfileManagerService extends IProfileManager.Stub {
        }
    }

    // TODO: Could do with returning true/false to convert to exception
    // TODO: Exceptions not supported in aidl.
    @Override
    public void setActiveProfile(String profileName) throws RemoteException {
        setActiveProfile(profileName, true);
    @Deprecated
    public boolean setActiveProfileByName(String profileName) throws RemoteException, SecurityException {
        if (mProfileNames.containsKey(profileName)) {
            if (LOCAL_LOGV) Log.v(TAG, "setActiveProfile(String) found profile name in mProfileNames.");
            return setActiveProfile(mProfiles.get(mProfileNames.get(profileName)), true);
        } else {
            // Since profileName could not be casted into a UUID, we can call it a string.
            Log.w(TAG, "Unable to find profile to set active, based on string: " + profileName);
            return false;
        }
    }

    @Override
    public boolean setActiveProfile(ParcelUuid profileParcelUuid) throws RemoteException, SecurityException {
        UUID profileUuid = profileParcelUuid.getUuid();
        if(mProfiles.containsKey(profileUuid)){
            if (LOCAL_LOGV) Log.v(TAG, "setActiveProfileByUuid(ParcelUuid) found profile UUID in mProfileNames.");
            return setActiveProfile(mProfiles.get(profileUuid), true);
        } else {
            Log.e(TAG, "Cannot set active profile to: " + profileUuid.toString() + " - does not exist.");
            return false;
        }
    }

    private boolean setActiveProfile(UUID profileUuid, boolean doinit) throws RemoteException {
        if(mProfiles.containsKey(profileUuid)){
            if (LOCAL_LOGV) Log.v(TAG, "setActiveProfile(UUID, boolean) found profile UUID in mProfiles.");
            return setActiveProfile(mProfiles.get(profileUuid), doinit);
        } else {
            Log.e(TAG, "Cannot set active profile to: " + profileUuid.toString() + " - does not exist.");
            return false;
        }
    }

    private boolean setActiveProfile(Profile newActiveProfile, boolean doinit) throws RemoteException {
        /*
         * NOTE: Since this is not a public function, and all public functions
         * take either a string or a UUID, the active profile should always be
         * in the collection.  If writing another setActiveProfile which receives
         * a Profile object, run enforceChangePermissions, add the profile to the
         * list, and THEN add it.
         */

    private void setActiveProfile(String profileName, boolean doinit) throws RemoteException {
        if(mProfiles.containsKey(profileName)){
            Log.d(TAG, "Set active profile to: " + profileName);
            mActiveProfile = getProfile(profileName);
        try {
            enforceChangePermissions();
            Log.d(TAG, "Set active profile to: " + newActiveProfile.getUuid().toString() + " - " + newActiveProfile.getName());
            Profile lastProfile = mActiveProfile;
            mActiveProfile = newActiveProfile;
            if(doinit){
                if (LOCAL_LOGV) Log.v(TAG, "setActiveProfile(Profile, boolean) - Running init");

                // Call profile's "doSelect"
                mActiveProfile.doSelect(mContext);

                /*
                 * Clearing the calling identity AFTER the profile doSelect
                 * to reduce security risks based on an external class extending the
                 * Profile class and embedding malicious code to be executed with "system" rights.
                 * This isn't a fool-proof safety measure, but it's better than giving
                 * the child class system-level access by simply calling setActiveProfile.
                 *
                 * We need to clear the permissions to broadcast INTENT_ACTION_PROFILE_SELECTED.
                 */
                long token = clearCallingIdentity();
                // Notify other applications of newly selected profile.
                Intent broadcast = new Intent(INTENT_ACTION_PROFILE_SELECTED);
                broadcast.putExtra("name", mActiveProfile.getName());
                broadcast.putExtra("uuid", mActiveProfile.getUuid().toString());
                broadcast.putExtra("lastName", lastProfile.getName());
                broadcast.putExtra("lastUuid", lastProfile.getUuid().toString());
                mContext.sendBroadcast(broadcast);
                restoreCallingIdentity(token);
            }
        }else{
            Log.e(TAG, "Cannot set active profile to: " + profileName + " - does not exist.");
            return true;
        } catch (Exception ex) {
            ex.printStackTrace();
            return false;
        }
    }

    @Override
    public void addProfile(Profile profile) throws RemoteException {
    public boolean addProfile(Profile profile) throws RemoteException, SecurityException {
        enforceChangePermissions();
        // Make sure this profile has all of the correct groups.
        for (NotificationGroup group : mGroups.values()) {
            profile.ensureProfileGroup(group.getName());
        }
        profile.ensureProfileGroup(
                mContext.getString(com.android.internal.R.string.wildcardProfile), true);
        mProfiles.put(profile.getName(), profile);
        mProfiles.put(profile.getUuid(), profile);
        mProfileNames.put(profile.getName(), profile.getUuid());
        return true;
    }

    @Override
    public Profile getProfile(String profileName) throws RemoteException {
        return mProfiles.get(profileName);
    @Deprecated
    public Profile getProfileByName(String profileName) throws RemoteException {
        if (mProfileNames.containsKey(profileName)) {
            return mProfiles.get(mProfileNames.get(profileName));
        } else if (mProfiles.containsKey(UUID.fromString((profileName)))) {
            return mProfiles.get(UUID.fromString(profileName));
        } else {
            return null;
        }
    }

    @Override
    public Profile getProfile(ParcelUuid profileParcelUuid) {
        UUID profileUuid = profileParcelUuid.getUuid();
        return mProfiles.get(profileUuid);
    }

    public Profile getProfile(UUID profileUuid) {
        return mProfiles.get(profileUuid);
    }

    @Override
@@ -117,8 +215,23 @@ public class ProfileManagerService extends IProfileManager.Stub {
    }

    @Override
    public void removeProfile(Profile profile) throws RemoteException {
        mProfiles.remove(profile.getName());
    public boolean removeProfile(Profile profile) throws RemoteException, SecurityException {
        enforceChangePermissions();
        if (mProfileNames.remove(profile.getName()) != null && mProfiles.remove(profile.getUuid()) != null) {
            return true;
        } else{
            return false;
        }
    }

    @Override
    public boolean profileExists(ParcelUuid profileUuid) throws RemoteException {
        return mProfiles.containsKey(profileUuid.getUuid());
    }

    @Override
    public boolean profileExistsByName(String profileName) throws RemoteException {
        return mProfileNames.containsKey(profileName);
    }

    @Override
@@ -127,7 +240,8 @@ public class ProfileManagerService extends IProfileManager.Stub {
    }

    @Override
    public void addNotificationGroup(NotificationGroup group) throws RemoteException {
    public void addNotificationGroup(NotificationGroup group) throws RemoteException, SecurityException {
        enforceChangePermissions();
        if (mGroups.put(group.getName(), group) == null) {
            // If the above is true, then the ProfileGroup shouldn't exist in
            // the profile. Ensure it is added.
@@ -138,7 +252,8 @@ public class ProfileManagerService extends IProfileManager.Stub {
    }

    @Override
    public void removeNotificationGroup(NotificationGroup group) throws RemoteException {
    public void removeNotificationGroup(NotificationGroup group) throws RemoteException, SecurityException {
        enforceChangePermissions();
        mGroups.remove(group.getName());
        // Remove the corresponding ProfileGroup from all the profiles too if
        // they use it.
@@ -162,16 +277,21 @@ public class ProfileManagerService extends IProfileManager.Stub {
        XmlPullParser xpp = xppf.newPullParser();
        FileReader fr = new FileReader(PROFILE_FILENAME);
        xpp.setInput(fr);
        loadXml(xpp);
        boolean saveRequired = loadXml(xpp);
        fr.close();
        if (saveRequired) {
            persist();
        }
    }

    private void loadXml(XmlPullParser xpp) throws XmlPullParserException, IOException,
    private boolean loadXml(XmlPullParser xpp) throws XmlPullParserException, IOException,
            RemoteException {
        loadXml(xpp, null);
        return loadXml(xpp, null);
    }

    private void loadXml(XmlPullParser xpp, Context context) throws XmlPullParserException, IOException,
    private boolean loadXml(XmlPullParser xpp, Context context) throws XmlPullParserException, IOException,
            RemoteException {
        boolean saveRequired = false;
        int event = xpp.next();
        String active = null;
        while (event != XmlPullParser.END_TAG || !"profiles".equals(xpp.getName())) {
@@ -185,7 +305,7 @@ public class ProfileManagerService extends IProfileManager.Stub {
                    addProfile(prof);
                    // Failsafe if no active found
                    if (active == null) {
                        active = prof.getName();
                        active = prof.getUuid().toString();
                    }
                } else if (name.equals("notificationGroup")) {
                    NotificationGroup ng = NotificationGroup.fromXml(xpp, context);
@@ -196,7 +316,21 @@ public class ProfileManagerService extends IProfileManager.Stub {
        }
        // Don't do initialisation on startup. The AudioManager doesn't exist yet
        // and besides, the volume settings will have survived the reboot.
        setActiveProfile(active, false);
        try {
            // Try / catch block to detect if XML file needs to be upgraded.
            setActiveProfile(UUID.fromString(active), false);
        } catch (IllegalArgumentException e) {
            if (mProfileNames.containsKey(active)) {
                setActiveProfile(mProfileNames.get(active), false);
            } else {
                // Final fail-safe: We must have SOME profile active.
                // If we couldn't select one by now, we'll pick the first in the set.
                setActiveProfile(mProfiles.values().iterator().next(), false);
            }
            // This is a hint that we probably just upgraded the XML file. Save changes.
            saveRequired = true;
        }
        return saveRequired;
    }

    private void initialiseStructure() throws RemoteException, XmlPullParserException, IOException {
@@ -212,13 +346,10 @@ public class ProfileManagerService extends IProfileManager.Stub {

    private String getXmlString() throws RemoteException {
        StringBuilder builder = new StringBuilder();
        getXmlString(builder);
        return builder.toString();
    }
        builder.append("<profiles>\n<active>");
        builder.append(TextUtils.htmlEncode(getActiveProfile().getUuid().toString()));
        builder.append("</active>\n");

    private void getXmlString(StringBuilder builder) throws RemoteException {
        builder.append("<profiles>\n<active>" + TextUtils.htmlEncode(getActiveProfile().getName())
                + "</active>\n");
        for (Profile p : mProfiles.values()) {
            p.getXmlString(builder);
        }
@@ -226,22 +357,32 @@ public class ProfileManagerService extends IProfileManager.Stub {
            g.getXmlString(builder);
        }
        builder.append("</profiles>\n");
        return builder.toString();
    }

    @Override
    public NotificationGroup getNotificationGroup(String name) throws RemoteException {
        return mGroups.get(name);
    }

    @Override
    public void persist() throws RemoteException {
    public void persist() throws RemoteException, SecurityException {
        enforceChangePermissions();
        long token = clearCallingIdentity();
        try {
            Log.d(TAG, "Saving profile data...");
            FileWriter fw = new FileWriter(PROFILE_FILENAME);
            fw.write(getXmlString());
            fw.close();
            Log.d(TAG, "Save completed.");
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            restoreCallingIdentity(token);
        }
    }

    @Override
    public NotificationGroup getNotificationGroup(String name) throws RemoteException {
        return mGroups.get(name);
    private void enforceChangePermissions() throws SecurityException {
        mContext.enforceCallingOrSelfPermission(PERMISSION_CHANGE_SETTINGS, "You do not have permissions to change the Profile Manager.");
    }

}