replicant-vendor_cmsdk/cm/lib/main/java/org/cyanogenmod/platform/internal/ProfileManagerService.java

675 lines
24 KiB
Java

/*
* Copyright (c) 2011-2015 CyanogenMod 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 org.cyanogenmod.platform.internal;
import android.content.ComponentName;
import android.content.ServiceConnection;
import com.android.internal.policy.IKeyguardService;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import android.app.ActivityManagerNative;
import android.app.NotificationGroup;
import android.app.backup.BackupManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.XmlResourceParser;
import android.os.Environment;
import android.os.Handler;
import android.os.UserHandle;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;
import android.os.ParcelUuid;
import com.android.server.SystemService;
import cyanogenmod.app.CMContextConstants;
import cyanogenmod.app.Profile;
import cyanogenmod.app.ProfileGroup;
import cyanogenmod.app.ProfileManager;
import cyanogenmod.app.IProfileManager;
import java.util.Collection;
import java.io.File;
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 SystemService {
private static final String TAG = "CMProfileService";
// Enable the below for detailed logging of this class
private static final boolean LOCAL_LOGV = false;
public static final String KEYGUARD_PACKAGE = "com.android.systemui";
public static final String KEYGUARD_CLASS = "com.android.systemui.keyguard.KeyguardService";
/* package */ static final File PROFILE_FILE =
new File(Environment.getSystemSecureDirectory(), "profiles.xml");
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 Handler mHandler;
private boolean mDirty;
private BackupManager mBackupManager;
private ProfileTriggerHelper mTriggerHelper;
private Runnable mBindKeyguard = new Runnable() {
@Override
public void run() {
bindKeyguard();
}
};
private IKeyguardService mKeyguardService;
private final ServiceConnection mKeyguardConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (LOCAL_LOGV) Log.v(TAG, "*** Keyguard connected (yay!)");
mKeyguardService = IKeyguardService.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
if (LOCAL_LOGV) Log.v(TAG, "*** Keyguard disconnected, retrying connection soon.");
mKeyguardService = null;
// system UI died? retry connection in 5s
mHandler.removeCallbacks(mBindKeyguard);
mHandler.postDelayed(mBindKeyguard, 5000);
}
};
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) {
super(context);
mContext = context;
mHandler = new Handler();
publishBinderService(CMContextConstants.CM_PROFILE_SERVICE, mService);
}
@Override
public void onStart() {
mBackupManager = new BackupManager(mContext);
mWildcardGroup = new NotificationGroup(
mContext.getString(org.cyanogenmod.platform.internal.R.string.wildcardProfile),
org.cyanogenmod.platform.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 bindKeyguard() {
if (mKeyguardService == null) {
Intent intent = new Intent();
intent.setClassName(KEYGUARD_PACKAGE, KEYGUARD_CLASS);
if (!mContext.bindServiceAsUser(intent, mKeyguardConnection,
Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) {
Log.e(TAG, "error binding to keyguard service");
}
}
}
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_ACTIVITY_MANAGER_READY) {
bindKeyguard();
}
}
private void initialize() {
initialize(false);
}
private void initialize(boolean skipFile) {
mTriggerHelper = new ProfileTriggerHelper(mContext, mHandler, this);
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 (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);
}
}
}
private final IBinder mService = new IProfileManager.Stub() {
@Override
public void resetAll() {
long token = clearCallingIdentity();
enforceChangePermissions();
initialize(true);
restoreCallingIdentity(token);
}
@Override
@Deprecated
public boolean setActiveProfileByName(String profileName) {
if (!mProfileNames.containsKey(profileName)) {
// 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;
}
if (LOCAL_LOGV) {
Log.v(TAG, "setActiveProfile(String) found profile name in mProfileNames.");
}
/*
* 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();
setActiveProfileInternal(mProfiles.get(mProfileNames.get(profileName)), true);
restoreCallingIdentity(token);
return true;
}
@Override
public boolean setActiveProfile(ParcelUuid profileParcelUuid) {
/*
* 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();
boolean ret = setActiveProfileInternal(profileParcelUuid.getUuid(), true);
restoreCallingIdentity(token);
return ret;
}
@Override
public boolean addProfile(Profile profile) {
enforceChangePermissions();
addProfileInternal(profile);
long token = clearCallingIdentity();
persistIfDirty();
restoreCallingIdentity(token);
return true;
}
@Override
@Deprecated
public Profile getProfileByName(String profileName) {
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 getProfileInternal(profileUuid);
}
@Override
public Profile[] getProfiles() {
Profile[] profiles = getProfileList().toArray(new Profile[mProfiles.size()]);
Arrays.sort(profiles);
return profiles;
}
@Override
public Profile getActiveProfile() {
return getActiveProfileInternal();
}
@Override
public boolean removeProfile(Profile profile) {
enforceChangePermissions();
if (mProfileNames.remove(profile.getName()) != null
&& mProfiles.remove(profile.getUuid()) != null) {
mDirty = true;
long token = clearCallingIdentity();
persistIfDirty();
restoreCallingIdentity(token);
return true;
} else {
return false;
}
}
@Override
public void updateProfile(Profile profile) {
enforceChangePermissions();
Profile old = mProfiles.get(profile.getUuid());
if (old == null) {
return;
}
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 */
long token = clearCallingIdentity();
persistIfDirty();
// Also update if we changed the active profile
if (mActiveProfile != null && mActiveProfile.getUuid().equals(profile.getUuid())) {
setActiveProfileInternal(profile, true);
}
restoreCallingIdentity(token);
}
@Override
public boolean profileExists(ParcelUuid profileUuid) {
return mProfiles.containsKey(profileUuid.getUuid());
}
@Override
@Deprecated
public boolean profileExistsByName(String profileName) {
for (Map.Entry<String, UUID> entry : mProfileNames.entrySet()) {
if (entry.getKey().equalsIgnoreCase(profileName)) {
return true;
}
}
return false;
}
@Override
@Deprecated
public boolean notificationGroupExistsByName(String notificationGroupName) {
for (NotificationGroup group : mGroups.values()) {
if (group.getName().equalsIgnoreCase(notificationGroupName)) {
return true;
}
}
return false;
}
@Override
public NotificationGroup[] getNotificationGroups() {
return mGroups.values().toArray(new NotificationGroup[mGroups.size()]);
}
@Override
public void addNotificationGroup(NotificationGroup group) {
enforceChangePermissions();
addNotificationGroupInternal(group);
long token = clearCallingIdentity();
persistIfDirty();
restoreCallingIdentity(token);
}
@Override
public void removeNotificationGroup(NotificationGroup group) {
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());
}
long token = clearCallingIdentity();
persistIfDirty();
restoreCallingIdentity(token);
}
@Override
public void updateNotificationGroup(NotificationGroup group) {
enforceChangePermissions();
NotificationGroup old = mGroups.get(group.getUuid());
if (old == null) {
return;
}
mGroups.put(group.getUuid(), group);
/* no need to set mDirty, if the group was actually changed,
* it's marked as dirty by itself */
long token = clearCallingIdentity();
persistIfDirty();
restoreCallingIdentity(token);
}
@Override
public NotificationGroup getNotificationGroupForPackage(String pkg) {
for (NotificationGroup group : mGroups.values()) {
if (group.hasPackage(pkg)) {
return group;
}
}
return null;
}
@Override
public NotificationGroup getNotificationGroup(ParcelUuid uuid) {
if (uuid.getUuid().equals(mWildcardGroup.getUuid())) {
return mWildcardGroup;
}
return mGroups.get(uuid.getUuid());
}
};
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));
}
private Profile getProfileInternal(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;
}
/* package */ Collection<Profile> getProfileList() {
return mProfiles.values();
}
private String getXmlString() {
StringBuilder builder = new StringBuilder();
builder.append("<profiles>\n<active>");
builder.append(TextUtils.htmlEncode(mActiveProfile.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();
}
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_FILE);
fw.write(getXmlString());
fw.close();
Log.d(TAG, "Save completed.");
mDirty = false;
mBackupManager.dataChanged();
} catch (Throwable e) {
e.printStackTrace();
}
}
}
private void enforceChangePermissions() {
mContext.enforceCallingOrSelfPermission(
cyanogenmod.platform.Manifest.permission.MODIFY_PROFILES,
"You do not have permissions to change the Profile Manager.");
}
// Called by SystemBackupAgent after files are restored to disk.
void settingsRestored() {
initialize();
for (Profile p : mProfiles.values()) {
p.validateRingtones(mContext);
}
persistIfDirty();
}
private void loadFromFile() throws XmlPullParserException, IOException {
XmlPullParserFactory xppf = XmlPullParserFactory.newInstance();
XmlPullParser xpp = xppf.newPullParser();
FileReader fr = new FileReader(PROFILE_FILE);
xpp.setInput(fr);
loadXml(xpp, mContext);
fr.close();
persistIfDirty();
}
private void loadXml(XmlPullParser xpp, Context context) throws
XmlPullParserException, IOException {
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_FILE);
}
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.
setActiveProfileInternal(UUID.fromString(active), false);
} catch (IllegalArgumentException e) {
if (mProfileNames.containsKey(active)) {
setActiveProfileInternal(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.
setActiveProfileInternal(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 XmlPullParserException, IOException {
XmlResourceParser xml = mContext.getResources().getXml(
org.cyanogenmod.platform.internal.R.xml.profile_default);
try {
loadXml(xml, mContext);
mDirty = true;
persistIfDirty();
} finally {
xml.close();
}
}
private boolean setActiveProfileInternal(UUID profileUuid, boolean doInit) {
if (!mProfiles.containsKey(profileUuid)) {
Log.e(TAG, "Cannot set active profile to: "
+ profileUuid.toString() + " - does not exist.");
return false;
}
if (LOCAL_LOGV) Log.v(TAG, "setActiveProfile(UUID, boolean) found UUID in mProfiles.");
setActiveProfileInternal(mProfiles.get(profileUuid), doInit);
return true;
}
/* package */ Profile getActiveProfileInternal() {
return mActiveProfile;
}
/* package */ void setActiveProfileInternal(Profile newActiveProfile, boolean doInit) {
/*
* 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.
*/
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");
// Call profile's "doSelect"
mActiveProfile.doSelect(mContext, mKeyguardService);
// Notify other applications of newly selected profile.
Intent broadcast = new Intent(ProfileManager.INTENT_ACTION_PROFILE_SELECTED);
broadcast.putExtra(ProfileManager.EXTRA_PROFILE_NAME,
mActiveProfile.getName());
broadcast.putExtra(ProfileManager.EXTRA_PROFILE_UUID,
mActiveProfile.getUuid().toString());
broadcast.putExtra(ProfileManager.EXTRA_LAST_PROFILE_NAME,
lastProfile.getName());
broadcast.putExtra(ProfileManager.EXTRA_LAST_PROFILE_UUID,
lastProfile.getUuid().toString());
mContext.sendBroadcastAsUser(broadcast, UserHandle.ALL);
persistIfDirty();
} else if (lastProfile != mActiveProfile && ActivityManagerNative.isSystemReady()) {
// Something definitely changed: notify.
Intent broadcast = new Intent(ProfileManager.INTENT_ACTION_PROFILE_UPDATED);
broadcast.putExtra(ProfileManager.EXTRA_PROFILE_NAME,
mActiveProfile.getName());
broadcast.putExtra(ProfileManager.EXTRA_PROFILE_UUID,
mActiveProfile.getUuid().toString());
mContext.sendBroadcastAsUser(broadcast, UserHandle.ALL);
}
}
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;
}
}