Loading core/java/android/app/IProfileManager.aidl +13 −6 Original line number Diff line number Diff line Loading @@ -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(); Loading @@ -37,4 +45,3 @@ interface IProfileManager NotificationGroup getNotificationGroupForPackage(in String pkg); NotificationGroup getNotificationGroup(in String name); } core/java/android/app/Profile.java +75 −9 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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>(); Loading @@ -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) { Loading Loading @@ -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( Loading @@ -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); Loading @@ -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 Loading @@ -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); } Loading @@ -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); Loading core/java/android/app/ProfileManager.java +45 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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); } Loading Loading @@ -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 { Loading policy/src/com/android/internal/policy/impl/GlobalActions.java +5 −3 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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; } Loading @@ -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, Loading services/java/com/android/server/ProfileManagerService.java +177 −36 Original line number Diff line number Diff line Loading @@ -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>(); Loading Loading @@ -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 Loading @@ -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 Loading @@ -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. Loading @@ -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. Loading @@ -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())) { Loading @@ -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); Loading @@ -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 { Loading @@ -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); } Loading @@ -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."); } } Loading
core/java/android/app/IProfileManager.aidl +13 −6 Original line number Diff line number Diff line Loading @@ -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(); Loading @@ -37,4 +45,3 @@ interface IProfileManager NotificationGroup getNotificationGroupForPackage(in String pkg); NotificationGroup getNotificationGroup(in String name); }
core/java/android/app/Profile.java +75 −9 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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>(); Loading @@ -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) { Loading Loading @@ -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( Loading @@ -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); Loading @@ -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 Loading @@ -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); } Loading @@ -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); Loading
core/java/android/app/ProfileManager.java +45 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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); } Loading Loading @@ -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 { Loading
policy/src/com/android/internal/policy/impl/GlobalActions.java +5 −3 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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; } Loading @@ -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, Loading
services/java/com/android/server/ProfileManagerService.java +177 −36 Original line number Diff line number Diff line Loading @@ -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>(); Loading Loading @@ -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 Loading @@ -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 Loading @@ -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. Loading @@ -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. Loading @@ -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())) { Loading @@ -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); Loading @@ -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 { Loading @@ -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); } Loading @@ -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."); } }