summaryrefslogtreecommitdiffstats
path: root/services/java/com/android/server/ProfileManagerService.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/java/com/android/server/ProfileManagerService.java')
-rw-r--r--services/java/com/android/server/ProfileManagerService.java550
1 files changed, 550 insertions, 0 deletions
diff --git a/services/java/com/android/server/ProfileManagerService.java b/services/java/com/android/server/ProfileManagerService.java
new file mode 100644
index 0000000..aa769a5
--- /dev/null
+++ b/services/java/com/android/server/ProfileManagerService.java
@@ -0,0 +1,550 @@
+/*
+ * Copyright (c) 2011, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import android.app.IProfileManager;
+import android.app.NotificationGroup;
+import android.app.Profile;
+import android.app.ProfileGroup;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+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.Arrays;
+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<UUID, Profile> mProfiles;
+
+ // Match UUIDs and names, used for reverse compatibility
+ private Map<String, UUID> mProfileNames;
+
+ private Map<UUID, NotificationGroup> mGroups;
+
+ private Profile mActiveProfile;
+
+ // Well-known UUID of the wildcard group
+ private static final UUID mWildcardUUID = UUID.fromString("a126d48a-aaef-47c4-baed-7f0e44aeffe5");
+ private NotificationGroup mWildcardGroup;
+
+ private Context mContext;
+ private boolean mDirty;
+
+ private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ if (action.equals(Intent.ACTION_LOCALE_CHANGED)) {
+ persistIfDirty();
+ initialize();
+ } else if (action.equals(Intent.ACTION_SHUTDOWN)) {
+ persistIfDirty();
+ }
+ }
+ };
+
+ public ProfileManagerService(Context context) {
+ mContext = context;
+
+ mWildcardGroup = new NotificationGroup(
+ context.getString(com.android.internal.R.string.wildcardProfile),
+ com.android.internal.R.string.wildcardProfile,
+ mWildcardUUID);
+
+ initialize();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_LOCALE_CHANGED);
+ filter.addAction(Intent.ACTION_SHUTDOWN);
+ mContext.registerReceiver(mIntentReceiver, filter);
+ }
+
+ private void initialize() {
+ initialize(false);
+ }
+
+ private void initialize(boolean skipFile) {
+ mProfiles = new HashMap<UUID, Profile>();
+ mProfileNames = new HashMap<String, UUID>();
+ mGroups = new HashMap<UUID, NotificationGroup>();
+ mDirty = false;
+
+ boolean init = skipFile;
+
+ if (! skipFile) {
+ try {
+ loadFromFile();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ } catch (XmlPullParserException e) {
+ init = true;
+ } catch (IOException e) {
+ init = true;
+ }
+ }
+
+ if (init) {
+ try {
+ initialiseStructure();
+ } catch (Throwable ex) {
+ Log.e(TAG, "Error loading xml from resource: ", ex);
+ }
+ }
+ }
+
+ @Override
+ public void resetAll() {
+ enforceChangePermissions();
+ initialize(true);
+ }
+
+ @Override
+ @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.
+ */
+
+ try {
+ enforceChangePermissions();
+ Log.d(TAG, "Set active profile to: " + newActiveProfile.getUuid().toString() + " - " + newActiveProfile.getName());
+ Profile lastProfile = mActiveProfile;
+ mActiveProfile = newActiveProfile;
+ mDirty = true;
+ if (doinit) {
+ if (LOCAL_LOGV) Log.v(TAG, "setActiveProfile(Profile, boolean) - Running init");
+
+ /*
+ * We need to clear the caller's identity in order to
+ * - allow the profile switch to execute actions not included in the caller's permissions
+ * - broadcast INTENT_ACTION_PROFILE_SELECTED
+ */
+ long token = clearCallingIdentity();
+
+ // Call profile's "doSelect"
+ mActiveProfile.doSelect(mContext);
+
+ // 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);
+ persistIfDirty();
+ }
+ return true;
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ return false;
+ }
+ }
+
+ @Override
+ public boolean addProfile(Profile profile) throws RemoteException, SecurityException {
+ enforceChangePermissions();
+ addProfileInternal(profile);
+ persistIfDirty();
+ return true;
+ }
+
+ private void addProfileInternal(Profile profile) {
+ // Make sure this profile has all of the correct groups.
+ for (NotificationGroup group : mGroups.values()) {
+ ensureGroupInProfile(profile, group, false);
+ }
+ ensureGroupInProfile(profile, mWildcardGroup, true);
+ mProfiles.put(profile.getUuid(), profile);
+ mProfileNames.put(profile.getName(), profile.getUuid());
+ mDirty = true;
+ }
+
+ private void ensureGroupInProfile(Profile profile, NotificationGroup group, boolean defaultGroup) {
+ if (profile.getProfileGroup(group.getUuid()) != null) {
+ return;
+ }
+
+ /* enforce a matchup between profile and notification group, which not only
+ * works by UUID, but also by name for backwards compatibility */
+ for (ProfileGroup pg : profile.getProfileGroups()) {
+ if (pg.matches(group, defaultGroup)) {
+ return;
+ }
+ }
+
+ /* didn't find any, create new group */
+ profile.addProfileGroup(new ProfileGroup(group.getUuid(), defaultGroup));
+ }
+
+ @Override
+ @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 getProfile(profileUuid);
+ }
+
+ public Profile getProfile(UUID profileUuid) {
+ // use primary UUID first
+ if (mProfiles.containsKey(profileUuid)) {
+ return mProfiles.get(profileUuid);
+ }
+ // if no match was found: try secondary UUID
+ for (Profile p : mProfiles.values()) {
+ for (UUID uuid : p.getSecondaryUuids()) {
+ if (profileUuid.equals(uuid)) {
+ return p;
+ }
+ }
+ }
+ // nothing found
+ return null;
+ }
+
+ @Override
+ public Profile[] getProfiles() throws RemoteException {
+ Profile[] tmpArr = mProfiles.values().toArray(new Profile[mProfiles.size()]);
+ Arrays.sort(tmpArr);
+ return tmpArr;
+ }
+
+ @Override
+ public Profile getActiveProfile() throws RemoteException {
+ return mActiveProfile;
+ }
+
+ @Override
+ public boolean removeProfile(Profile profile) throws RemoteException, SecurityException {
+ enforceChangePermissions();
+ if (mProfileNames.remove(profile.getName()) != null && mProfiles.remove(profile.getUuid()) != null) {
+ mDirty = true;
+ persistIfDirty();
+ return true;
+ } else{
+ return false;
+ }
+ }
+
+ @Override
+ public void updateProfile(Profile profile) throws RemoteException, SecurityException {
+ enforceChangePermissions();
+ Profile old = mProfiles.get(profile.getUuid());
+ if (old != null) {
+ mProfileNames.remove(old.getName());
+ mProfileNames.put(profile.getName(), profile.getUuid());
+ mProfiles.put(profile.getUuid(), profile);
+ /* no need to set mDirty, if the profile was actually changed,
+ * it's marked as dirty by itself */
+ persistIfDirty();
+
+ // Also update we changed the active profile
+ if (mActiveProfile != null && mActiveProfile.getUuid().equals(profile.getUuid())) {
+ setActiveProfile(profile, true);
+ }
+ }
+ }
+
+ @Override
+ public boolean profileExists(ParcelUuid profileUuid) throws RemoteException {
+ return mProfiles.containsKey(profileUuid.getUuid());
+ }
+
+ @Override
+ public boolean profileExistsByName(String profileName) throws RemoteException {
+ for (Map.Entry<String, UUID> entry : mProfileNames.entrySet()) {
+ if (entry.getKey().equalsIgnoreCase(profileName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean notificationGroupExistsByName(String notificationGroupName) throws RemoteException {
+ for (NotificationGroup group : mGroups.values()) {
+ if (group.getName().equalsIgnoreCase(notificationGroupName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public NotificationGroup[] getNotificationGroups() throws RemoteException {
+ return mGroups.values().toArray(new NotificationGroup[mGroups.size()]);
+ }
+
+ @Override
+ public void addNotificationGroup(NotificationGroup group) throws RemoteException, SecurityException {
+ enforceChangePermissions();
+ addNotificationGroupInternal(group);
+ persistIfDirty();
+ }
+
+ private void addNotificationGroupInternal(NotificationGroup group) {
+ if (mGroups.put(group.getUuid(), group) == null) {
+ // If the above is true, then the ProfileGroup shouldn't exist in
+ // the profile. Ensure it is added.
+ for (Profile profile : mProfiles.values()) {
+ ensureGroupInProfile(profile, group, false);
+ }
+ }
+ mDirty = true;
+ }
+
+ @Override
+ public void removeNotificationGroup(NotificationGroup group) throws RemoteException, SecurityException {
+ enforceChangePermissions();
+ mDirty |= (mGroups.remove(group.getUuid()) != null);
+ // Remove the corresponding ProfileGroup from all the profiles too if
+ // they use it.
+ for (Profile profile : mProfiles.values()) {
+ profile.removeProfileGroup(group.getUuid());
+ }
+ persistIfDirty();
+ }
+
+ @Override
+ public void updateNotificationGroup(NotificationGroup group) throws RemoteException, SecurityException {
+ enforceChangePermissions();
+ NotificationGroup old = mGroups.get(group.getUuid());
+ if (old != null) {
+ mGroups.put(group.getUuid(), group);
+ /* no need to set mDirty, if the group was actually changed,
+ * it's marked as dirty by itself */
+ persistIfDirty();
+ }
+ }
+
+ @Override
+ public NotificationGroup getNotificationGroupForPackage(String pkg) throws RemoteException {
+ for (NotificationGroup group : mGroups.values()) {
+ if (group.hasPackage(pkg)) {
+ return group;
+ }
+ }
+ return null;
+ }
+
+ private void loadFromFile() throws RemoteException, XmlPullParserException, IOException {
+ XmlPullParserFactory xppf = XmlPullParserFactory.newInstance();
+ XmlPullParser xpp = xppf.newPullParser();
+ FileReader fr = new FileReader(PROFILE_FILENAME);
+ xpp.setInput(fr);
+ loadXml(xpp, mContext);
+ fr.close();
+ persistIfDirty();
+ }
+
+ private void loadXml(XmlPullParser xpp, Context context) throws
+ XmlPullParserException, IOException, RemoteException {
+ int event = xpp.next();
+ String active = null;
+ while (event != XmlPullParser.END_TAG || !"profiles".equals(xpp.getName())) {
+ if (event == XmlPullParser.START_TAG) {
+ String name = xpp.getName();
+ if (name.equals("active")) {
+ active = xpp.nextText();
+ Log.d(TAG, "Found active: " + active);
+ } else if (name.equals("profile")) {
+ Profile prof = Profile.fromXml(xpp, context);
+ addProfileInternal(prof);
+ // Failsafe if no active found
+ if (active == null) {
+ active = prof.getUuid().toString();
+ }
+ } else if (name.equals("notificationGroup")) {
+ NotificationGroup ng = NotificationGroup.fromXml(xpp, context);
+ addNotificationGroupInternal(ng);
+ }
+ } else if (event == XmlPullParser.END_DOCUMENT) {
+ throw new IOException("Premature end of file while reading " + PROFILE_FILENAME);
+ }
+ event = xpp.next();
+ }
+ // Don't do initialisation on startup. The AudioManager doesn't exist yet
+ // and besides, the volume settings will have survived the reboot.
+ 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.
+ mDirty = true;
+ }
+ }
+
+ private void initialiseStructure() throws RemoteException, XmlPullParserException, IOException {
+ XmlResourceParser xml = mContext.getResources().getXml(
+ com.android.internal.R.xml.profile_default);
+ try {
+ loadXml(xml, mContext);
+ mDirty = true;
+ persistIfDirty();
+ } finally {
+ xml.close();
+ }
+ }
+
+ private String getXmlString() throws RemoteException {
+ StringBuilder builder = new StringBuilder();
+ builder.append("<profiles>\n<active>");
+ builder.append(TextUtils.htmlEncode(getActiveProfile().getUuid().toString()));
+ builder.append("</active>\n");
+
+ for (Profile p : mProfiles.values()) {
+ p.getXmlString(builder, mContext);
+ }
+ for (NotificationGroup g : mGroups.values()) {
+ g.getXmlString(builder, mContext);
+ }
+ builder.append("</profiles>\n");
+ return builder.toString();
+ }
+
+ @Override
+ public NotificationGroup getNotificationGroup(ParcelUuid uuid) throws RemoteException {
+ if (uuid.getUuid().equals(mWildcardGroup.getUuid())) {
+ return mWildcardGroup;
+ }
+ return mGroups.get(uuid.getUuid());
+ }
+
+ private synchronized void persistIfDirty() {
+ boolean dirty = mDirty;
+ if (!dirty) {
+ for (Profile profile : mProfiles.values()) {
+ if (profile.isDirty()) {
+ dirty = true;
+ break;
+ }
+ }
+ }
+ if (!dirty) {
+ for (NotificationGroup group : mGroups.values()) {
+ if (group.isDirty()) {
+ dirty = true;
+ break;
+ }
+ }
+ }
+ if (dirty) {
+ try {
+ Log.d(TAG, "Saving profile data...");
+ FileWriter fw = new FileWriter(PROFILE_FILENAME);
+ fw.write(getXmlString());
+ fw.close();
+ Log.d(TAG, "Save completed.");
+ mDirty = false;
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private void enforceChangePermissions() throws SecurityException {
+ mContext.enforceCallingOrSelfPermission(PERMISSION_CHANGE_SETTINGS,
+ "You do not have permissions to change the Profile Manager.");
+ }
+}