CMSDK: Introduce Profiles API from frameworks.

Refactor to create a system service in secondary
  CM framework.

Change-Id: Ic69da01d941bbd09271c260429d744f8e79ab7b9
This commit is contained in:
Adnan Begovic 2015-06-25 12:54:38 -07:00
parent 4e081aef67
commit 4334b3d969
8 changed files with 2546 additions and 0 deletions

View File

@ -0,0 +1,616 @@
/*
* Copyright (c) 2011-2014 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 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.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 PERMISSION_CHANGE_SETTINGS = "android.permission.WRITE_SETTINGS";
/* 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 boolean mDirty;
private BackupManager mBackupManager;
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;
publishBinderService(CMContextConstants.CM_PROFILE_SERVICE, mService);
}
@Override
public void onStart() {
mBackupManager = new BackupManager(mContext);
mWildcardGroup = new NotificationGroup(
mContext.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 (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() {
enforceChangePermissions();
initialize(true);
}
@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 internalGetProfile(profileUuid);
}
@Override
public Profile[] getProfiles() {
Profile[] profiles = getProfileList().toArray(new Profile[mProfiles.size()]);
Arrays.sort(profiles);
return profiles;
}
@Override
public Profile getActiveProfile() {
return mActiveProfile;
}
@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 internalGetProfile(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(PERMISSION_CHANGE_SETTINGS,
"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(
com.android.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 */ 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);
// 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;
}
}

View File

@ -39,4 +39,15 @@ public final class CMContextConstants {
*/
public static final String CM_STATUS_BAR_SERVICE = "cmstatusbar";
/**
* Use with {@link android.content.Context#getSystemService} to retrieve a
* {@link cyanogenmod.app.ProfileManager} for informing the user of
* background events.
*
* @see android.content.Context#getSystemService
* @see cyanogenmod.app.ProfileManager
*
* @hide
*/
public static final String CM_PROFILE_SERVICE = "profile";
}

View File

@ -0,0 +1,50 @@
/* //device/java/android/android/app/IProfileManager.aidl
**
** Copyright (C) 2015 The 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 cyanogenmod.app;
import cyanogenmod.app.Profile;
import android.app.NotificationGroup;
import android.os.ParcelUuid;
/** {@hide} */
interface IProfileManager
{
boolean setActiveProfile(in ParcelUuid profileParcelUuid);
boolean setActiveProfileByName(String profileName);
Profile getActiveProfile();
boolean addProfile(in Profile profile);
boolean removeProfile(in Profile profile);
void updateProfile(in Profile profile);
Profile getProfile(in ParcelUuid profileParcelUuid);
Profile getProfileByName(String profileName);
Profile[] getProfiles();
boolean profileExists(in ParcelUuid profileUuid);
boolean profileExistsByName(String profileName);
boolean notificationGroupExistsByName(String notificationGroupName);
NotificationGroup[] getNotificationGroups();
void addNotificationGroup(in NotificationGroup group);
void removeNotificationGroup(in NotificationGroup group);
void updateNotificationGroup(in NotificationGroup group);
NotificationGroup getNotificationGroupForPackage(in String pkg);
NotificationGroup getNotificationGroup(in ParcelUuid groupParcelUuid);
void resetAll();
}

View File

@ -0,0 +1,19 @@
/**
* Copyright (C) 2014 The 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 cyanogenmod.app;
parcelable Profile;

View File

@ -0,0 +1,869 @@
/*
* Copyright (C) 2014 The 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 cyanogenmod.app;
import android.app.AirplaneModeSettings;
import android.app.BrightnessSettings;
import android.app.ConnectionSettings;
import android.app.RingModeSettings;
import android.app.StreamSettings;
import android.content.Context;
import android.media.AudioManager;
import android.os.Parcel;
import android.os.ParcelUuid;
import android.os.Parcelable;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
/**
* @hide
*/
public final class Profile implements Parcelable, Comparable {
private String mName;
private int mNameResId;
private UUID mUuid;
private ArrayList<UUID> mSecondaryUuids = new ArrayList<UUID>();
private Map<UUID, ProfileGroup> profileGroups = new HashMap<UUID, ProfileGroup>();
private ProfileGroup mDefaultGroup;
private boolean mStatusBarIndicator = false;
private boolean mDirty;
private static final String TAG = "Profile";
private int mProfileType;
private static final int CONDITIONAL_TYPE = 1;
private static final int TOGGLE_TYPE = 0;
private Map<Integer, StreamSettings> streams = new HashMap<Integer, StreamSettings>();
private Map<String, ProfileTrigger> mTriggers = new HashMap<String, ProfileTrigger>();
private Map<Integer, ConnectionSettings> connections = new HashMap<Integer, ConnectionSettings>();
private RingModeSettings mRingMode = new RingModeSettings();
private AirplaneModeSettings mAirplaneMode = new AirplaneModeSettings();
private BrightnessSettings mBrightness = new BrightnessSettings();
private int mScreenLockMode = LockMode.DEFAULT;
private int mExpandedDesktopMode = ExpandedDesktopMode.DEFAULT;
private int mDozeMode = DozeMode.DEFAULT;
/** @hide */
public static class LockMode {
public static final int DEFAULT = 0;
public static final int INSECURE = 1;
public static final int DISABLE = 2;
}
/** @hide */
public static class ExpandedDesktopMode {
public static final int DEFAULT = 0;
public static final int ENABLE = 1;
public static final int DISABLE = 2;
}
/** @hide */
public static class DozeMode {
public static final int DEFAULT = 0;
public static final int ENABLE = 1;
public static final int DISABLE = 2;
}
/** @hide */
public static class TriggerType {
public static final int WIFI = 0;
public static final int BLUETOOTH = 1;
}
/** @hide */
public static class TriggerState {
public static final int ON_CONNECT = 0;
public static final int ON_DISCONNECT = 1;
public static final int DISABLED = 2;
public static final int ON_A2DP_CONNECT = 3;
public static final int ON_A2DP_DISCONNECT = 4;
}
public static class ProfileTrigger implements Parcelable {
private int mType;
private String mId;
private String mName;
private int mState;
public ProfileTrigger(int type, String id, int state, String name) {
mType = type;
mId = id;
mState = state;
mName = name;
}
private ProfileTrigger(Parcel in) {
mType = in.readInt();
mId = in.readString();
mState = in.readInt();
mName = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mType);
dest.writeString(mId);
dest.writeInt(mState);
dest.writeString(mName);
}
@Override
public int describeContents() {
return 0;
}
public int getType() {
return mType;
}
public String getName() {
return mName;
}
public String getId() {
return mId;
}
public int getState() {
return mState;
}
public void getXmlString(StringBuilder builder, Context context) {
final String itemType = mType == TriggerType.WIFI ? "wifiAP" : "btDevice";
builder.append("<");
builder.append(itemType);
builder.append(" ");
builder.append(getIdType(mType));
builder.append("=\"");
builder.append(mId);
builder.append("\" state=\"");
builder.append(mState);
builder.append("\" name=\"");
builder.append(mName);
builder.append("\"></");
builder.append(itemType);
builder.append(">\n");
}
public static ProfileTrigger fromXml(XmlPullParser xpp, Context context) {
final String name = xpp.getName();
final int type;
if (name.equals("wifiAP")) {
type = TriggerType.WIFI;
} else if (name.equals("btDevice")) {
type = TriggerType.BLUETOOTH;
} else {
return null;
}
String id = xpp.getAttributeValue(null, getIdType(type));
int state = Integer.valueOf(xpp.getAttributeValue(null, "state"));
String triggerName = xpp.getAttributeValue(null, "name");
if (triggerName == null) {
triggerName = id;
}
return new ProfileTrigger(type, id, state, triggerName);
}
private static String getIdType(int type) {
return type == TriggerType.WIFI ? "ssid" : "address";
}
public static final Parcelable.Creator<ProfileTrigger> CREATOR
= new Parcelable.Creator<ProfileTrigger>() {
public ProfileTrigger createFromParcel(Parcel in) {
return new ProfileTrigger(in);
}
@Override
public ProfileTrigger[] newArray(int size) {
return new ProfileTrigger[size];
}
};
}
/** @hide */
public static final Parcelable.Creator<Profile> CREATOR = new Parcelable.Creator<Profile>() {
public Profile createFromParcel(Parcel in) {
return new Profile(in);
}
@Override
public Profile[] newArray(int size) {
return new Profile[size];
}
};
/** @hide */
public Profile(String name) {
this(name, -1, UUID.randomUUID());
}
private Profile(String name, int nameResId, UUID uuid) {
mName = name;
mNameResId = nameResId;
mUuid = uuid;
mProfileType = TOGGLE_TYPE; //Default to toggle type
mDirty = false;
}
private Profile(Parcel in) {
readFromParcel(in);
}
public int getTrigger(int type, String id) {
ProfileTrigger trigger = id != null ? mTriggers.get(id) : null;
if (trigger != null) {
return trigger.mState;
}
return TriggerState.DISABLED;
}
public ArrayList<ProfileTrigger> getTriggersFromType(int type) {
ArrayList<ProfileTrigger> result = new ArrayList<ProfileTrigger>();
for (Entry<String, ProfileTrigger> profileTrigger: mTriggers.entrySet()) {
ProfileTrigger trigger = profileTrigger.getValue();
if (trigger.getType() == type) {
result.add(trigger);
}
}
return result;
}
public void setTrigger(int type, String id, int state, String name) {
if (id == null
|| type < TriggerType.WIFI || type > TriggerType.BLUETOOTH
|| state < TriggerState.ON_CONNECT || state > TriggerState.ON_A2DP_DISCONNECT) {
return;
}
ProfileTrigger trigger = mTriggers.get(id);
if (state == TriggerState.DISABLED) {
if (trigger != null) {
mTriggers.remove(id);
}
} else if (trigger != null) {
trigger.mState = state;
} else {
mTriggers.put(id, new ProfileTrigger(type, id, state, name));
}
mDirty = true;
}
public int compareTo(Object obj) {
Profile tmp = (Profile) obj;
if (mName.compareTo(tmp.mName) < 0) {
return -1;
} else if (mName.compareTo(tmp.mName) > 0) {
return 1;
}
return 0;
}
/** @hide */
public void addProfileGroup(ProfileGroup value) {
if (value.isDefaultGroup()) {
/* we must not have more than one default group */
if (mDefaultGroup != null) {
return;
}
mDefaultGroup = value;
}
profileGroups.put(value.getUuid(), value);
mDirty = true;
}
/** @hide */
public void removeProfileGroup(UUID uuid) {
if (!profileGroups.get(uuid).isDefaultGroup()) {
profileGroups.remove(uuid);
} else {
Log.e(TAG, "Cannot remove default group: " + uuid);
}
}
public ProfileGroup[] getProfileGroups() {
return profileGroups.values().toArray(new ProfileGroup[profileGroups.size()]);
}
public ProfileGroup getProfileGroup(UUID uuid) {
return profileGroups.get(uuid);
}
public ProfileGroup getDefaultGroup() {
return mDefaultGroup;
}
/** @hide */
@Override
public int describeContents() {
return 0;
}
/** @hide */
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mName);
dest.writeInt(mNameResId);
new ParcelUuid(mUuid).writeToParcel(dest, 0);
ArrayList<ParcelUuid> uuids = new ArrayList<ParcelUuid>(mSecondaryUuids.size());
for (UUID u : mSecondaryUuids) {
uuids.add(new ParcelUuid(u));
}
dest.writeParcelableArray(uuids.toArray(new Parcelable[uuids.size()]), flags);
dest.writeInt(mStatusBarIndicator ? 1 : 0);
dest.writeInt(mProfileType);
dest.writeInt(mDirty ? 1 : 0);
dest.writeParcelableArray(
profileGroups.values().toArray(new Parcelable[profileGroups.size()]), flags);
dest.writeParcelableArray(
streams.values().toArray(new Parcelable[streams.size()]), flags);
dest.writeParcelableArray(
connections.values().toArray(new Parcelable[connections.size()]), flags);
dest.writeParcelable(mRingMode, flags);
dest.writeParcelable(mAirplaneMode, flags);
dest.writeParcelable(mBrightness, flags);
dest.writeInt(mScreenLockMode);
dest.writeMap(mTriggers);
dest.writeInt(mExpandedDesktopMode);
dest.writeInt(mDozeMode);
}
/** @hide */
public void readFromParcel(Parcel in) {
mName = in.readString();
mNameResId = in.readInt();
mUuid = ParcelUuid.CREATOR.createFromParcel(in).getUuid();
for (Parcelable parcel : in.readParcelableArray(null)) {
ParcelUuid u = (ParcelUuid) parcel;
mSecondaryUuids.add(u.getUuid());
}
mStatusBarIndicator = (in.readInt() == 1);
mProfileType = in.readInt();
mDirty = (in.readInt() == 1);
for (Parcelable group : in.readParcelableArray(null)) {
ProfileGroup grp = (ProfileGroup) group;
profileGroups.put(grp.getUuid(), grp);
if (grp.isDefaultGroup()) {
mDefaultGroup = grp;
}
}
for (Parcelable parcel : in.readParcelableArray(null)) {
StreamSettings stream = (StreamSettings) parcel;
streams.put(stream.getStreamId(), stream);
}
for (Parcelable parcel : in.readParcelableArray(null)) {
ConnectionSettings connection = (ConnectionSettings) parcel;
connections.put(connection.getConnectionId(), connection);
}
mRingMode = (RingModeSettings) in.readParcelable(null);
mAirplaneMode = (AirplaneModeSettings) in.readParcelable(null);
mBrightness = (BrightnessSettings) in.readParcelable(null);
mScreenLockMode = in.readInt();
in.readMap(mTriggers, null);
mExpandedDesktopMode = in.readInt();
mDozeMode = in.readInt();
}
public String getName() {
return mName;
}
/** @hide */
public void setName(String name) {
mName = name;
mNameResId = -1;
mDirty = true;
}
public int getProfileType() {
return mProfileType;
}
/** @hide */
public void setProfileType(int type) {
mProfileType = type;
mDirty = true;
}
public UUID getUuid() {
if (this.mUuid == null) this.mUuid = UUID.randomUUID();
return this.mUuid;
}
public UUID[] getSecondaryUuids() {
return mSecondaryUuids.toArray(new UUID[mSecondaryUuids.size()]);
}
public void setSecondaryUuids(List<UUID> uuids) {
mSecondaryUuids.clear();
if (uuids != null) {
mSecondaryUuids.addAll(uuids);
mDirty = true;
}
}
public void addSecondaryUuid(UUID uuid) {
if (uuid != null) {
mSecondaryUuids.add(uuid);
mDirty = true;
}
}
public boolean getStatusBarIndicator() {
return mStatusBarIndicator;
}
public void setStatusBarIndicator(boolean newStatusBarIndicator) {
mStatusBarIndicator = newStatusBarIndicator;
mDirty = true;
}
public boolean isConditionalType() {
return(mProfileType == CONDITIONAL_TYPE ? true : false);
}
public void setConditionalType() {
mProfileType = CONDITIONAL_TYPE;
mDirty = true;
}
public RingModeSettings getRingMode() {
return mRingMode;
}
public void setRingMode(RingModeSettings descriptor) {
mRingMode = descriptor;
mDirty = true;
}
public int getScreenLockMode() {
return mScreenLockMode;
}
public void setScreenLockMode(int screenLockMode) {
if (screenLockMode < LockMode.DEFAULT || screenLockMode > LockMode.DISABLE) {
mScreenLockMode = LockMode.DEFAULT;
} else {
mScreenLockMode = screenLockMode;
}
mDirty = true;
}
public int getExpandedDesktopMode() {
return mExpandedDesktopMode;
}
public void setExpandedDesktopMode(int expandedDesktopMode) {
if (expandedDesktopMode < ExpandedDesktopMode.DEFAULT
|| expandedDesktopMode > ExpandedDesktopMode.DISABLE) {
mExpandedDesktopMode = ExpandedDesktopMode.DEFAULT;
} else {
mExpandedDesktopMode = expandedDesktopMode;
}
mDirty = true;
}
public int getDozeMode() {
return mDozeMode;
}
public void setDozeMode(int dozeMode) {
if (dozeMode < DozeMode.DEFAULT
|| dozeMode > DozeMode.DISABLE) {
mDozeMode = DozeMode.DEFAULT;
} else {
mDozeMode = dozeMode;
}
mDirty = true;
}
public AirplaneModeSettings getAirplaneMode() {
return mAirplaneMode;
}
public void setAirplaneMode(AirplaneModeSettings descriptor) {
mAirplaneMode = descriptor;
mDirty = true;
}
public BrightnessSettings getBrightness() {
return mBrightness;
}
public void setBrightness(BrightnessSettings descriptor) {
mBrightness = descriptor;
mDirty = true;
}
/** @hide */
public boolean isDirty() {
if (mDirty) {
return true;
}
for (ProfileGroup group : profileGroups.values()) {
if (group.isDirty()) {
return true;
}
}
for (StreamSettings stream : streams.values()) {
if (stream.isDirty()) {
return true;
}
}
for (ConnectionSettings conn : connections.values()) {
if (conn.isDirty()) {
return true;
}
}
if (mRingMode.isDirty()) {
return true;
}
if (mAirplaneMode.isDirty()) {
return true;
}
if (mBrightness.isDirty()) {
return true;
}
return false;
}
/** @hide */
public void getXmlString(StringBuilder builder, Context context) {
builder.append("<profile ");
if (mNameResId > 0) {
builder.append("nameres=\"");
builder.append(context.getResources().getResourceEntryName(mNameResId));
} else {
builder.append("name=\"");
builder.append(TextUtils.htmlEncode(getName()));
}
builder.append("\" uuid=\"");
builder.append(TextUtils.htmlEncode(getUuid().toString()));
builder.append("\">\n");
builder.append("<uuids>");
for (UUID u : mSecondaryUuids) {
builder.append("<uuid>");
builder.append(TextUtils.htmlEncode(u.toString()));
builder.append("</uuid>");
}
builder.append("</uuids>\n");
builder.append("<profiletype>");
builder.append(getProfileType() == TOGGLE_TYPE ? "toggle" : "conditional");
builder.append("</profiletype>\n");
builder.append("<statusbar>");
builder.append(getStatusBarIndicator() ? "yes" : "no");
builder.append("</statusbar>\n");
builder.append("<screen-lock-mode>");
builder.append(mScreenLockMode);
builder.append("</screen-lock-mode>\n");
builder.append("<expanded-desktop-mode>");
builder.append(mExpandedDesktopMode);
builder.append("</expanded-desktop-mode>\n");
builder.append("<doze-mode>");
builder.append(mDozeMode);
builder.append("</doze-mode>\n");
mAirplaneMode.getXmlString(builder, context);
mBrightness.getXmlString(builder, context);
mRingMode.getXmlString(builder, context);
for (ProfileGroup pGroup : profileGroups.values()) {
pGroup.getXmlString(builder, context);
}
for (StreamSettings sd : streams.values()) {
sd.getXmlString(builder, context);
}
for (ConnectionSettings cs : connections.values()) {
cs.getXmlString(builder, context);
}
if (!mTriggers.isEmpty()) {
builder.append("<triggers>\n");
for (ProfileTrigger trigger : mTriggers.values()) {
trigger.getXmlString(builder, context);
}
builder.append("</triggers>\n");
}
builder.append("</profile>\n");
mDirty = false;
}
private static List<UUID> readSecondaryUuidsFromXml(XmlPullParser xpp, Context context)
throws XmlPullParserException,
IOException {
ArrayList<UUID> uuids = new ArrayList<UUID>();
int event = xpp.next();
while (event != XmlPullParser.END_TAG || !xpp.getName().equals("uuids")) {
if (event == XmlPullParser.START_TAG) {
String name = xpp.getName();
if (name.equals("uuid")) {
try {
uuids.add(UUID.fromString(xpp.nextText()));
} catch (NullPointerException e) {
Log.w(TAG, "Null Pointer - invalid UUID");
} catch (IllegalArgumentException e) {
Log.w(TAG, "UUID not recognized");
}
}
}
event = xpp.next();
}
return uuids;
}
private static void readTriggersFromXml(XmlPullParser xpp, Context context, Profile profile)
throws XmlPullParserException, IOException {
int event = xpp.next();
while (event != XmlPullParser.END_TAG || !xpp.getName().equals("triggers")) {
if (event == XmlPullParser.START_TAG) {
ProfileTrigger trigger = ProfileTrigger.fromXml(xpp, context);
if (trigger != null) {
profile.mTriggers.put(trigger.mId, trigger);
}
} else if (event == XmlPullParser.END_DOCUMENT) {
throw new IOException("Premature end of file while parsing triggers");
}
event = xpp.next();
}
}
/** @hide */
public void validateRingtones(Context context) {
for (ProfileGroup pg : profileGroups.values()) {
pg.validateOverrideUris(context);
}
}
/** @hide */
public static Profile fromXml(XmlPullParser xpp, Context context)
throws XmlPullParserException, IOException {
String value = xpp.getAttributeValue(null, "nameres");
int profileNameResId = -1;
String profileName = null;
if (value != null) {
profileNameResId = context.getResources().getIdentifier(value, "string", "android");
if (profileNameResId > 0) {
profileName = context.getResources().getString(profileNameResId);
}
}
if (profileName == null) {
profileName = xpp.getAttributeValue(null, "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, profileNameResId, profileUuid);
int event = xpp.next();
while (event != XmlPullParser.END_TAG) {
if (event == XmlPullParser.START_TAG) {
String name = xpp.getName();
if (name.equals("uuids")) {
profile.setSecondaryUuids(readSecondaryUuidsFromXml(xpp, context));
}
if (name.equals("statusbar")) {
profile.setStatusBarIndicator(xpp.nextText().equals("yes"));
}
if (name.equals("profiletype")) {
profile.setProfileType(xpp.nextText().equals("toggle")
? TOGGLE_TYPE : CONDITIONAL_TYPE);
}
if (name.equals("ringModeDescriptor")) {
RingModeSettings smd = RingModeSettings.fromXml(xpp, context);
profile.setRingMode(smd);
}
if (name.equals("airplaneModeDescriptor")) {
AirplaneModeSettings amd = AirplaneModeSettings.fromXml(xpp, context);
profile.setAirplaneMode(amd);
}
if (name.equals("brightnessDescriptor")) {
BrightnessSettings bd = BrightnessSettings.fromXml(xpp, context);
profile.setBrightness(bd);
}
if (name.equals("screen-lock-mode")) {
profile.setScreenLockMode(Integer.valueOf(xpp.nextText()));
}
if (name.equals("expanded-desktop-mode")) {
profile.setExpandedDesktopMode(Integer.valueOf(xpp.nextText()));
}
if (name.equals("doze-mode")) {
profile.setDozeMode(Integer.valueOf(xpp.nextText()));
}
if (name.equals("profileGroup")) {
ProfileGroup pg = ProfileGroup.fromXml(xpp, context);
profile.addProfileGroup(pg);
}
if (name.equals("streamDescriptor")) {
StreamSettings sd = StreamSettings.fromXml(xpp, context);
profile.setStreamSettings(sd);
}
if (name.equals("connectionDescriptor")) {
ConnectionSettings cs = ConnectionSettings.fromXml(xpp, context);
profile.connections.put(cs.getConnectionId(), cs);
}
if (name.equals("triggers")) {
readTriggersFromXml(xpp, context, profile);
}
} else if (event == XmlPullParser.END_DOCUMENT) {
throw new IOException("Premature end of file while parsing profle:" + profileName);
}
event = xpp.next();
}
/* we just loaded from XML, so nothing needs saving */
profile.mDirty = false;
return profile;
}
/** @hide */
public void doSelect(Context context) {
// Set stream volumes
AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
for (StreamSettings sd : streams.values()) {
if (sd.isOverride()) {
am.setStreamVolume(sd.getStreamId(), sd.getValue(), 0);
}
}
// Set connections
for (ConnectionSettings cs : connections.values()) {
if (cs.isOverride()) {
cs.processOverride(context);
}
}
// Set ring mode
mRingMode.processOverride(context);
// Set airplane mode
mAirplaneMode.processOverride(context);
// Set brightness
mBrightness.processOverride(context);
// Set expanded desktop
// if (mExpandedDesktopMode != ExpandedDesktopMode.DEFAULT) {
// Settings.System.putIntForUser(context.getContentResolver(),
// Settings.System.EXPANDED_DESKTOP_STATE,
// mExpandedDesktopMode == ExpandedDesktopMode.ENABLE ? 1 : 0,
// UserHandle.USER_CURRENT);
// }
// Set doze mode
if (mDozeMode != DozeMode.DEFAULT) {
Settings.Secure.putIntForUser(context.getContentResolver(),
Settings.Secure.DOZE_ENABLED,
mDozeMode == DozeMode.ENABLE ? 1 : 0,
UserHandle.USER_CURRENT);
}
}
/** @hide */
public StreamSettings getSettingsForStream(int streamId){
return streams.get(streamId);
}
/** @hide */
public void setStreamSettings(StreamSettings descriptor){
streams.put(descriptor.getStreamId(), descriptor);
mDirty = true;
}
/** @hide */
public Collection<StreamSettings> getStreamSettings(){
return streams.values();
}
/** @hide */
public ConnectionSettings getSettingsForConnection(int connectionId){
return connections.get(connectionId);
}
/** @hide */
public void setConnectionSettings(ConnectionSettings descriptor){
connections.put(descriptor.getConnectionId(), descriptor);
}
/** @hide */
public Collection<ConnectionSettings> getConnectionSettings(){
return connections.values();
}
}

View File

@ -0,0 +1,370 @@
/*
* Copyright (C) 2014 The 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 cyanogenmod.app;
import android.app.Notification;
import android.app.NotificationGroup;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.content.Context;
import android.database.Cursor;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.ParcelUuid;
import android.text.TextUtils;
import android.util.Log;
import java.io.IOException;
import java.util.UUID;
/**
* @hide
*/
public final class ProfileGroup implements Parcelable {
private static final String TAG = "ProfileGroup";
private String mName;
private int mNameResId;
private UUID mUuid;
private Uri mSoundOverride = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
private Uri mRingerOverride = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
private Mode mSoundMode = Mode.DEFAULT;
private Mode mRingerMode = Mode.DEFAULT;
private Mode mVibrateMode = Mode.DEFAULT;
private Mode mLightsMode = Mode.DEFAULT;
private boolean mDefaultGroup = false;
private boolean mDirty;
/** @hide */
public static final Parcelable.Creator<ProfileGroup> CREATOR
= new Parcelable.Creator<ProfileGroup>() {
public ProfileGroup createFromParcel(Parcel in) {
return new ProfileGroup(in);
}
@Override
public ProfileGroup[] newArray(int size) {
return new ProfileGroup[size];
}
};
/** @hide */
public ProfileGroup(UUID uuid, boolean defaultGroup) {
this(null, uuid, defaultGroup);
}
private ProfileGroup(String name, UUID uuid, boolean defaultGroup) {
mName = name;
mUuid = (uuid != null) ? uuid : UUID.randomUUID();
mDefaultGroup = defaultGroup;
mDirty = uuid == null;
}
/** @hide */
private ProfileGroup(Parcel in) {
readFromParcel(in);
}
/** @hide */
public boolean matches(NotificationGroup group, boolean defaultGroup) {
if (mUuid.equals(group.getUuid())) {
return true;
}
/* fallback matches for backwards compatibility */
boolean matches = false;
/* fallback attempt 1: match name */
if (mName != null && mName.equals(group.getName())) {
matches = true;
/* fallback attempt 2: match for the 'defaultGroup' flag to match the wildcard group */
} else if (mDefaultGroup && defaultGroup) {
matches = true;
}
if (!matches) {
return false;
}
mName = null;
mUuid = group.getUuid();
mDirty = true;
return true;
}
public UUID getUuid() {
return mUuid;
}
public boolean isDefaultGroup() {
return mDefaultGroup;
}
/** @hide */
public boolean isDirty() {
return mDirty;
}
/** @hide */
public void setSoundOverride(Uri sound) {
mSoundOverride = sound;
mDirty = true;
}
public Uri getSoundOverride() {
return mSoundOverride;
}
/** @hide */
public void setRingerOverride(Uri ringer) {
mRingerOverride = ringer;
mDirty = true;
}
public Uri getRingerOverride() {
return mRingerOverride;
}
/** @hide */
public void setSoundMode(Mode soundMode) {
mSoundMode = soundMode;
mDirty = true;
}
public Mode getSoundMode() {
return mSoundMode;
}
/** @hide */
public void setRingerMode(Mode ringerMode) {
mRingerMode = ringerMode;
mDirty = true;
}
public Mode getRingerMode() {
return mRingerMode;
}
/** @hide */
public void setVibrateMode(Mode vibrateMode) {
mVibrateMode = vibrateMode;
mDirty = true;
}
public Mode getVibrateMode() {
return mVibrateMode;
}
/** @hide */
public void setLightsMode(Mode lightsMode) {
mLightsMode = lightsMode;
mDirty = true;
}
public Mode getLightsMode() {
return mLightsMode;
}
// TODO : add support for LEDs / screen etc.
/** @hide */
public void applyOverridesToNotification(Notification notification) {
switch (mSoundMode) {
case OVERRIDE:
notification.sound = mSoundOverride;
break;
case SUPPRESS:
notification.defaults &= ~Notification.DEFAULT_SOUND;
notification.sound = null;
break;
case DEFAULT:
break;
}
switch (mVibrateMode) {
case OVERRIDE:
notification.defaults |= Notification.DEFAULT_VIBRATE;
break;
case SUPPRESS:
notification.defaults &= ~Notification.DEFAULT_VIBRATE;
notification.vibrate = null;
break;
case DEFAULT:
break;
}
switch (mLightsMode) {
case OVERRIDE:
notification.defaults |= Notification.DEFAULT_LIGHTS;
break;
case SUPPRESS:
notification.defaults &= ~Notification.DEFAULT_LIGHTS;
notification.flags &= ~Notification.FLAG_SHOW_LIGHTS;
break;
case DEFAULT:
break;
}
}
private boolean validateOverrideUri(Context context, Uri uri) {
if (RingtoneManager.isDefault(uri)) {
return true;
}
Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
boolean valid = false;
if (cursor != null) {
valid = cursor.moveToFirst();
cursor.close();
}
return valid;
}
void validateOverrideUris(Context context) {
if (!validateOverrideUri(context, mSoundOverride)) {
mSoundOverride = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
mSoundMode = Mode.DEFAULT;
mDirty = true;
}
if (!validateOverrideUri(context, mRingerOverride)) {
mRingerOverride = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
mRingerMode = Mode.DEFAULT;
mDirty = true;
}
}
/** @hide */
@Override
public int describeContents() {
return 0;
}
/** @hide */
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mName);
new ParcelUuid(mUuid).writeToParcel(dest, 0);
dest.writeInt(mDefaultGroup ? 1 : 0);
dest.writeInt(mDirty ? 1 : 0);
dest.writeParcelable(mSoundOverride, flags);
dest.writeParcelable(mRingerOverride, flags);
dest.writeString(mSoundMode.name());
dest.writeString(mRingerMode.name());
dest.writeString(mVibrateMode.name());
dest.writeString(mLightsMode.name());
}
/** @hide */
public void readFromParcel(Parcel in) {
mName = in.readString();
mUuid = ParcelUuid.CREATOR.createFromParcel(in).getUuid();
mDefaultGroup = in.readInt() != 0;
mDirty = in.readInt() != 0;
mSoundOverride = in.readParcelable(null);
mRingerOverride = in.readParcelable(null);
mSoundMode = Mode.valueOf(Mode.class, in.readString());
mRingerMode = Mode.valueOf(Mode.class, in.readString());
mVibrateMode = Mode.valueOf(Mode.class, in.readString());
mLightsMode = Mode.valueOf(Mode.class, in.readString());
}
public enum Mode {
SUPPRESS, DEFAULT, OVERRIDE;
}
/** @hide */
public void getXmlString(StringBuilder builder, Context context) {
builder.append("<profileGroup uuid=\"");
builder.append(TextUtils.htmlEncode(mUuid.toString()));
if (mName != null) {
builder.append("\" name=\"");
builder.append(mName);
}
builder.append("\" default=\"");
builder.append(isDefaultGroup());
builder.append("\">\n<sound>");
builder.append(TextUtils.htmlEncode(mSoundOverride.toString()));
builder.append("</sound>\n<ringer>");
builder.append(TextUtils.htmlEncode(mRingerOverride.toString()));
builder.append("</ringer>\n<soundMode>");
builder.append(mSoundMode);
builder.append("</soundMode>\n<ringerMode>");
builder.append(mRingerMode);
builder.append("</ringerMode>\n<vibrateMode>");
builder.append(mVibrateMode);
builder.append("</vibrateMode>\n<lightsMode>");
builder.append(mLightsMode);
builder.append("</lightsMode>\n</profileGroup>\n");
mDirty = false;
}
/** @hide */
public static ProfileGroup fromXml(XmlPullParser xpp, Context context)
throws XmlPullParserException, IOException {
String name = xpp.getAttributeValue(null, "name");
UUID uuid = null;
String value = xpp.getAttributeValue(null, "uuid");
if (value != null) {
try {
uuid = UUID.fromString(value);
} catch (IllegalArgumentException e) {
Log.w(TAG, "UUID not recognized for " + name + ", using new one.");
}
}
value = xpp.getAttributeValue(null, "default");
boolean defaultGroup = TextUtils.equals(value, "true");
ProfileGroup profileGroup = new ProfileGroup(name, uuid, defaultGroup);
int event = xpp.next();
while (event != XmlPullParser.END_TAG || !xpp.getName().equals("profileGroup")) {
if (event == XmlPullParser.START_TAG) {
name = xpp.getName();
if (name.equals("sound")) {
profileGroup.setSoundOverride(Uri.parse(xpp.nextText()));
} else if (name.equals("ringer")) {
profileGroup.setRingerOverride(Uri.parse(xpp.nextText()));
} else if (name.equals("soundMode")) {
profileGroup.setSoundMode(Mode.valueOf(xpp.nextText()));
} else if (name.equals("ringerMode")) {
profileGroup.setRingerMode(Mode.valueOf(xpp.nextText()));
} else if (name.equals("vibrateMode")) {
profileGroup.setVibrateMode(Mode.valueOf(xpp.nextText()));
} else if (name.equals("lightsMode")) {
profileGroup.setLightsMode(Mode.valueOf(xpp.nextText()));
}
} else if (event == XmlPullParser.END_DOCUMENT) {
throw new IOException("Premature end of file while parsing profleGroup:" + name);
}
event = xpp.next();
}
/* we just loaded from XML, no need to save */
profileGroup.mDirty = false;
return profileGroup;
}
}

View File

@ -0,0 +1,467 @@
/*
* Copyright (C) 2014 The 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 cyanogenmod.app;
import java.util.UUID;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.NotificationGroup;
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.provider.Settings;
import android.util.Log;
import cyanogenmod.app.IProfileManager;
import com.android.internal.R;
/**
* @hide
*/
public class ProfileManager {
private static IProfileManager sService;
private Context mContext;
private static final String TAG = "ProfileManager";
private static final String SYSTEM_PROFILES_ENABLED = "system_profiles_enabled";
/**
* <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";
/**
* Extra for {@link INTENT_ACTION_PROFILE_SELECTED} and {@link INTENT_ACTION_PROFILE_UPDATED}:
* The name of the newly activated or updated profile
* @hide
*/
public static final String EXTRA_PROFILE_NAME = "name";
/**
* Extra for {@link INTENT_ACTION_PROFILE_SELECTED} and {@link INTENT_ACTION_PROFILE_UPDATED}:
* The string representation of the UUID of the newly activated or updated profile
* @hide
*/
public static final String EXTRA_PROFILE_UUID = "uuid";
/**
* Extra for {@link INTENT_ACTION_PROFILE_SELECTED}:
* The name of the previously active profile
* @hide
*/
public static final String EXTRA_LAST_PROFILE_NAME = "lastName";
/**
* Extra for {@link INTENT_ACTION_PROFILE_SELECTED}:
* The string representation of the UUID of the previously active profile
* @hide
*/
public static final String EXTRA_LAST_PROFILE_UUID = "uuid";
/**
* <p>Broadcast Action: Current profile has been updated. This is triggered every time the
* currently active profile is updated, instead of selected.</p>
* <p> For instance, this includes profile updates caused by a locale change, which doesn't
* trigger a profile selection, but causes its name to change.</p>
* @hide
*/
public static final String INTENT_ACTION_PROFILE_UPDATED =
"android.intent.action.PROFILE_UPDATED";
/**
* Activity Action: Shows a profile picker.
* <p>
* Input: {@link #EXTRA_PROFILE_EXISTING_UUID}, {@link #EXTRA_PROFILE_SHOW_NONE},
* {@link #EXTRA_PROFILE_TITLE}.
* <p>
* Output: {@link #EXTRA_PROFILE_PICKED_UUID}.
* @hide
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_PROFILE_PICKER = "android.intent.action.PROFILE_PICKER";
/**
* @hide
*/
public static final UUID NO_PROFILE =
UUID.fromString("00000000-0000-0000-0000-000000000000");
/**
* Given to the profile picker as a boolean. Whether to show an item for
* deselect the profile. If the "None" item is picked,
* {@link #EXTRA_PROFILE_PICKED_UUID} will be {@link #NO_PROFILE}.
*
* @see #ACTION_PROFILE_PICKER
* @hide
*/
public static final String EXTRA_PROFILE_SHOW_NONE =
"android.intent.extra.profile.SHOW_NONE";
/**
* Given to the profile picker as a {@link UUID} string representation. The {@link UUID}
* representation of the current profile, which will be used to show a checkmark next to
* the item for this {@link UUID}. If the item is {@link #NO_PROFILE} then "None" item
* is selected if {@link EXTRA_PROFILE_SHOW_NONE} is enabled. Otherwise, the current
* profile is selected.
*
* @see #ACTION_PROFILE_PICKER
* @hide
*/
public static final String EXTRA_PROFILE_EXISTING_UUID =
"android.intent.extra.profile.EXISTING_UUID";
/**
* Given to the profile picker as a {@link CharSequence}. The title to
* show for the profile picker. This has a default value that is suitable
* in most cases.
*
* @see #ACTION_PROFILE_PICKER
* @hide
*/
public static final String EXTRA_PROFILE_TITLE = "android.intent.extra.profile.TITLE";
/**
* Returned from the profile picker as a {@link UUID} string representation.
* <p>
* It will be one of:
* <li> the picked profile,
* <li> null if the "None" item was picked.
*
* @see #ACTION_PROFILE_PICKER
* @hide
*/
public static final String EXTRA_PROFILE_PICKED_UUID =
"android.intent.extra.profile.PICKED_UUID";
/**
* Broadcast intent action indicating that Profiles has been enabled or disabled.
* One extra provides this state as an int.
*
* @see #EXTRA_PROFILES_STATE
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String PROFILES_STATE_CHANGED_ACTION =
"android.app.profiles.PROFILES_STATE_CHANGED";
/**
* The lookup key for an int that indicates whether Profiles are enabled or
* disabled. Retrieve it with {@link android.content.Intent#getIntExtra(String,int)}.
*
* @see #PROFILES_STATE_DISABLED
* @see #PROFILES_STATE_ENABLED
* @hide
*/
public static final String EXTRA_PROFILES_STATE = "profile_state";
/**
* Set the resource id theme to use for the dialog picker activity.<br/>
* The default theme is <code>com.android.internal.R.Theme_Holo_Dialog_Alert</code>.
*
* @see #ACTION_PROFILE_PICKER
* @hide
*/
public static final String EXTRA_PROFILE_DIALOG_THEME =
"android.intent.extra.profile.DIALOG_THEME";
/**
* Profiles are disabled.
*
* @see #PROFILES_STATE_CHANGED_ACTION
* @hide
*/
public static final int PROFILES_STATE_DISABLED = 0;
/**
* Profiles are enabled.
*
* @see #PROFILES_STATE_CHANGED_ACTION
* @hide
*/
public static final int PROFILES_STATE_ENABLED = 1;
// A blank profile that is created to be returned if profiles disabled
private static Profile mEmptyProfile;
private static ProfileManager sProfileManagerInstance;
private ProfileManager(Context context) {
Context appContext = context.getApplicationContext();
if (appContext != null) {
mContext = appContext;
} else {
mContext = context;
}
sService = getService();
mEmptyProfile = new Profile("EmptyProfile");
}
/**
* Get or create an instance of the {@link cyanogenmod.app.ProfileManager}
* @param context
* @return {@link cyanogenmod.app.ProfileManager}
*/
public static ProfileManager getInstance(Context context) {
if (sProfileManagerInstance == null) {
sProfileManagerInstance = new ProfileManager(context);
}
return sProfileManagerInstance;
}
/** @hide */
static public IProfileManager getService() {
if (sService != null) {
return sService;
}
IBinder b = ServiceManager.getService(CMContextConstants.CM_PROFILE_SERVICE);
sService = IProfileManager.Stub.asInterface(b);
return sService;
}
@Deprecated
public void setActiveProfile(String profileName) {
if (Settings.System.getInt(mContext.getContentResolver(),
SYSTEM_PROFILES_ENABLED, 1) == 1) {
// Profiles are enabled, return active profile
try {
getService().setActiveProfileByName(profileName);
} catch (RemoteException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
}
}
}
public void setActiveProfile(UUID profileUuid) {
if (Settings.System.getInt(mContext.getContentResolver(),
SYSTEM_PROFILES_ENABLED, 1) == 1) {
// Profiles are enabled, return active profile
try {
getService().setActiveProfile(new ParcelUuid(profileUuid));
} catch (RemoteException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
}
}
}
public Profile getActiveProfile() {
if (Settings.System.getInt(mContext.getContentResolver(),
SYSTEM_PROFILES_ENABLED, 1) == 1) {
// Profiles are enabled, return active profile
try {
return getService().getActiveProfile();
} catch (RemoteException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
}
return null;
} else {
// Profiles are not enabled, return the empty profile
return mEmptyProfile;
}
}
/** @hide */
public void addProfile(Profile profile) {
try {
getService().addProfile(profile);
} catch (RemoteException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
}
}
/** @hide */
public void removeProfile(Profile profile) {
try {
getService().removeProfile(profile);
} catch (RemoteException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
}
}
/** @hide */
public void updateProfile(Profile profile) {
try {
getService().updateProfile(profile);
} catch (RemoteException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
}
}
@Deprecated
public Profile getProfile(String profileName) {
try {
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);
}
return null;
}
public String[] getProfileNames() {
try {
Profile[] profiles = getService().getProfiles();
String[] names = new String[profiles.length];
for (int i = 0; i < profiles.length; i++) {
names[i] = profiles[i].getName();
}
return names;
} catch (RemoteException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
}
return null;
}
public Profile[] getProfiles() {
try {
return getService().getProfiles();
} catch (RemoteException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
}
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;
}
}
public boolean notificationGroupExists(String notificationGroupName) {
try {
return getService().notificationGroupExistsByName(notificationGroupName);
} catch (RemoteException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
// To be on the safe side, we'll return "true", to prevent duplicate notification
// groups from being created.
return true;
}
}
/** @hide */
public NotificationGroup[] getNotificationGroups() {
try {
return getService().getNotificationGroups();
} catch (RemoteException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
}
return null;
}
/** @hide */
public void addNotificationGroup(NotificationGroup group) {
try {
getService().addNotificationGroup(group);
} catch (RemoteException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
}
}
/** @hide */
public void removeNotificationGroup(NotificationGroup group) {
try {
getService().removeNotificationGroup(group);
} catch (RemoteException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
}
}
/** @hide */
public void updateNotificationGroup(NotificationGroup group) {
try {
getService().updateNotificationGroup(group);
} catch (RemoteException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
}
}
/** @hide */
public NotificationGroup getNotificationGroupForPackage(String pkg) {
try {
return getService().getNotificationGroupForPackage(pkg);
} catch (RemoteException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
}
return null;
}
/** @hide */
public NotificationGroup getNotificationGroup(UUID uuid) {
try {
return getService().getNotificationGroup(new ParcelUuid(uuid));
} catch (RemoteException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
}
return null;
}
/** @hide */
public ProfileGroup getActiveProfileGroup(String packageName) {
NotificationGroup notificationGroup = getNotificationGroupForPackage(packageName);
if (notificationGroup == null) {
ProfileGroup defaultGroup = getActiveProfile().getDefaultGroup();
return defaultGroup;
}
return getActiveProfile().getProfileGroup(notificationGroup.getUuid());
}
/** @hide */
public void resetAll() {
try {
getService().resetAll();
} catch (RemoteException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
} catch (SecurityException e) {
Log.e(TAG, e.getLocalizedMessage(), e);
}
}
}

View File

@ -0,0 +1,144 @@
/*
* Copyright (c) 2013-2014 The 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 cyanogenmod.app;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiSsid;
import android.net.wifi.WifiInfo;
import android.os.Handler;
import android.provider.Settings;
import android.util.Log;
import java.util.UUID;
public class ProfileTriggerHelper extends BroadcastReceiver {
private static final String TAG = "ProfileTriggerHelper";
private Context mContext;
private ProfileManager mManager;
private WifiManager mWifiManager;
private String mLastConnectedSSID;
private IntentFilter mIntentFilter;
private boolean mFilterRegistered = false;
private ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
updateEnabled();
}
};
public ProfileTriggerHelper(Context context, ProfileManager manager) {
mContext = context;
mManager = manager;
mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
mLastConnectedSSID = getActiveSSID();
mIntentFilter = new IntentFilter();
mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
// mIntentFilter.addAction(AudioManager.A2DP_ROUTE_CHANGED_ACTION);
updateEnabled();
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.SYSTEM_PROFILES_ENABLED), false,
mSettingsObserver);
}
public void updateEnabled() {
boolean enabled = Settings.System.getInt(mContext.getContentResolver(),
Settings.System.SYSTEM_PROFILES_ENABLED, 1) == 1;
if (enabled && !mFilterRegistered) {
Log.v(TAG, "Enabling");
mContext.registerReceiver(this, mIntentFilter);
mFilterRegistered = true;
} else if (!enabled && mFilterRegistered) {
Log.v(TAG, "Disabling");
mContext.unregisterReceiver(this);
mFilterRegistered = false;
}
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
String activeSSID = getActiveSSID();
int triggerState;
if (activeSSID != null) {
triggerState = Profile.TriggerState.ON_CONNECT;
mLastConnectedSSID = activeSSID;
} else {
triggerState = Profile.TriggerState.ON_DISCONNECT;
}
checkTriggers(Profile.TriggerType.WIFI, mLastConnectedSSID, triggerState);
} else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)
|| action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
int triggerState = action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)
? Profile.TriggerState.ON_CONNECT : Profile.TriggerState.ON_DISCONNECT;
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
checkTriggers(Profile.TriggerType.BLUETOOTH, device.getAddress(), triggerState);
/* } else if (action.equals(AudioManager.A2DP_ROUTE_CHANGED_ACTION)) {
BluetoothDevice device = intent
.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
int triggerState = (state == BluetoothProfile.STATE_CONNECTED)
? Profile.TriggerState.ON_A2DP_CONNECT :
Profile.TriggerState.ON_A2DP_DISCONNECT;
checkTriggers(Profile.TriggerType.BLUETOOTH, device.getAddress(), triggerState);*/
}
}
private void checkTriggers(int type, String id, int newState) {
/*for (Profile p : mManager.getProfileList()) {
if (newState != p.getTrigger(type, id)) {
continue;
}
UUID currentProfileUuid = mManager.getActiveProfile().getUuid();
if (!currentProfileUuid.equals(p.getUuid())) {
//mManager.setActiveProfile(p, true);
}
}*/
}
private String getActiveSSID() {
WifiInfo wifiinfo = mWifiManager.getConnectionInfo();
if (wifiinfo == null) {
return null;
}
WifiSsid ssid = wifiinfo.getWifiSsid();
if (ssid == null) {
return null;
}
return ssid.toString();
}
}