From 4334b3d969ba557c33106f4894878cac2760c051 Mon Sep 17 00:00:00 2001 From: Adnan Begovic Date: Thu, 25 Jun 2015 12:54:38 -0700 Subject: [PATCH] CMSDK: Introduce Profiles API from frameworks. Refactor to create a system service in secondary CM framework. Change-Id: Ic69da01d941bbd09271c260429d744f8e79ab7b9 --- .../internal/ProfileManagerService.java | 616 +++++++++++++ .../cyanogenmod/app/CMContextConstants.java | 11 + src/java/cyanogenmod/app/IProfileManager.aidl | 50 + src/java/cyanogenmod/app/Profile.aidl | 19 + src/java/cyanogenmod/app/Profile.java | 869 ++++++++++++++++++ src/java/cyanogenmod/app/ProfileGroup.java | 370 ++++++++ src/java/cyanogenmod/app/ProfileManager.java | 467 ++++++++++ .../cyanogenmod/app/ProfileTriggerHelper.java | 144 +++ 8 files changed, 2546 insertions(+) create mode 100644 cm/lib/main/java/org/cyanogenmod/platform/internal/ProfileManagerService.java create mode 100644 src/java/cyanogenmod/app/IProfileManager.aidl create mode 100644 src/java/cyanogenmod/app/Profile.aidl create mode 100755 src/java/cyanogenmod/app/Profile.java create mode 100644 src/java/cyanogenmod/app/ProfileGroup.java create mode 100644 src/java/cyanogenmod/app/ProfileManager.java create mode 100644 src/java/cyanogenmod/app/ProfileTriggerHelper.java diff --git a/cm/lib/main/java/org/cyanogenmod/platform/internal/ProfileManagerService.java b/cm/lib/main/java/org/cyanogenmod/platform/internal/ProfileManagerService.java new file mode 100644 index 0000000..08861ec --- /dev/null +++ b/cm/lib/main/java/org/cyanogenmod/platform/internal/ProfileManagerService.java @@ -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 mProfiles; + + // Match UUIDs and names, used for reverse compatibility + private Map mProfileNames; + + private Map 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(); + mProfileNames = new HashMap(); + mGroups = new HashMap(); + 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 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 getProfileList() { + return mProfiles.values(); + } + + private String getXmlString() { + StringBuilder builder = new StringBuilder(); + builder.append("\n"); + builder.append(TextUtils.htmlEncode(mActiveProfile.getUuid().toString())); + builder.append("\n"); + + for (Profile p : mProfiles.values()) { + p.getXmlString(builder, mContext); + } + for (NotificationGroup g : mGroups.values()) { + g.getXmlString(builder, mContext); + } + builder.append("\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; + } +} diff --git a/src/java/cyanogenmod/app/CMContextConstants.java b/src/java/cyanogenmod/app/CMContextConstants.java index 924f820..3ce899a 100644 --- a/src/java/cyanogenmod/app/CMContextConstants.java +++ b/src/java/cyanogenmod/app/CMContextConstants.java @@ -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"; } diff --git a/src/java/cyanogenmod/app/IProfileManager.aidl b/src/java/cyanogenmod/app/IProfileManager.aidl new file mode 100644 index 0000000..21e68cd --- /dev/null +++ b/src/java/cyanogenmod/app/IProfileManager.aidl @@ -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(); +} diff --git a/src/java/cyanogenmod/app/Profile.aidl b/src/java/cyanogenmod/app/Profile.aidl new file mode 100644 index 0000000..e211a58 --- /dev/null +++ b/src/java/cyanogenmod/app/Profile.aidl @@ -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; diff --git a/src/java/cyanogenmod/app/Profile.java b/src/java/cyanogenmod/app/Profile.java new file mode 100755 index 0000000..0416e24 --- /dev/null +++ b/src/java/cyanogenmod/app/Profile.java @@ -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 mSecondaryUuids = new ArrayList(); + + private Map profileGroups = new HashMap(); + + 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 streams = new HashMap(); + + private Map mTriggers = new HashMap(); + + private Map connections = new HashMap(); + + 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("\">\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 CREATOR + = new Parcelable.Creator() { + 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 CREATOR = new Parcelable.Creator() { + 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 getTriggersFromType(int type) { + ArrayList result = new ArrayList(); + for (Entry 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 uuids = new ArrayList(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 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(" 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(""); + for (UUID u : mSecondaryUuids) { + builder.append(""); + builder.append(TextUtils.htmlEncode(u.toString())); + builder.append(""); + } + builder.append("\n"); + + builder.append(""); + builder.append(getProfileType() == TOGGLE_TYPE ? "toggle" : "conditional"); + builder.append("\n"); + + builder.append(""); + builder.append(getStatusBarIndicator() ? "yes" : "no"); + builder.append("\n"); + + builder.append(""); + builder.append(mScreenLockMode); + builder.append("\n"); + + builder.append(""); + builder.append(mExpandedDesktopMode); + builder.append("\n"); + + builder.append(""); + builder.append(mDozeMode); + builder.append("\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("\n"); + for (ProfileTrigger trigger : mTriggers.values()) { + trigger.getXmlString(builder, context); + } + builder.append("\n"); + } + + builder.append("\n"); + mDirty = false; + } + + private static List readSecondaryUuidsFromXml(XmlPullParser xpp, Context context) + throws XmlPullParserException, + IOException { + ArrayList uuids = new ArrayList(); + 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 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 getConnectionSettings(){ + return connections.values(); + } + +} diff --git a/src/java/cyanogenmod/app/ProfileGroup.java b/src/java/cyanogenmod/app/ProfileGroup.java new file mode 100644 index 0000000..2a0c6fb --- /dev/null +++ b/src/java/cyanogenmod/app/ProfileGroup.java @@ -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 CREATOR + = new Parcelable.Creator() { + 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("\n"); + builder.append(TextUtils.htmlEncode(mSoundOverride.toString())); + builder.append("\n"); + builder.append(TextUtils.htmlEncode(mRingerOverride.toString())); + builder.append("\n"); + builder.append(mSoundMode); + builder.append("\n"); + builder.append(mRingerMode); + builder.append("\n"); + builder.append(mVibrateMode); + builder.append("\n"); + builder.append(mLightsMode); + builder.append("\n\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; + } +} diff --git a/src/java/cyanogenmod/app/ProfileManager.java b/src/java/cyanogenmod/app/ProfileManager.java new file mode 100644 index 0000000..30ae1d7 --- /dev/null +++ b/src/java/cyanogenmod/app/ProfileManager.java @@ -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"; + + /** + *

Broadcast Action: A new profile has been selected. This can be triggered by the user + * or by calls to the ProfileManagerService / Profile.

+ * @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"; + + /** + *

Broadcast Action: Current profile has been updated. This is triggered every time the + * currently active profile is updated, instead of selected.

+ *

For instance, this includes profile updates caused by a locale change, which doesn't + * trigger a profile selection, but causes its name to change.

+ * @hide + */ + public static final String INTENT_ACTION_PROFILE_UPDATED = + "android.intent.action.PROFILE_UPDATED"; + + /** + * Activity Action: Shows a profile picker. + *

+ * Input: {@link #EXTRA_PROFILE_EXISTING_UUID}, {@link #EXTRA_PROFILE_SHOW_NONE}, + * {@link #EXTRA_PROFILE_TITLE}. + *

+ * 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. + *

+ * It will be one of: + *

  • the picked profile, + *
  • 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.
    + * The default theme is com.android.internal.R.Theme_Holo_Dialog_Alert. + * + * @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); + } + } +} diff --git a/src/java/cyanogenmod/app/ProfileTriggerHelper.java b/src/java/cyanogenmod/app/ProfileTriggerHelper.java new file mode 100644 index 0000000..a82d3a2 --- /dev/null +++ b/src/java/cyanogenmod/app/ProfileTriggerHelper.java @@ -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(); + } +}