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

Commit 3f386f9a authored by Steve Kondik's avatar Steve Kondik Committed by Gerrit Code Review
Browse files

Merge "Significant updates to the Profile framework" into gingerbread

parents 252b59a3 b17439b4
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.");
    }

}