diff options
author | Danny Baumann <dannybaumann@web.de> | 2014-11-12 17:14:27 -0800 |
---|---|---|
committer | Adnan Begovic <adnan@cyngn.com> | 2015-10-26 16:11:13 -0700 |
commit | d40cd5728a415efd72ee192fa6f67727411ed959 (patch) | |
tree | ae0afabb0d80930130005dd09c6c864e9177cfb1 /src/com/android/settings/profiles | |
parent | cf65d3f14dce8dae3590daac56bdd59be28e1779 (diff) | |
download | packages_apps_Settings-d40cd5728a415efd72ee192fa6f67727411ed959.zip packages_apps_Settings-d40cd5728a415efd72ee192fa6f67727411ed959.tar.gz packages_apps_Settings-d40cd5728a415efd72ee192fa6f67727411ed959.tar.bz2 |
Profiles : Settings
Change-Id: I072758a1c5ec04ef34077551220b6611068fe71d
Diffstat (limited to 'src/com/android/settings/profiles')
24 files changed, 3719 insertions, 0 deletions
diff --git a/src/com/android/settings/profiles/AbstractTriggerPreference.java b/src/com/android/settings/profiles/AbstractTriggerPreference.java new file mode 100644 index 0000000..1bc2ec0 --- /dev/null +++ b/src/com/android/settings/profiles/AbstractTriggerPreference.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2013 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 com.android.settings.profiles; + +import android.app.Profile; +import android.content.Context; +import android.preference.Preference; + +public class AbstractTriggerPreference extends Preference { + + public AbstractTriggerPreference(Context context) { + super(context); + } + + private int mTriggerState = Profile.TriggerState.DISABLED; + + public void setTriggerState(int trigger) { + mTriggerState = trigger; + } + + public int getTriggerState() { + return mTriggerState; + } +} diff --git a/src/com/android/settings/profiles/AppGroupConfig.java b/src/com/android/settings/profiles/AppGroupConfig.java new file mode 100644 index 0000000..8fbcfd0 --- /dev/null +++ b/src/com/android/settings/profiles/AppGroupConfig.java @@ -0,0 +1,438 @@ +/* + * Copyright (C) 2012 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 com.android.settings.profiles; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.NotificationGroup; +import android.app.ProfileManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Handler; +import android.preference.Preference; +import android.preference.PreferenceGroup; +import android.preference.PreferenceScreen; +import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; + +public class AppGroupConfig extends SettingsPreferenceFragment + implements Preference.OnPreferenceChangeListener { + + private static String TAG = "AppGroupConfig"; + + private static final int DIALOG_APPS = 0; + + private static final int DELETE_CONFIRM = 1; + + private static final int DELETE_GROUP_CONFIRM = 2; + + public static final String PROFILE_SERVICE = "profile"; + + private ListView mListView; + + private PackageManager mPackageManager; + + private NotificationGroup mNotificationGroup; + + private ProfileManager mProfileManager; + + private NamePreference mNamePreference; + + private static final int MENU_DELETE = Menu.FIRST; + + private static final int MENU_ADD = Menu.FIRST + 1; + + PackageAdaptor mAppAdapter; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mProfileManager = (ProfileManager) getActivity().getSystemService(PROFILE_SERVICE); + addPreferencesFromResource(R.xml.application_list); + + final Bundle args = getArguments(); + if (args != null) { + mNotificationGroup = (NotificationGroup) args.getParcelable("NotificationGroup"); + mPackageManager = getPackageManager(); + mAppAdapter = new PackageAdaptor(mPackageManager.getInstalledPackages(0)); + mAppAdapter.update(); + + updatePackages(); + + setHasOptionsMenu(true); + } + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + MenuItem delete = menu.add(0, MENU_DELETE, 0, R.string.profile_menu_delete) + .setIcon(R.drawable.ic_menu_trash_holo_dark); + delete.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | + MenuItem.SHOW_AS_ACTION_WITH_TEXT); + + MenuItem addApplication = menu.add(0, MENU_ADD, 0, R.string.profiles_add) + .setIcon(R.drawable.ic_menu_add) + .setAlphabeticShortcut('a'); + addApplication.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | + MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_DELETE: + deleteNotificationGroup(); + return true; + case MENU_ADD: + addNewApp(); + return true; + default: + return false; + } + } + + Preference mAddPreference; + + Preference mDeletePreference; + + private void updatePackages() { + PreferenceScreen prefSet = getPreferenceScreen(); + + // Add the General section + PreferenceGroup generalPrefs = (PreferenceGroup) prefSet.findPreference("general_section"); + if (generalPrefs != null) { + generalPrefs.removeAll(); + + // Name preference + mNamePreference = new NamePreference(getActivity(), mNotificationGroup.getName()); + mNamePreference.setOnPreferenceChangeListener(this); + generalPrefs.addPreference(mNamePreference); + } + + PreferenceGroup applicationsList = (PreferenceGroup) prefSet.findPreference("applications_list"); + if (applicationsList != null) { + applicationsList.removeAll(); + for (String pkg : mNotificationGroup.getPackages()) { + Preference pref = new Preference(getActivity()); + try { + PackageInfo group = mPackageManager.getPackageInfo(pkg, 0); + pref.setKey(group.packageName); + pref.setTitle(group.applicationInfo.loadLabel(mPackageManager)); + Drawable icon = group.applicationInfo.loadIcon(mPackageManager); + pref.setIcon(icon); + pref.setSelectable(true); + pref.setPersistent(false); + applicationsList.addPreference(pref); + } catch (NameNotFoundException e) { + e.printStackTrace(); + } + } + } + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + menu.add(0, R.string.profile_menu_delete, 0, R.string.profile_menu_delete); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + AdapterContextMenuInfo aMenuInfo = (AdapterContextMenuInfo) item.getMenuInfo(); + PackageItem selectedGroup = (PackageItem) mListView.getItemAtPosition(aMenuInfo.position); + switch (item.getItemId()) { + case R.string.profile_menu_delete: + deleteAppFromGroup(selectedGroup); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void deleteAppFromGroup(PackageItem selectedGroup) { + if (selectedGroup != null) { + mNotificationGroup.removePackage(selectedGroup.packageName); + updatePackages(); + } + } + + @Override + public void onPause() { + if (mNotificationGroup != null) { + mProfileManager.addNotificationGroup(mNotificationGroup); + } + super.onPause(); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference == mNamePreference) { + String name = mNamePreference.getName().toString(); + if (!name.equals(mNotificationGroup.getName())) { + if (!mProfileManager.notificationGroupExists(name)) { + mNotificationGroup.setName(name); + } else { + mNamePreference.setName(mNotificationGroup.getName()); + Toast.makeText(getActivity(), R.string.duplicate_appgroup_name, Toast.LENGTH_LONG).show(); + } + } + } + return true; + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + if (preference instanceof Preference) { + String deleteItem = preference.getKey(); + removeApp(deleteItem); + return true; + } + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + + private void addNewApp() { + showDialog(DIALOG_APPS); + // TODO: switch to using the built in app list rather than dialog box? + } + + private void removeApp(String key) { + mPackageToDelete = key.toString(); + showDialog(DELETE_CONFIRM); + } + + private void deleteNotificationGroup() { + showDialog(DELETE_GROUP_CONFIRM); + } + + @Override + public Dialog onCreateDialog(int id) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + final Dialog dialog; + switch (id) { + case DIALOG_APPS: + final ListView list = new ListView(getActivity()); + list.setAdapter(mAppAdapter); + builder.setTitle(R.string.profile_choose_app); + builder.setView(list); + dialog = builder.create(); + list.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + PackageItem info = (PackageItem) parent.getItemAtPosition(position); + mNotificationGroup.addPackage(info.packageName); + updatePackages(); + dialog.cancel(); + } + }); + break; + case DELETE_CONFIRM: + builder.setMessage(R.string.profile_app_delete_confirm); + builder.setTitle(R.string.profile_menu_delete); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setPositiveButton(android.R.string.yes, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + doDelete(); + } + }); + builder.setNegativeButton(android.R.string.no, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + } + }); + dialog = builder.create(); + break; + case DELETE_GROUP_CONFIRM: + builder.setMessage(R.string.profile_delete_appgroup); + builder.setTitle(R.string.profile_menu_delete); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setPositiveButton(android.R.string.yes, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mProfileManager.removeNotificationGroup(mNotificationGroup); + mNotificationGroup = null; + finish(); + } + }); + builder.setNegativeButton(android.R.string.no, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + } + }); + dialog = builder.create(); + break; + default: + dialog = null; + } + return dialog; + } + + String mPackageToDelete; + + private void doDelete() { + mNotificationGroup.removePackage(mPackageToDelete); + updatePackages(); + } + + class PackageItem implements Comparable<PackageItem> { + String title; + String packageName; + Drawable icon; + boolean enabled; + + @Override + public int compareTo(PackageItem another) { + if (enabled != another.enabled) { + return enabled ? -1 : 1; + } + int titleResult = title.compareToIgnoreCase(another.title); + if (titleResult != 0) { + return titleResult; + } + return packageName.compareTo(another.packageName); + } + } + + class PackageAdaptor extends BaseAdapter { + + protected List<PackageInfo> mInstalledPackageInfo; + + protected List<PackageItem> mInstalledPackages = new LinkedList<PackageItem>(); + + private void reloadList() { + final Handler handler = new Handler(); + new Thread(new Runnable() { + + @Override + public void run() { + synchronized (mInstalledPackages) { + mInstalledPackages.clear(); + for (PackageInfo info : mInstalledPackageInfo) { + final PackageItem item = new PackageItem(); + ApplicationInfo applicationInfo = info.applicationInfo; + item.title = applicationInfo.loadLabel(mPackageManager).toString(); + item.icon = applicationInfo.loadIcon(mPackageManager); + item.packageName = applicationInfo.packageName; + item.enabled = applicationInfo.enabled; + handler.post(new Runnable() { + @Override + public void run() { + int index = Collections.binarySearch(mInstalledPackages, item); + if (index < 0) { + index = -index - 1; + mInstalledPackages.add(index, item); + } + notifyDataSetChanged(); + } + }); + } + } + } + }).start(); + } + + public PackageAdaptor(List<PackageInfo> installedPackagesInfo) { + mInstalledPackageInfo = installedPackagesInfo; + } + + public void update() { + reloadList(); + } + + @Override + public int getCount() { + return mInstalledPackages.size(); + } + + @Override + public PackageItem getItem(int position) { + return mInstalledPackages.get(position); + } + + @Override + public long getItemId(int position) { + return mInstalledPackages.get(position).packageName.hashCode(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ViewHolder holder; + if (convertView != null) { + holder = (ViewHolder) convertView.getTag(); + } else { + final LayoutInflater layoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); + convertView = layoutInflater.inflate(R.layout.preference_icon, null, false); + holder = new ViewHolder(); + convertView.setTag(holder); + holder.title = (TextView) convertView.findViewById(com.android.internal.R.id.title); + holder.summary = (TextView) convertView + .findViewById(com.android.internal.R.id.summary); + holder.icon = (ImageView) convertView.findViewById(R.id.icon); + } + PackageItem applicationInfo = getItem(position); + + if (holder.title != null) { + holder.title.setText(applicationInfo.title); + } + if (holder.summary != null) { + holder.summary.setVisibility(View.GONE); + } + if (holder.icon != null) { + Drawable loadIcon = applicationInfo.icon; + holder.icon.setImageDrawable(loadIcon); + } + return convertView; + } + } + + static class ViewHolder { + TextView title; + TextView summary; + ImageView icon; + } +} diff --git a/src/com/android/settings/profiles/AppGroupList.java b/src/com/android/settings/profiles/AppGroupList.java new file mode 100644 index 0000000..088b344 --- /dev/null +++ b/src/com/android/settings/profiles/AppGroupList.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2012 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 com.android.settings.profiles; + +import java.util.UUID; + +import android.app.NotificationGroup; +import android.app.ProfileManager; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceScreen; +import android.provider.Settings; + +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.Utils; + +public class AppGroupList extends SettingsPreferenceFragment { + + private static final String TAG = "AppGroupSettings"; + public static final String PROFILE_SERVICE = "profile"; + + private ProfileManager mProfileManager; + + // constant value that can be used to check return code from sub activity. + private static final int APP_GROUP_CONFIG = 1; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.appgroup_list); + mProfileManager = (ProfileManager) getActivity().getSystemService(PROFILE_SERVICE); + } + + @Override + public void onResume() { + super.onResume(); + refreshList(); + + // On tablet devices remove the padding + if (Utils.isTablet(getActivity())) { + getListView().setPadding(0, 0, 0, 0); + } + } + + public void refreshList() { + PreferenceScreen appgroupList = getPreferenceScreen(); + appgroupList.removeAll(); + + // Add the existing app groups + for (NotificationGroup group : mProfileManager.getNotificationGroups()) { + PreferenceScreen pref = new PreferenceScreen(getActivity(), null); + pref.setKey(group.getUuid().toString()); + pref.setTitle(group.getName()); + pref.setPersistent(false); + appgroupList.addPreference(pref); + } + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + if (preference instanceof PreferenceScreen) { + NotificationGroup group = mProfileManager.getNotificationGroup( + UUID.fromString(preference.getKey())); + editGroup(group); + } + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + + private void editGroup(NotificationGroup group) { + Bundle args = new Bundle(); + args.putParcelable("NotificationGroup", group); + + SettingsActivity pa = (SettingsActivity) getActivity(); + pa.startPreferencePanel(AppGroupConfig.class.getName(), args, + R.string.profile_appgroup_manage, null, this, APP_GROUP_CONFIG); + } +} diff --git a/src/com/android/settings/profiles/ApplicationItemPreference.java b/src/com/android/settings/profiles/ApplicationItemPreference.java new file mode 100644 index 0000000..1247e85 --- /dev/null +++ b/src/com/android/settings/profiles/ApplicationItemPreference.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2012 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 com.android.settings.profiles; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.preference.Preference; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.widget.ImageView; +import com.android.settings.R; + +public class ApplicationItemPreference extends Preference { + + private static String TAG = "ApplicationItemPreference"; + + private Drawable mIcon; + + public ApplicationItemPreference(Context context) { + this(context, null, 0); + } + + public ApplicationItemPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + setLayoutResource(R.layout.preference_icon); + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.IconPreferenceScreen, defStyle, 0); + mIcon = a.getDrawable(R.styleable.IconPreferenceScreen_icon); + } + + public void setIcon(Drawable icon){ + mIcon = icon; + } + + @Override + public void onBindView(View view) { + super.onBindView(view); + + float valueDips = 36.0f; + final float scale = getContext().getResources().getDisplayMetrics().density; + int valuePixels = (int) (valueDips * scale + 0.5f); + + ImageView imageView = (ImageView) view.findViewById(R.id.icon); + if (imageView != null && mIcon != null) { + imageView.setAdjustViewBounds(true); + imageView.setMaxHeight(valuePixels); + imageView.setMaxWidth(valuePixels); + imageView.setImageDrawable(mIcon); + } + } +} diff --git a/src/com/android/settings/profiles/BluetoothTriggerPreference.java b/src/com/android/settings/profiles/BluetoothTriggerPreference.java new file mode 100644 index 0000000..848fc28 --- /dev/null +++ b/src/com/android/settings/profiles/BluetoothTriggerPreference.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2013 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 com.android.settings.profiles; + +import android.bluetooth.BluetoothDevice; +import android.content.Context; + +public class BluetoothTriggerPreference extends AbstractTriggerPreference { + + private String mAddress; + + BluetoothTriggerPreference(Context context, BluetoothDevice device) { + super(context); + mAddress = device.getAddress(); + if (device.getAlias() != null) { + setTitle(device.getAlias()); + } else { + setTitle(device.getName()); + } + } + + BluetoothTriggerPreference(Context context, String name, String address) { + super(context); + mAddress = address; + setTitle(name); + } + + public String getAddress() { + return mAddress; + } +} diff --git a/src/com/android/settings/profiles/NFCProfile.java b/src/com/android/settings/profiles/NFCProfile.java new file mode 100644 index 0000000..a6e2334 --- /dev/null +++ b/src/com/android/settings/profiles/NFCProfile.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2012 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 com.android.settings.profiles; + +import java.util.UUID; + +import android.app.Activity; +import android.app.Profile; +import android.app.ProfileManager; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.nfc.NdefMessage; +import android.nfc.NdefRecord; +import android.nfc.NfcAdapter; +import android.os.Bundle; +import android.os.Parcelable; +import android.provider.Settings; +import android.widget.Toast; + +import com.android.settings.R; + +/** + * This activity handles NDEF_DISCOVERED intents with the cm/profile mime type. + * Tags should be encoded with the 16-byte UUID of the profile to be activated. + * Tapping a tag while that profile is already active will select the previously + * active profile. + */ +public class NFCProfile extends Activity { + + private static final String PREFS_NAME = "NFCProfile"; + + private static final String PREFS_PREVIOUS_PROFILE = "previous-profile"; + + static final String PROFILE_MIME_TYPE = "cm/profile"; + + private ProfileManager mProfileManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mProfileManager = (ProfileManager) getSystemService(Context.PROFILE_SERVICE); + } + + @Override + protected void onResume() { + super.onResume(); + + Intent intent = getIntent(); + String action = intent.getAction(); + if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) { + Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); + if (rawMsgs != null) { + NdefMessage[] msgs = new NdefMessage[rawMsgs.length]; + for (int i = 0; i < rawMsgs.length; i++) { + msgs[i] = (NdefMessage) rawMsgs[i]; + for (NdefRecord record : msgs[i].getRecords()) { + String type = new String(record.getType()); + byte[] payload = record.getPayload(); + if (PROFILE_MIME_TYPE.equals(type) && payload != null + && payload.length == 16) { + handleProfileMimeType(payload); + } + } + } + } + } + finish(); + } + + private void handleProfileMimeType(byte[] payload) { + UUID profileUuid = NFCProfileUtils.toUUID(payload); + + boolean enabled = Settings.System.getInt(getContentResolver(), + Settings.System.SYSTEM_PROFILES_ENABLED, 1) == 1; + + if (enabled) { + // Only do NFC profile changing if System Profile support is enabled + Profile currentProfile = mProfileManager.getActiveProfile(); + Profile targetProfile = mProfileManager.getProfile(profileUuid); + + if (targetProfile == null) { + // show profile selection for unknown tag + Intent i = new Intent(this, NFCProfileSelect.class); + i.putExtra(NFCProfileSelect.EXTRA_PROFILE_UUID, profileUuid.toString()); + i.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + this.startActivity(i); + } else { + // switch to profile + if (currentProfile == null || !currentProfile.getUuid().equals(profileUuid)) { + saveCurrentProfile(); + switchTo(profileUuid); + } else { + Profile lastProfile = getPreviouslySelectedProfile(); + if (lastProfile != null) { + switchTo(lastProfile.getUuid()); + clearPreviouslySelectedProfile(); + } + } + } + } + } + + private void switchTo(UUID uuid) { + Profile p = mProfileManager.getProfile(uuid); + if (p != null) { + mProfileManager.setActiveProfile(uuid); + + Toast.makeText( + this, + getString(R.string.profile_selected, p.getName()), + Toast.LENGTH_LONG).show(); + NFCProfileUtils.vibrate(this); + } + } + + private Profile getPreviouslySelectedProfile() { + Profile previous = null; + SharedPreferences prefs = getSharedPreferences(PREFS_NAME, 0); + String uuid = prefs.getString(PREFS_PREVIOUS_PROFILE, null); + if (uuid != null) { + previous = mProfileManager.getProfile(UUID.fromString(uuid)); + } + return previous; + } + + private void clearPreviouslySelectedProfile() { + SharedPreferences.Editor editor = getSharedPreferences(PREFS_NAME, 0).edit(); + editor.remove(PREFS_PREVIOUS_PROFILE); + editor.commit(); + } + + private void saveCurrentProfile() { + Profile currentProfile = mProfileManager.getActiveProfile(); + if (currentProfile != null) { + SharedPreferences.Editor editor = getSharedPreferences(PREFS_NAME, 0).edit(); + editor.putString(PREFS_PREVIOUS_PROFILE, currentProfile.getUuid().toString()); + editor.commit(); + } + } +} diff --git a/src/com/android/settings/profiles/NFCProfileSelect.java b/src/com/android/settings/profiles/NFCProfileSelect.java new file mode 100644 index 0000000..c66de64 --- /dev/null +++ b/src/com/android/settings/profiles/NFCProfileSelect.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2012 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 com.android.settings.profiles; + +import java.util.UUID; + +import android.app.Activity; +import android.app.PendingIntent; +import android.app.Profile; +import android.app.ProfileManager; +import android.app.AlertDialog.Builder; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Toast; + +import com.android.settings.R; + +/** + * Activity to support attaching a unknown NFC tag to an existing profile. + */ +public class NFCProfileSelect extends Activity { + + private static final String TAG = "NFCProfileSelect"; + + static final String EXTRA_PROFILE_UUID = "PROFILE_UUID"; + + private ProfileManager mProfileManager; + + private UUID mProfileUuid; + + final static int defaultChoice = -1; + + private int currentChoice = defaultChoice; + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mProfileManager = (ProfileManager) getSystemService(Context.PROFILE_SERVICE); + + setContentView(R.layout.nfc_select); + setTitle(R.string.profile_unknown_nfc_tag); + + findViewById(R.id.add_tag).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + showProfileSelectionDialog(); + } + }); + } + + @Override + public void onResume() { + super.onResume(); + + String profileUuid = getIntent().getStringExtra(EXTRA_PROFILE_UUID); + if (profileUuid != null) { + mProfileUuid = UUID.fromString(profileUuid); + } else { + finish(); + } + } + + void showProfileSelectionDialog() { + final Profile[] profiles = mProfileManager.getProfiles(); + final String[] profileNames = new String[profiles.length]; + for (int i = 0; i < profiles.length; i++) { + profileNames[i] = profiles[i].getName(); + } + + Builder builder = new Builder(this); + builder.setTitle(R.string.profile_settings_title); + builder.setSingleChoiceItems(profileNames, currentChoice, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + currentChoice = which; + } + }); + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (currentChoice != defaultChoice) { + Profile profile = profiles[currentChoice]; + profile.addSecondaryUuid(mProfileUuid); + mProfileManager.updateProfile(profile); + Toast.makeText(NFCProfileSelect.this, R.string.profile_write_success, Toast.LENGTH_LONG).show(); + } + finish(); + } + }); + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + builder.show(); + } +} diff --git a/src/com/android/settings/profiles/NFCProfileUtils.java b/src/com/android/settings/profiles/NFCProfileUtils.java new file mode 100644 index 0000000..f6461d1 --- /dev/null +++ b/src/com/android/settings/profiles/NFCProfileUtils.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2012 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 com.android.settings.profiles; + +import java.io.IOException; +import java.util.UUID; + +import android.app.Profile; +import android.content.Context; +import android.nfc.NdefMessage; +import android.nfc.NdefRecord; +import android.nfc.Tag; +import android.nfc.tech.Ndef; +import android.nfc.tech.NdefFormatable; +import android.os.Vibrator; +import android.util.Log; + +public class NFCProfileUtils { + + private static final String TAG = "NFCUtils"; + + private static final long[] VIBRATION_PATTERN = { + 0, 100, 10000 + }; + + static void vibrate(Context context) { + Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); + vibrator.vibrate(VIBRATION_PATTERN, -1); + } + + /* + * Writes an NdefMessage to a NFC tag + */ + static boolean writeTag(NdefMessage message, Tag tag) { + int size = message.toByteArray().length; + try { + Ndef ndef = Ndef.get(tag); + if (ndef != null) { + ndef.connect(); + if (!ndef.isWritable()) { + Log.e(TAG, "Tag is not writable!"); + return false; + } + if (ndef.getMaxSize() < size) { + Log.e(TAG, + "Tag exceeds max ndef message size! [" + size + " > " + + ndef.getMaxSize() + "]"); + return false; + } + ndef.writeNdefMessage(message); + return true; + } else { + NdefFormatable format = NdefFormatable.get(tag); + if (format != null) { + try { + format.connect(); + format.format(message); + return true; + } catch (IOException e) { + Log.e(TAG, "Write error!", e); + return false; + } + } else { + return false; + } + } + } catch (Exception e) { + Log.e(TAG, "Write error!", e); + return false; + } + } + + /* Convert a 16-byte array to a UUID */ + static UUID toUUID(byte[] byteArray) { + + long msb = 0; + long lsb = 0; + for (int i = 0; i < 8; i++) { + msb = (msb << 8) | (byteArray[i] & 0xff); + } + for (int i = 8; i < 16; i++) { + lsb = (lsb << 8) | (byteArray[i] & 0xff); + } + UUID result = new UUID(msb, lsb); + + return result; + } + + /* Convert a UUID to a 16-byte array */ + static byte[] asByteArray(UUID uuid) { + long msb = uuid.getMostSignificantBits(); + long lsb = uuid.getLeastSignificantBits(); + byte[] buffer = new byte[16]; + + for (int i = 0; i < 8; i++) { + buffer[i] = (byte) (msb >>> 8 * (7 - i)); + } + for (int i = 8; i < 16; i++) { + buffer[i] = (byte) (lsb >>> 8 * (7 - i)); + } + + return buffer; + } + + /* + * Convert a profiles into an NdefMessage. The profile UUID is 16 bytes and + * stored with the cm/profile mimetype + */ + static NdefMessage getProfileAsNdef(Profile profile) { + byte[] profileBytes = NFCProfileUtils.asByteArray(profile.getUuid()); + + NdefRecord record = NdefRecord.createMime(NFCProfile.PROFILE_MIME_TYPE, profileBytes); + return new NdefMessage(new NdefRecord[] { record }); + } +} diff --git a/src/com/android/settings/profiles/NFCProfileWriter.java b/src/com/android/settings/profiles/NFCProfileWriter.java new file mode 100644 index 0000000..72b8c13 --- /dev/null +++ b/src/com/android/settings/profiles/NFCProfileWriter.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2012 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 com.android.settings.profiles; + +import java.util.UUID; + +import android.app.Activity; +import android.app.PendingIntent; +import android.app.Profile; +import android.app.ProfileManager; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.os.Bundle; +import android.util.Log; +import android.widget.Toast; + +import com.android.settings.R; + +/** + * Activity to support writing a profile to an NFC tag. + * The mime type is "cm/profile" and the payload is the raw bytes of the profile's + * UUID. The payload was intentionally kept small to support writing on 46-byte tags. + */ +public class NFCProfileWriter extends Activity { + + private static final String TAG = "NFCProfileWriter"; + + static final String EXTRA_PROFILE_UUID = "PROFILE_UUID"; + + private NfcAdapter mNfcAdapter; + + private IntentFilter[] mWriteTagFilters; + + private Profile mProfile; + + private ProfileManager mProfileManager; + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mNfcAdapter = NfcAdapter.getDefaultAdapter(this); + mProfileManager = (ProfileManager) getSystemService(Context.PROFILE_SERVICE); + + setContentView(R.layout.nfc_writer); + setTitle(R.string.profile_write_nfc_tag); + } + + @Override + public void onResume() { + super.onResume(); + String profileUuid = getIntent().getStringExtra(EXTRA_PROFILE_UUID); + if (profileUuid != null) { + mProfile = mProfileManager.getProfile(UUID.fromString(profileUuid)); + Log.d(TAG, "Profile to write: " + mProfile.getName()); + enableTagWriteMode(); + } + } + + + @Override + protected void onPause() { + super.onPause(); + disableTagWriteMode(); + } + + private PendingIntent getPendingIntent() { + return PendingIntent.getActivity(this, 0, + new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); + } + + private void disableTagWriteMode() { + mNfcAdapter.disableForegroundDispatch(this); + } + + private void enableTagWriteMode() { + IntentFilter tagDetected = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED); + mWriteTagFilters = new IntentFilter[] { + tagDetected + }; + mNfcAdapter.enableForegroundDispatch(this, getPendingIntent(), mWriteTagFilters, null); + } + + @Override + protected void onNewIntent(Intent intent) { + // Tag writing mode + if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) { + Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); + if (NFCProfileUtils.writeTag(NFCProfileUtils.getProfileAsNdef(mProfile), detectedTag)) { + Toast.makeText(this, R.string.profile_write_success, Toast.LENGTH_LONG).show(); + NFCProfileUtils.vibrate(this); + } else { + Toast.makeText(this, R.string.profile_write_failed, Toast.LENGTH_LONG).show(); + } + finish(); + } + } +} diff --git a/src/com/android/settings/profiles/NamePreference.java b/src/com/android/settings/profiles/NamePreference.java new file mode 100644 index 0000000..7b48e0c --- /dev/null +++ b/src/com/android/settings/profiles/NamePreference.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2012 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 com.android.settings.profiles; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.preference.Preference; +import android.view.View; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.settings.R; + +public class NamePreference extends Preference implements + View.OnClickListener, Preference.OnPreferenceChangeListener { + private static final String TAG = NamePreference.class.getSimpleName(); + + private TextView mNameView; + + private String mName; + + /** + * @param context + * @param title + */ + public NamePreference(Context context, String name) { + super(context); + mName = name.toString(); + init(); + } + + /** + * @param context + */ + public NamePreference(Context context) { + super(context); + init(); + } + + @Override + public void onBindView(View view) { + super.onBindView(view); + + View namePref = view.findViewById(R.id.name_pref); + if ((namePref != null) && namePref instanceof LinearLayout) { + namePref.setOnClickListener(this); + } + + mNameView = (TextView) view.findViewById(R.id.title); + + updatePreferenceViews(); + } + + private void init() { + setLayoutResource(R.layout.preference_name); + } + + public void setName(String name) { + mName = (name.toString()); + updatePreferenceViews(); + } + + public String getName() { + return(mName.toString()); + } + + private void updatePreferenceViews() { + if (mNameView != null) { + mNameView.setText(mName.toString()); + } + } + + @Override + public void onClick(android.view.View v) { + if (v != null) { + Context context = getContext(); + if (context != null) { + final EditText entry = new EditText(context); + entry.setSingleLine(); + entry.setText(mName.toString()); + + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.rename_dialog_title); + builder.setMessage(R.string.rename_dialog_message); + builder.setView(entry, 34, 16, 34, 16); + builder.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String value = entry.getText().toString(); + mName = value.toString(); + mNameView.setText(value.toString()); + callChangeListener(this); + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + AlertDialog dialog = builder.create(); + dialog.show(); + ((TextView)dialog.findViewById(android.R.id.message)).setTextAppearance(context, + android.R.style.TextAppearance_DeviceDefault_Small); + } + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + callChangeListener(preference); + return false; + } +} diff --git a/src/com/android/settings/profiles/ProfileAirplaneModePreference.java b/src/com/android/settings/profiles/ProfileAirplaneModePreference.java new file mode 100644 index 0000000..067e43b --- /dev/null +++ b/src/com/android/settings/profiles/ProfileAirplaneModePreference.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2012 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 com.android.settings.profiles; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.preference.Preference; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.LinearLayout; + +import com.android.settings.R; + +public class ProfileAirplaneModePreference extends Preference implements + CompoundButton.OnCheckedChangeListener, View.OnClickListener { + + private boolean mProtectFromCheckedChange = false; + + private CheckBox mCheckBox; + + final static String TAG = "ProfileSilentModePreference"; + + private ProfileConfig.AirplaneModeItem mAirplaneModeItem; + + final static int defaultChoice = -1; + + private int currentChoice; + + /** + * @param context + * @param attrs + * @param defStyle + */ + public ProfileAirplaneModePreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + /** + * @param context + * @param attrs + */ + public ProfileAirplaneModePreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + /** + * @param context + */ + public ProfileAirplaneModePreference(Context context) { + super(context); + init(); + } + + @Override + public View getView(View convertView, ViewGroup parent) { + View view = super.getView(convertView, parent); + + View widget = view.findViewById(R.id.profile_checkbox); + if ((widget != null) && widget instanceof CheckBox) { + mCheckBox = (CheckBox) widget; + mCheckBox.setOnCheckedChangeListener(this); + + mProtectFromCheckedChange = true; + mCheckBox.setChecked(isChecked()); + mProtectFromCheckedChange = false; + } + + View textLayout = view.findViewById(R.id.text_layout); + if ((textLayout != null) && textLayout instanceof LinearLayout) { + textLayout.setOnClickListener(this); + } + + return view; + } + + private void init() { + setLayoutResource(R.layout.preference_streamvolume); + } + + public boolean isChecked() { + return mAirplaneModeItem != null && mAirplaneModeItem.mSettings.isOverride(); + } + + public void setAirplaneModeItem(ProfileConfig.AirplaneModeItem airplaneModeItem) { + mAirplaneModeItem = airplaneModeItem; + + if (mCheckBox != null) { + mCheckBox.setChecked(mAirplaneModeItem.mSettings.isOverride()); + } + } + + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (mProtectFromCheckedChange) { + return; + } + + mAirplaneModeItem.mSettings.setOverride(isChecked); + + callChangeListener(isChecked); + } + + protected Dialog createAirplaneModeDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + final String[] AirplaneModeValues = getContext().getResources().getStringArray( + R.array.profile_connection_values); + + currentChoice = mAirplaneModeItem.mSettings.getValue(); + + builder.setTitle(R.string.profile_airplanemode_title); + builder.setSingleChoiceItems(R.array.profile_connection_entries, currentChoice, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int item) { + currentChoice = item; + } + }); + + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int item) { + if (currentChoice != defaultChoice) { + int value = Integer.parseInt(AirplaneModeValues[currentChoice]); + mAirplaneModeItem.mSettings.setValue(currentChoice); + setSummary(value == 1 ? getContext().getString(R.string.connection_state_enabled) : getContext() + .getString(R.string.connection_state_disabled)); + } + } + }); + + builder.setNegativeButton(android.R.string.cancel, null); + return builder.create(); + } + + public ProfileConfig.AirplaneModeItem getAirplaneModeItem() { + return mAirplaneModeItem; + } + + @Override + public void onClick(android.view.View v) { + if ((v != null) && (R.id.text_layout == v.getId())) { + createAirplaneModeDialog().show(); + } + } + + public void setSummary(Context context) { + int value = mAirplaneModeItem.mSettings.getValue(); + setSummary(value == 1 ? getContext().getString(R.string.connection_state_enabled) : getContext().getString( + R.string.connection_state_disabled)); + } +} diff --git a/src/com/android/settings/profiles/ProfileConfig.java b/src/com/android/settings/profiles/ProfileConfig.java new file mode 100644 index 0000000..3532720 --- /dev/null +++ b/src/com/android/settings/profiles/ProfileConfig.java @@ -0,0 +1,479 @@ +/* + * Copyright (C) 2012 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 com.android.settings.profiles; + +import java.util.ArrayList; +import java.util.UUID; + +import android.app.AirplaneModeSettings; +import android.app.AlertDialog; +import android.app.ConnectionSettings; +import android.app.Profile; +import android.app.ProfileGroup; +import android.app.ProfileManager; +import android.app.RingModeSettings; +import android.app.StreamSettings; +import android.app.admin.DevicePolicyManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.media.AudioManager; +import android.net.wimax.WimaxHelper; +import android.os.Bundle; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceGroup; +import android.preference.PreferenceScreen; +import android.telephony.TelephonyManager; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.widget.Toast; + +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import static com.android.settings.profiles.ProfilesUtils.*; +import com.android.settings.SettingsPreferenceFragment; + +public class ProfileConfig extends SettingsPreferenceFragment + implements Preference.OnPreferenceChangeListener { + + static final String TAG = "ProfileConfig"; + + public static final String PROFILE_SERVICE = "profile"; + + private ProfileManager mProfileManager; + + private static final int MENU_NFC_WRITE = Menu.FIRST; + + private static final int MENU_DELETE = Menu.FIRST + 1; + + private static final int MENU_TRIGGERS = Menu.FIRST + 2; + + private Profile mProfile; + + private NamePreference mNamePreference; + + private ListPreference mScreenLockModePreference; + + // constant value that can be used to check return code from sub activity. + private static final int PROFILE_GROUP_DETAILS = 1; + + private StreamItem[] mStreams; + + private ArrayList<ConnectionItem> mConnections; + + private RingModeItem mRingMode; + + private AirplaneModeItem mAirplaneMode; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + mStreams = new StreamItem[] { + new StreamItem(AudioManager.STREAM_ALARM, getString(R.string.alarm_volume_title)), + new StreamItem(AudioManager.STREAM_MUSIC, getString(R.string.media_volume_title)), + new StreamItem(AudioManager.STREAM_RING, getString(R.string.incoming_call_volume_title)), + new StreamItem(AudioManager.STREAM_NOTIFICATION, getString(R.string.notification_volume_title)) + }; + + mConnections = new ArrayList<ConnectionItem>(); + if (deviceSupportsBluetooth()) { + mConnections.add(new ConnectionItem(ConnectionSettings.PROFILE_CONNECTION_BLUETOOTH, getString(R.string.toggleBluetooth))); + } + mConnections.add(new ConnectionItem(ConnectionSettings.PROFILE_CONNECTION_GPS, getString(R.string.toggleGPS))); + mConnections.add(new ConnectionItem(ConnectionSettings.PROFILE_CONNECTION_WIFI, getString(R.string.toggleWifi))); + mConnections.add(new ConnectionItem(ConnectionSettings.PROFILE_CONNECTION_SYNC, getString(R.string.toggleSync))); + if (deviceSupportsMobileData(getActivity())) { + mConnections.add(new ConnectionItem(ConnectionSettings.PROFILE_CONNECTION_MOBILEDATA, getString(R.string.toggleData))); + mConnections.add(new ConnectionItem(ConnectionSettings.PROFILE_CONNECTION_WIFIAP, getString(R.string.toggleWifiAp))); + final TelephonyManager tm = (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE); + if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) { + mConnections.add(new ConnectionItem(ConnectionSettings.PROFILE_CONNECTION_2G3G, getString(R.string.toggle2g3g), R.array.profile_networkmode_entries)); + } + } + if (WimaxHelper.isWimaxSupported(getActivity())) { + mConnections.add(new ConnectionItem(ConnectionSettings.PROFILE_CONNECTION_WIMAX, getString(R.string.toggleWimax))); + } + if (deviceSupportsNfc(getActivity())) { + mConnections.add(new ConnectionItem(ConnectionSettings.PROFILE_CONNECTION_NFC, getString(R.string.toggleNfc))); + } + + addPreferencesFromResource(R.xml.profile_config); + + mProfileManager = (ProfileManager) getActivity().getSystemService(PROFILE_SERVICE); + + final Bundle args = getArguments(); + mProfile = (args != null) ? (Profile) args.getParcelable("Profile") : null; + + if (mProfile == null) { + mProfile = new Profile(getString(R.string.new_profile_name)); + mProfileManager.addProfile(mProfile); + } + + setHasOptionsMenu(true); + + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + if (deviceSupportsNfc(getActivity())) { + MenuItem nfc = menu.add(0, MENU_NFC_WRITE, 0, R.string.profile_write_nfc_tag) + .setIcon(R.drawable.ic_menu_nfc_writer_dark); + nfc.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | + MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + MenuItem triggers = menu.add(0, MENU_TRIGGERS, 0, R.string.profile_triggers) + .setIcon(R.drawable.ic_location); + triggers.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | + MenuItem.SHOW_AS_ACTION_WITH_TEXT); + MenuItem delete = menu.add(0, MENU_DELETE, 1, R.string.profile_menu_delete) + .setIcon(R.drawable.ic_menu_trash_holo_dark); + delete.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | + MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_DELETE: + deleteProfile(); + return true; + case MENU_NFC_WRITE: + startNFCProfileWriter(); + return true; + case MENU_TRIGGERS: + startTriggerFragment(); + return true; + default: + return false; + } + } + + @Override + public void onResume() { + super.onResume(); + mProfile = mProfileManager.getProfile(mProfile.getUuid()); + fillList(); + } + + @Override + public void onPause() { + super.onPause(); + // Save profile here + if (mProfile != null) { + mProfileManager.updateProfile(mProfile); + } + } + + private void startNFCProfileWriter() { + SettingsActivity pa = (SettingsActivity) getActivity(); + Intent i = new Intent(this.getActivity(), NFCProfileWriter.class); + i.putExtra(NFCProfileWriter.EXTRA_PROFILE_UUID, mProfile.getUuid().toString()); + i.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + pa.startActivity(i); + } + + private void startTriggerFragment() { + final SettingsActivity pa = (SettingsActivity) getActivity(); + final Bundle args = new Bundle(); + args.putParcelable("profile", mProfile); + + pa.startPreferencePanel(TriggersFragment.class.getName(), args, 0, "", null, 0); + } + + private void fillList() { + PreferenceScreen prefSet = getPreferenceScreen(); + + // Add the General section + PreferenceGroup generalPrefs = (PreferenceGroup) prefSet.findPreference("profile_general_section"); + if (generalPrefs != null) { + generalPrefs.removeAll(); + + // Name preference + mNamePreference = new NamePreference(getActivity(), mProfile.getName()); + mNamePreference.setOnPreferenceChangeListener(this); + generalPrefs.addPreference(mNamePreference); + } + + // Populate system settings + PreferenceGroup systemPrefs = (PreferenceGroup) prefSet.findPreference("profile_system_settings"); + if (systemPrefs != null) { + systemPrefs.removeAll(); + // Ring mode preference + if (mRingMode == null) { + mRingMode = new RingModeItem(); + } + RingModeSettings rms = mProfile.getRingMode(); + if (rms == null) { + rms = new RingModeSettings(); + mProfile.setRingMode(rms); + } + mRingMode.mSettings = rms; + ProfileRingModePreference rmp = new ProfileRingModePreference(getActivity()); + rmp.setRingModeItem(mRingMode); + rmp.setTitle(R.string.ring_mode_title); + rmp.setPersistent(false); + rmp.setSummary(getActivity()); + rmp.setOnPreferenceChangeListener(this); + mRingMode.mCheckbox = rmp; + systemPrefs.addPreference(rmp); + + // Airplane mode preference + if (mAirplaneMode == null) { + mAirplaneMode = new AirplaneModeItem(); + } + AirplaneModeSettings ams = mProfile.getAirplaneMode(); + if (ams == null) { + ams = new AirplaneModeSettings(); + mProfile.setAirplaneMode(ams); + } + mAirplaneMode.mSettings = ams; + ProfileAirplaneModePreference amp = new ProfileAirplaneModePreference(getActivity()); + amp.setAirplaneModeItem(mAirplaneMode); + amp.setTitle(R.string.profile_airplanemode_title); + amp.setPersistent(false); + amp.setSummary(getActivity()); + amp.setOnPreferenceChangeListener(this); + mAirplaneMode.mCheckbox = amp; + systemPrefs.addPreference(amp); + + // Lockscreen mode preference + mScreenLockModePreference = new ListPreference(getActivity()); + mScreenLockModePreference.setTitle(R.string.profile_lockmode_title); + mScreenLockModePreference.setEntries(R.array.profile_lockmode_entries); + mScreenLockModePreference.setEntryValues(R.array.profile_lockmode_values); + mScreenLockModePreference.setPersistent(false); + mScreenLockModePreference.setSummary(getResources().getStringArray( + R.array.profile_lockmode_summaries)[mProfile.getScreenLockMode()]); + mScreenLockModePreference.setValue(String.valueOf(mProfile.getScreenLockMode())); + mScreenLockModePreference.setOnPreferenceChangeListener(this); + + // DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE); + // if (dpm.requireSecureKeyguard()) { + // mScreenLockModePreference.setEnabled(false); + // mScreenLockModePreference.setSummary(R.string.unlock_set_unlock_disabled_summary); + // } + + systemPrefs.addPreference(mScreenLockModePreference); + } + + // Populate the audio streams list + final AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + PreferenceGroup streamList = (PreferenceGroup) prefSet.findPreference("profile_volumeoverrides"); + if (streamList != null) { + streamList.removeAll(); + for (StreamItem stream : mStreams) { + StreamSettings settings = mProfile.getSettingsForStream(stream.mStreamId); + if (settings == null) { + settings = new StreamSettings(stream.mStreamId); + mProfile.setStreamSettings(settings); + } + stream.mSettings = settings; + StreamVolumePreference pref = new StreamVolumePreference(getActivity()); + pref.setKey("stream_" + stream.mStreamId); + pref.setTitle(stream.mLabel); + pref.setSummary(getString(R.string.volume_override_summary) + " " + settings.getValue() + + "/" + am.getStreamMaxVolume(stream.mStreamId)); + pref.setPersistent(false); + pref.setStreamItem(stream); + stream.mCheckbox = pref; + streamList.addPreference(pref); + } + } + + // Populate Connections list + PreferenceGroup connectionList = (PreferenceGroup) prefSet.findPreference("profile_connectionoverrides"); + if (connectionList != null) { + connectionList.removeAll(); + for (ConnectionItem connection : mConnections) { + String[] connectionstrings = getResources().getStringArray(connection.mChoices); + ConnectionSettings settings = mProfile.getSettingsForConnection(connection.mConnectionId); + if (settings == null) { + settings = new ConnectionSettings(connection.mConnectionId); + mProfile.setConnectionSettings(settings); + } + connection.mSettings = settings; + ProfileConnectionPreference pref = new ProfileConnectionPreference(getActivity()); + pref.setKey("connection_" + connection.mConnectionId); + pref.setTitle(connection.mLabel); + pref.setSummary(connectionstrings[settings.getValue()]); + pref.setPersistent(false); + pref.setConnectionItem(connection); + connection.mCheckbox = pref; + connectionList.addPreference(pref); + } + } + + // Populate Application groups + PreferenceGroup groupList = (PreferenceGroup) prefSet.findPreference("profile_appgroups"); + if (groupList != null) { + groupList.removeAll(); + for (ProfileGroup profileGroup : mProfile.getProfileGroups()) { + PreferenceScreen pref = new PreferenceScreen(getActivity(), null); + UUID uuid = profileGroup.getUuid(); + pref.setKey(uuid.toString()); + pref.setTitle(mProfileManager.getNotificationGroup(uuid).getName()); + //pref.setSummary(R.string.profile_summary); // summary is repetitive, consider removing + pref.setPersistent(false); + pref.setSelectable(true); + groupList.addPreference(pref); + } + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference instanceof StreamVolumePreference) { + for (StreamItem stream : mStreams) { + if (preference == stream.mCheckbox) { + stream.mSettings.setOverride((Boolean) newValue); + } + } + } else if (preference instanceof ProfileConnectionPreference) { + for (ConnectionItem connection : mConnections) { + if (preference == connection.mCheckbox) { + connection.mSettings.setOverride((Boolean) newValue); + } + } + } else if (preference == mRingMode.mCheckbox) { + mRingMode.mSettings.setOverride((Boolean) newValue); + } else if (preference == mAirplaneMode.mCheckbox) { + mAirplaneMode.mSettings.setOverride((Boolean) newValue); + } else if (preference == mNamePreference) { + String name = mNamePreference.getName().toString(); + if (!name.equals(mProfile.getName())) { + if (!mProfileManager.profileExists(name)) { + mProfile.setName(name); + } else { + mNamePreference.setName(mProfile.getName()); + Toast.makeText(getActivity(), R.string.duplicate_profile_name, Toast.LENGTH_LONG).show(); + } + } + } else if (preference == mScreenLockModePreference) { + mProfile.setScreenLockMode(Integer.valueOf((String) newValue)); + mScreenLockModePreference.setSummary(getResources().getStringArray( + R.array.profile_lockmode_summaries)[mProfile.getScreenLockMode()]); + } + return true; + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + Log.d(TAG, "onPreferenceTreeClick(): entered" + preferenceScreen.getKey() + preference.getKey()); + if (preference instanceof PreferenceScreen) { + startProfileGroupActivity(preference.getKey(), preference.getTitle().toString()); + return true; + } + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + + private void startProfileGroupActivity(String key, String title) { + Bundle args = new Bundle(); + args.putString("ProfileGroup", key.toString()); + args.putParcelable("Profile", mProfile); + + String header = mProfile.getName().toString() + ": " + title.toString(); + SettingsActivity pa = (SettingsActivity) getActivity(); + pa.startPreferencePanel(ProfileGroupConfig.class.getName(), args, + 0, header, this, PROFILE_GROUP_DETAILS); + } + + + private void deleteProfile() { + if (mProfile.getUuid().equals(mProfileManager.getActiveProfile().getUuid())) { + Toast toast = Toast.makeText(getActivity(), getString(R.string.profile_cannot_delete), + Toast.LENGTH_SHORT); + toast.show(); + } else { + AlertDialog.Builder alert = new AlertDialog.Builder(getActivity()); + alert.setTitle(R.string.profile_menu_delete); + alert.setIconAttribute(android.R.attr.alertDialogIcon); + alert.setMessage(R.string.profile_delete_confirm); + alert.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + doDelete(); + } + }); + alert.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + alert.create().show(); + } + } + + private void doDelete() { + mProfileManager.removeProfile(mProfile); + mProfile = null; + finish(); + } + + static class StreamItem { + int mStreamId; + String mLabel; + StreamSettings mSettings; + StreamVolumePreference mCheckbox; + + public StreamItem(int streamId, String label) { + mStreamId = streamId; + mLabel = label; + } + } + + static class ConnectionItem { + int mConnectionId; + String mLabel; + ConnectionSettings mSettings; + ProfileConnectionPreference mCheckbox; + int mChoices; + + public ConnectionItem(int connectionId, String label) { + mConnectionId = connectionId; + mChoices = R.array.profile_connection_entries; + mLabel = label; + } + + public ConnectionItem(int connectionId, String label, int choices) { + mConnectionId = connectionId; + mLabel = label; + mChoices = choices; + } + } + + static class RingModeItem { + RingModeSettings mSettings; + ProfileRingModePreference mCheckbox; + + public RingModeItem() { + // nothing to do + } + } + + static class AirplaneModeItem { + AirplaneModeSettings mSettings; + ProfileAirplaneModePreference mCheckbox; + + public AirplaneModeItem() { + // nothing to do + } + } +} diff --git a/src/com/android/settings/profiles/ProfileConnectionPreference.java b/src/com/android/settings/profiles/ProfileConnectionPreference.java new file mode 100644 index 0000000..a5fc986 --- /dev/null +++ b/src/com/android/settings/profiles/ProfileConnectionPreference.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2012 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 com.android.settings.profiles; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.preference.Preference; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.LinearLayout; + +import com.android.settings.R; + +public class ProfileConnectionPreference extends Preference implements + CompoundButton.OnCheckedChangeListener, View.OnClickListener { + + private boolean mProtectFromCheckedChange = false; + + private CheckBox mCheckBox; + + final static String TAG = "ProfileConnectionPreference"; + + private ProfileConfig.ConnectionItem mConnectionItem; + + final static int defaultChoice = -1; + + private int currentChoice; + + /** + * @param context + * @param attrs + * @param defStyle + */ + public ProfileConnectionPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + /** + * @param context + * @param attrs + */ + public ProfileConnectionPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + /** + * @param context + */ + public ProfileConnectionPreference(Context context) { + super(context); + init(); + } + + @Override + public View getView(View convertView, ViewGroup parent) { + View view = super.getView(convertView, parent); + + View widget = view.findViewById(R.id.profile_checkbox); + if ((widget != null) && widget instanceof CheckBox) { + mCheckBox = (CheckBox) widget; + mCheckBox.setOnCheckedChangeListener(this); + + mProtectFromCheckedChange = true; + mCheckBox.setChecked(isChecked()); + mProtectFromCheckedChange = false; + } + + View textLayout = view.findViewById(R.id.text_layout); + if ((textLayout != null) && textLayout instanceof LinearLayout) { + textLayout.setOnClickListener(this); + } + + return view; + } + + private void init() { + setLayoutResource(R.layout.preference_streamvolume); + } + + public boolean isChecked() { + return mConnectionItem != null && mConnectionItem.mSettings.isOverride(); + } + + public void setConnectionItem(ProfileConfig.ConnectionItem connectionItem) { + mConnectionItem = connectionItem; + + if (mCheckBox != null) { + mCheckBox.setChecked(mConnectionItem.mSettings.isOverride()); + } + } + + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (mProtectFromCheckedChange) { + return; + } + + mConnectionItem.mSettings.setOverride(isChecked); + + callChangeListener(isChecked); + } + + protected Dialog createConnectionDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + final String[] ConnectionValues = getContext().getResources().getStringArray(R.array.profile_connection_values); + final String[] connectionNames = getContext().getResources().getStringArray(mConnectionItem.mChoices); + + currentChoice = mConnectionItem.mSettings.getValue(); + + builder.setTitle(mConnectionItem.mLabel); + builder.setSingleChoiceItems(mConnectionItem.mChoices, currentChoice, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int item) { + currentChoice = item; + } + }); + + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int item) { + if (currentChoice != defaultChoice) { + int value = Integer.parseInt(ConnectionValues[currentChoice]); + mConnectionItem.mSettings.setValue(value); + setSummary(connectionNames[value]); + } + } + }); + + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int item) { + } + }); + return builder.create(); + } + + public ProfileConfig.ConnectionItem getConnectionItem() { + return mConnectionItem; + } + + @Override + public void onClick(android.view.View v) { + if ((v != null) && (R.id.text_layout == v.getId())) { + createConnectionDialog().show(); + } + } +} diff --git a/src/com/android/settings/profiles/ProfileEnabler.java b/src/com/android/settings/profiles/ProfileEnabler.java new file mode 100644 index 0000000..0540dbe --- /dev/null +++ b/src/com/android/settings/profiles/ProfileEnabler.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2012 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 com.android.settings.profiles; + +import android.app.ProfileManager; +import android.content.Context; +import android.content.Intent; +import android.provider.Settings; +import android.widget.CompoundButton; +import android.widget.Switch; + +public class ProfileEnabler implements CompoundButton.OnCheckedChangeListener { + private final Context mContext; + private Switch mSwitch; + private boolean mStateMachineEvent; + + public ProfileEnabler(Context context, Switch switch_) { + mContext = context; + mSwitch = switch_; + } + + public void resume() { + mSwitch.setOnCheckedChangeListener(this); + setSwitchState(); + } + + public void pause() { + mSwitch.setOnCheckedChangeListener(null); + } + + public void setSwitch(Switch switch_) { + if (mSwitch == switch_) return; + mSwitch.setOnCheckedChangeListener(null); + mSwitch = switch_; + mSwitch.setOnCheckedChangeListener(this); + setSwitchState(); + } + + private void setSwitchState() { + boolean enabled = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.SYSTEM_PROFILES_ENABLED, 1) == 1; + mStateMachineEvent = true; + mSwitch.setChecked(enabled); + mStateMachineEvent = false; + } + + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (mStateMachineEvent) { + return; + } + // Handle a switch change + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.SYSTEM_PROFILES_ENABLED, isChecked ? 1 : 0); + + // Send a broadcast intent to the world + // TODO Enabling or disabling profiles should be at ProfileManager, not here + Intent intent=new Intent(ProfileManager.PROFILES_STATE_CHANGED_ACTION); + intent.putExtra( + ProfileManager.EXTRA_PROFILES_STATE, + isChecked ? + ProfileManager.PROFILES_STATE_ENABLED : + ProfileManager.PROFILES_STATE_DISABLED); + mContext.sendBroadcast(intent); + + } + +} diff --git a/src/com/android/settings/profiles/ProfileGroupConfig.java b/src/com/android/settings/profiles/ProfileGroupConfig.java new file mode 100644 index 0000000..f1d4e7b --- /dev/null +++ b/src/com/android/settings/profiles/ProfileGroupConfig.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2012 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 com.android.settings.profiles; + +import java.util.UUID; + +import android.app.Profile; +import android.app.ProfileGroup; +import android.app.ProfileGroup.Mode; +import android.app.ProfileManager; +import android.net.Uri; +import android.os.Bundle; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; + +public class ProfileGroupConfig extends SettingsPreferenceFragment implements + OnPreferenceChangeListener { + + public static final String PROFILE_SERVICE = "profile"; + + private static final CharSequence KEY_SOUNDMODE = "sound_mode"; + + private static final CharSequence KEY_VIBRATEMODE = "vibrate_mode"; + + private static final CharSequence KEY_LIGHTSMODE = "lights_mode"; + + private static final CharSequence KEY_RINGERMODE = "ringer_mode"; + + private static final CharSequence KEY_SOUNDTONE = "soundtone"; + + private static final CharSequence KEY_RINGTONE = "ringtone"; + + Profile mProfile; + + ProfileGroup mProfileGroup; + + private ListPreference mSoundMode; + + private ListPreference mRingerMode; + + private ListPreference mVibrateMode; + + private ListPreference mLightsMode; + + private ProfileRingtonePreference mRingTone; + + private ProfileRingtonePreference mSoundTone; + + private ProfileManager mProfileManager; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.profile_settings); + + final Bundle args = getArguments(); + if (args != null) { + mProfile = (Profile) args.getParcelable("Profile"); + UUID uuid = UUID.fromString(args.getString("ProfileGroup")); + + mProfileManager = (ProfileManager) getSystemService(PROFILE_SERVICE); + mProfileGroup = mProfile.getProfileGroup(uuid); + + mRingerMode = (ListPreference) findPreference(KEY_RINGERMODE); + mSoundMode = (ListPreference) findPreference(KEY_SOUNDMODE); + mVibrateMode = (ListPreference) findPreference(KEY_VIBRATEMODE); + mLightsMode = (ListPreference) findPreference(KEY_LIGHTSMODE); + mRingTone = (ProfileRingtonePreference) findPreference(KEY_RINGTONE); + mSoundTone = (ProfileRingtonePreference) findPreference(KEY_SOUNDTONE); + + mRingTone.setShowSilent(false); + mSoundTone.setShowSilent(false); + + mSoundMode.setOnPreferenceChangeListener(this); + mRingerMode.setOnPreferenceChangeListener(this); + mVibrateMode.setOnPreferenceChangeListener(this); + mLightsMode.setOnPreferenceChangeListener(this); + mSoundTone.setOnPreferenceChangeListener(this); + mRingTone.setOnPreferenceChangeListener(this); + + updateState(); + } + } + + private void updateState() { + + mVibrateMode.setValue(mProfileGroup.getVibrateMode().name()); + mSoundMode.setValue(mProfileGroup.getSoundMode().name()); + mRingerMode.setValue(mProfileGroup.getRingerMode().name()); + mLightsMode.setValue(mProfileGroup.getLightsMode().name()); + + mVibrateMode.setSummary(mVibrateMode.getEntry()); + mSoundMode.setSummary(mSoundMode.getEntry()); + mRingerMode.setSummary(mRingerMode.getEntry()); + mLightsMode.setSummary(mLightsMode.getEntry()); + + if (mProfileGroup.getSoundOverride() != null) { + mSoundTone.setRingtone(mProfileGroup.getSoundOverride()); + } + + if (mProfileGroup.getRingerOverride() != null) { + mRingTone.setRingtone(mProfileGroup.getRingerOverride()); + } + + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference == mVibrateMode) { + mProfileGroup.setVibrateMode(Mode.valueOf((String) newValue)); + } else if (preference == mSoundMode) { + mProfileGroup.setSoundMode(Mode.valueOf((String) newValue)); + } else if (preference == mRingerMode) { + mProfileGroup.setRingerMode(Mode.valueOf((String) newValue)); + } else if (preference == mLightsMode) { + mProfileGroup.setLightsMode(Mode.valueOf((String) newValue)); + } else if (preference == mRingTone) { + Uri uri = Uri.parse((String) newValue); + mProfileGroup.setRingerOverride(uri); + } else if (preference == mSoundTone) { + Uri uri = Uri.parse((String) newValue); + mProfileGroup.setSoundOverride(uri); + } + + mProfileManager.updateProfile(mProfile); + + updateState(); + return true; + } +} diff --git a/src/com/android/settings/profiles/ProfileRingModePreference.java b/src/com/android/settings/profiles/ProfileRingModePreference.java new file mode 100644 index 0000000..d471998 --- /dev/null +++ b/src/com/android/settings/profiles/ProfileRingModePreference.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2012 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 com.android.settings.profiles; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.preference.Preference; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.LinearLayout; + +import com.android.settings.R; + +public class ProfileRingModePreference extends Preference implements + CompoundButton.OnCheckedChangeListener, View.OnClickListener { + + private boolean mProtectFromCheckedChange = false; + + private CheckBox mCheckBox; + + final static String TAG = "ProfileRingModePreference"; + + private ProfileConfig.RingModeItem mRingModeItem; + + final static int defaultChoice = -1; + + private int currentChoice; + + /** + * @param context + * @param attrs + * @param defStyle + */ + public ProfileRingModePreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + /** + * @param context + * @param attrs + */ + public ProfileRingModePreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + /** + * @param context + */ + public ProfileRingModePreference(Context context) { + super(context); + init(); + } + + @Override + public View getView(View convertView, ViewGroup parent) { + View view = super.getView(convertView, parent); + + View widget = view.findViewById(R.id.profile_checkbox); + if ((widget != null) && widget instanceof CheckBox) { + mCheckBox = (CheckBox) widget; + mCheckBox.setOnCheckedChangeListener(this); + + mProtectFromCheckedChange = true; + mCheckBox.setChecked(isChecked()); + mProtectFromCheckedChange = false; + } + + View textLayout = view.findViewById(R.id.text_layout); + if ((textLayout != null) && textLayout instanceof LinearLayout) { + textLayout.setOnClickListener(this); + } + + return view; + } + + private void init() { + setLayoutResource(R.layout.preference_streamvolume); + } + + public boolean isChecked() { + return mRingModeItem != null && mRingModeItem.mSettings.isOverride(); + } + + public void setRingModeItem(ProfileConfig.RingModeItem ringModeItem) { + mRingModeItem = ringModeItem; + + if (mCheckBox != null) { + mCheckBox.setChecked(mRingModeItem.mSettings.isOverride()); + } + } + + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (mProtectFromCheckedChange) { + return; + } + + mRingModeItem.mSettings.setOverride(isChecked); + + callChangeListener(isChecked); + } + + protected Dialog createRingModeDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + final String[] ringModeValues = getContext().getResources().getStringArray(R.array.ring_mode_values); + String currentValue = mRingModeItem.mSettings.getValue(); + if (currentValue != null) { + for (int i = 0; i < ringModeValues.length; i++) { + if (currentValue.equals(ringModeValues[i])) { + currentChoice = i; + break; + } + } + } + + builder.setTitle(R.string.ring_mode_title); + builder.setSingleChoiceItems(R.array.ring_mode_entries, currentChoice, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int item) { + currentChoice = item; + } + }); + + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int item) { + if (currentChoice != defaultChoice) { + String value = ringModeValues[currentChoice]; + mRingModeItem.mSettings.setValue(value); + setSummary(getContext().getResources().getStringArray(R.array.ring_mode_entries)[currentChoice]); + } + } + }); + + builder.setNegativeButton(android.R.string.cancel, null); + return builder.create(); + } + + public ProfileConfig.RingModeItem getRingModeItem() { + return mRingModeItem; + } + + @Override + public void onClick(android.view.View v) { + if ((v != null) && (R.id.text_layout == v.getId())) { + createRingModeDialog().show(); + } + } + + public void setSummary(Context context) { + String[] entries = context.getResources().getStringArray(R.array.ring_mode_entries); + String[] values = context.getResources().getStringArray(R.array.ring_mode_values); + int l = entries.length; + String value = mRingModeItem.mSettings.getValue(); + for (int i = 0; i < l; i++) { + if (value.equals(values[i])) { + setSummary(entries[i]); + break; + } + } + } +} diff --git a/src/com/android/settings/profiles/ProfileRingtonePreference.java b/src/com/android/settings/profiles/ProfileRingtonePreference.java new file mode 100644 index 0000000..91ccbe6 --- /dev/null +++ b/src/com/android/settings/profiles/ProfileRingtonePreference.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2012 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 com.android.settings.profiles; + +import android.content.Context; +import android.content.Intent; +import android.media.RingtoneManager; +import android.net.Uri; +import android.preference.RingtonePreference; +import android.util.AttributeSet; + +public class ProfileRingtonePreference extends RingtonePreference { + private static final String TAG = "ProfileRingtonePreference"; + + public ProfileRingtonePreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onPrepareRingtonePickerIntent(Intent ringtonePickerIntent) { + super.onPrepareRingtonePickerIntent(ringtonePickerIntent); + + /* + * Since this preference is for choosing the default ringtone, it + * doesn't make sense to show a 'Default' item. + */ + ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false); + } + + private Uri mRingtone; + + void setRingtone(Uri uri) { + mRingtone = uri; + } + + @Override + protected Uri onRestoreRingtone() { + if (mRingtone == null) { + return super.onRestoreRingtone(); + } else { + return mRingtone; + } + } +} diff --git a/src/com/android/settings/profiles/ProfilesList.java b/src/com/android/settings/profiles/ProfilesList.java new file mode 100644 index 0000000..13fa16b --- /dev/null +++ b/src/com/android/settings/profiles/ProfilesList.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2012 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 com.android.settings.profiles; + +import java.util.UUID; + +import android.app.Profile; +import android.app.ProfileManager; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceScreen; +import android.provider.Settings; +import android.text.TextUtils; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.Utils; + +public class ProfilesList extends SettingsPreferenceFragment implements + Preference.OnPreferenceChangeListener { + static final String TAG = "ProfilesSettings"; + public static final String PROFILE_SERVICE = "profile"; + + private ProfileManager mProfileManager; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.profiles_settings); + mProfileManager = (ProfileManager) getActivity().getSystemService(PROFILE_SERVICE); + } + + @Override + public void onResume() { + super.onResume(); + refreshList(); + + // On tablet devices remove the padding + if (Utils.isTablet(getActivity())) { + getListView().setPadding(0, 0, 0, 0); + } + } + + public void refreshList() { + PreferenceScreen plist = getPreferenceScreen(); + plist.removeAll(); + + // Get active profile, if null + Profile prof = mProfileManager.getActiveProfile(); + String selectedKey = prof != null ? prof.getUuid().toString() : null; + + for (Profile profile : mProfileManager.getProfiles()) { + Bundle args = new Bundle(); + args.putParcelable("Profile", profile); + + ProfilesPreference ppref = new ProfilesPreference(this, args); + ppref.setKey(profile.getUuid().toString()); + ppref.setTitle(profile.getName()); + ppref.setPersistent(false); + ppref.setOnPreferenceChangeListener(this); + ppref.setSelectable(true); + ppref.setEnabled(true); + + if (TextUtils.equals(selectedKey, ppref.getKey())) { + ppref.setChecked(true); + } + + plist.addPreference(ppref); + } + } + + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (newValue instanceof String) { + setSelectedProfile((String) newValue); + refreshList(); + } + return true; + } + + private void setSelectedProfile(String key) { + try { + UUID selectedUuid = UUID.fromString(key); + mProfileManager.setActiveProfile(selectedUuid); + } catch (IllegalArgumentException ex) { + ex.printStackTrace(); + } + } +} diff --git a/src/com/android/settings/profiles/ProfilesPreference.java b/src/com/android/settings/profiles/ProfilesPreference.java new file mode 100644 index 0000000..91f57ad --- /dev/null +++ b/src/com/android/settings/profiles/ProfilesPreference.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2012 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 com.android.settings.profiles; + +import android.content.ActivityNotFoundException; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.PreferenceActivity; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.SettingsPreferenceFragment; + +public class ProfilesPreference extends CheckBoxPreference { + private static final String TAG = ProfilesPreference.class.getSimpleName(); + private static final float DISABLED_ALPHA = 0.4f; + private final SettingsPreferenceFragment mFragment; + private final Bundle mSettingsBundle; + + // constant value that can be used to check return code from sub activity. + private static final int PROFILE_DETAILS = 1; + + private ImageView mProfilesSettingsButton; + private TextView mTitleText; + private TextView mSummaryText; + private View mProfilesPref; + + private final OnClickListener mPrefOnclickListener = new OnClickListener() { + @Override + public void onClick(View arg0) { + if (!isEnabled() || isChecked()) { + return; + } + setChecked(true); + callChangeListener(getKey()); + } + }; + + public ProfilesPreference(SettingsPreferenceFragment fragment, Bundle settingsBundle) { + super(fragment.getActivity(), null, R.style.ProfilesPreferenceStyle); + setLayoutResource(R.layout.preference_profiles); + setWidgetLayoutResource(R.layout.preference_profiles_widget); + mFragment = fragment; + mSettingsBundle = settingsBundle; + } + + @Override + protected void onBindView(View view) { + super.onBindView(view); + mProfilesPref = view.findViewById(R.id.profiles_pref); + mProfilesPref.setOnClickListener(mPrefOnclickListener); + mProfilesSettingsButton = (ImageView)view.findViewById(R.id.profiles_settings); + mTitleText = (TextView)view.findViewById(android.R.id.title); + mSummaryText = (TextView)view.findViewById(android.R.id.summary); + + if (mSettingsBundle != null) { + mProfilesSettingsButton.setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View arg0) { + try { + startProfileConfigActivity(); + } catch (ActivityNotFoundException e) { + // If the settings activity does not exist, we can just + // do nothing... + } + } + }); + } + if (mSettingsBundle == null) { + mProfilesSettingsButton.setVisibility(View.GONE); + } else { + updatePreferenceViews(); + } + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + if (enabled) { + updatePreferenceViews(); + } else { + disablePreferenceViews(); + } + } + + private void disablePreferenceViews() { + if (mProfilesSettingsButton != null) { + mProfilesSettingsButton.setEnabled(false); + mProfilesSettingsButton.setAlpha(DISABLED_ALPHA); + } + if (mProfilesPref != null) { + mProfilesPref.setEnabled(false); + mProfilesPref.setBackgroundColor(0); + } + } + + private void updatePreferenceViews() { + final boolean checked = isChecked(); + if (mProfilesSettingsButton != null) { + mProfilesSettingsButton.setEnabled(true); + mProfilesSettingsButton.setClickable(true); + mProfilesSettingsButton.setFocusable(true); + } + if (mTitleText != null) { + mTitleText.setEnabled(true); + } + if (mSummaryText != null) { + mSummaryText.setEnabled(checked); + } + if (mProfilesPref != null) { + mProfilesPref.setEnabled(true); + mProfilesPref.setLongClickable(checked); + final boolean enabled = isEnabled(); + mProfilesPref.setOnClickListener(enabled ? mPrefOnclickListener : null); + if (!enabled) { + mProfilesPref.setBackgroundColor(0); + } + } + } + + // utility method used to start sub activity + private void startProfileConfigActivity() { + SettingsActivity pa = (SettingsActivity) mFragment.getActivity(); + pa.startPreferencePanel(ProfileConfig.class.getName(), mSettingsBundle, + R.string.profile_profile_manage, null, mFragment, PROFILE_DETAILS); + } + + @Override + public void setChecked(boolean checked) { + super.setChecked(checked); + } +} diff --git a/src/com/android/settings/profiles/ProfilesSettings.java b/src/com/android/settings/profiles/ProfilesSettings.java new file mode 100644 index 0000000..2576858 --- /dev/null +++ b/src/com/android/settings/profiles/ProfilesSettings.java @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2012 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 com.android.settings.profiles; + +import android.app.ActionBar; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Fragment; +import android.app.FragmentManager; +import android.app.NotificationGroup; +import android.app.Profile; +import android.app.ProfileManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.provider.Settings; +import android.support.v4.view.PagerTabStrip; +import android.support.v4.view.ViewPager; +import android.support.v13.app.FragmentStatePagerAdapter; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.Switch; +import android.widget.TextView; +import android.widget.Toast; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.Utils; + +import java.util.HashMap; + +public class ProfilesSettings extends SettingsPreferenceFragment { + + private static final String TAG = "ProfilesSettings"; + private static final String PROFILE_SERVICE = "profile"; + + private static final int MENU_RESET = Menu.FIRST; + private static final int MENU_ADD = Menu.FIRST + 1; + + private final IntentFilter mFilter; + private final BroadcastReceiver mReceiver; + + private ProfileManager mProfileManager; + private ProfileEnabler mProfileEnabler; + + private Switch mActionBarSwitch; + + private ViewPager mViewPager; + private TextView mEmptyText; + private ProfilesPagerAdapter mAdapter; + private boolean mEnabled; + + ViewGroup mContainer; + + static Bundle mSavedState; + + public ProfilesSettings() { + mFilter = new IntentFilter(); + mFilter.addAction(ProfileManager.PROFILES_STATE_CHANGED_ACTION); + + mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (ProfileManager.PROFILES_STATE_CHANGED_ACTION.equals(action)) { + updateProfilesEnabledState(); + } + } + }; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + mContainer = container; + + View view = inflater.inflate(R.layout.profile_tabs, container, false); + mViewPager = (ViewPager) view.findViewById(R.id.pager); + mEmptyText = (TextView) view.findViewById(R.id.empty); + + mAdapter = new ProfilesPagerAdapter(getFragmentManager()); + mViewPager.setAdapter(mAdapter); + + PagerTabStrip tabs = (PagerTabStrip) view.findViewById(R.id.tabs); + tabs.setTabIndicatorColorResource(android.R.color.holo_blue_light); + + mProfileManager = (ProfileManager) getActivity().getSystemService(PROFILE_SERVICE); + + setHasOptionsMenu(true); + + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + // We don't call super.onActivityCreated() here, since it assumes we already set up + // Preference (probably in onCreate()), while ProfilesSettings exceptionally set it up in + // this method. + // On/off switch + Activity activity = getActivity(); + //Switch + mActionBarSwitch = new Switch(activity); + + if (activity instanceof PreferenceActivity) { + PreferenceActivity preferenceActivity = (PreferenceActivity) activity; + if (preferenceActivity.onIsHidingHeaders() || !preferenceActivity.onIsMultiPane()) { + final int padding = activity.getResources().getDimensionPixelSize( + R.dimen.action_bar_switch_padding); + mActionBarSwitch.setPaddingRelative(0, 0, padding, 0); + activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, + ActionBar.DISPLAY_SHOW_CUSTOM); + activity.getActionBar().setCustomView(mActionBarSwitch, new ActionBar.LayoutParams( + ActionBar.LayoutParams.WRAP_CONTENT, + ActionBar.LayoutParams.WRAP_CONTENT, + Gravity.CENTER_VERTICAL | Gravity.END)); + } + } + + mProfileEnabler = new ProfileEnabler(activity, mActionBarSwitch); + + // After confirming PreferenceScreen is available, we call super. + super.onActivityCreated(savedInstanceState); + } + + @Override + public void onResume() { + super.onResume(); + if (mProfileEnabler != null) { + mProfileEnabler.resume(); + } + getActivity().registerReceiver(mReceiver, mFilter); + + // check if we are enabled + updateProfilesEnabledState(); + + // If running on a phone, remove padding around tabs + if (!Utils.isTablet(getActivity())) { + mContainer.setPadding(0, 0, 0, 0); + } + } + + @Override + public void onPause() { + super.onPause(); + if (mProfileEnabler != null) { + mProfileEnabler.pause(); + } + getActivity().unregisterReceiver(mReceiver); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + menu.add(0, MENU_RESET, 0, R.string.profile_reset_title) + .setIcon(R.drawable.ic_settings_backup) // use the backup icon + .setAlphabeticShortcut('r') + .setEnabled(mEnabled) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | + MenuItem.SHOW_AS_ACTION_WITH_TEXT); + + menu.add(0, MENU_ADD, 0, R.string.profiles_add) + .setIcon(R.drawable.ic_menu_add) + .setAlphabeticShortcut('a') + .setEnabled(mEnabled) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | + MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_RESET: + resetAll(); + return true; + + case MENU_ADD: + // determine dialog to launch + if (mViewPager.getCurrentItem() == 0) { + addProfile(); + } else { + addAppGroup(); + } + return true; + + default: + return false; + } + } + + private void addProfile() { + LayoutInflater inflater = getActivity().getLayoutInflater(); + View content = inflater.inflate(R.layout.profile_name_dialog, null); + final TextView prompt = (TextView) content.findViewById(R.id.prompt); + final EditText entry = (EditText) content.findViewById(R.id.name); + + prompt.setText(R.string.profile_profile_name_prompt); + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(R.string.menu_new_profile); + builder.setView(content); + + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String name = entry.getText().toString(); + if (!mProfileManager.profileExists(name)) { + Profile profile = new Profile(name); + mProfileManager.addProfile(profile); + mAdapter.refreshProfiles(); + } else { + Toast.makeText(getActivity(), + R.string.duplicate_profile_name, Toast.LENGTH_LONG).show(); + } + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + + AlertDialog dialog = builder.create(); + dialog.show(); + } + + private void resetAll() { + AlertDialog.Builder alert = new AlertDialog.Builder(getActivity()); + alert.setTitle(R.string.profile_reset_title); + alert.setIconAttribute(android.R.attr.alertDialogIcon); + alert.setMessage(R.string.profile_reset_message); + alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + mProfileManager.resetAll(); + mAdapter.refreshProfiles(); + mAdapter.refreshAppGroups(); + } + }); + alert.setNegativeButton(R.string.cancel, null); + alert.show(); + } + + private void addAppGroup() { + LayoutInflater inflater = getActivity().getLayoutInflater(); + View content = inflater.inflate(R.layout.profile_name_dialog, null); + final TextView prompt = (TextView) content.findViewById(R.id.prompt); + final EditText entry = (EditText) content.findViewById(R.id.name); + + prompt.setText(R.string.profile_appgroup_name_prompt); + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(R.string.profile_new_appgroup); + builder.setView(content); + + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String name = entry.getText().toString(); + if (!mProfileManager.notificationGroupExists(name)) { + NotificationGroup newGroup = new NotificationGroup(name); + mProfileManager.addNotificationGroup(newGroup); + mAdapter.refreshAppGroups(); + } else { + Toast.makeText(getActivity(), + R.string.duplicate_appgroup_name, Toast.LENGTH_LONG).show(); + } + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + + AlertDialog dialog = builder.create(); + dialog.show(); + } + + private void updateProfilesEnabledState() { + Activity activity = getActivity(); + + mEnabled = Settings.System.getInt(activity.getContentResolver(), + Settings.System.SYSTEM_PROFILES_ENABLED, 1) == 1; + activity.invalidateOptionsMenu(); + + mViewPager.setVisibility(mEnabled ? View.VISIBLE : View.GONE); + mEmptyText.setVisibility(mEnabled ? View.GONE : View.VISIBLE); + } + + class ProfilesPagerAdapter extends FragmentStatePagerAdapter { + Fragment[] frags = { new ProfilesList(), new AppGroupList() }; + String[] titles = { getString(R.string.profile_profiles_manage), + getString(R.string.profile_appgroups_manage) }; + + ProfilesPagerAdapter(FragmentManager fm) { + super(fm); + } + + @Override + public Fragment getItem(int position) { + return frags[position]; + } + + @Override + public int getCount() { + return frags.length; + } + + @Override + public CharSequence getPageTitle(int position) { + return titles[position]; + } + + public void refreshProfiles() { + ((ProfilesList) frags[0]).refreshList(); + } + + public void refreshAppGroups() { + ((AppGroupList) frags[1]).refreshList(); + } + } +} diff --git a/src/com/android/settings/profiles/ProfilesUtils.java b/src/com/android/settings/profiles/ProfilesUtils.java new file mode 100644 index 0000000..4f59dd9 --- /dev/null +++ b/src/com/android/settings/profiles/ProfilesUtils.java @@ -0,0 +1,68 @@ +package com.android.settings.profiles; + +import android.bluetooth.BluetoothAdapter; +import android.content.ContentResolver; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.hardware.Camera; +import android.hardware.display.DisplayManager; +import android.hardware.display.WifiDisplayStatus; +import android.hardware.Sensor; +import android.hardware.SensorManager; +import android.net.ConnectivityManager; +import android.nfc.NfcAdapter; +import android.os.BatteryManager; +import android.os.UserHandle; +import android.provider.Settings; +import android.telephony.TelephonyManager; +import android.text.TextUtils; + +import com.android.internal.telephony.PhoneConstants; + +public class ProfilesUtils { + public static boolean deviceSupportsUsbTether(Context ctx) { + ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE); + return (cm.getTetherableUsbRegexs().length != 0); + } + + public static boolean deviceSupportsWifiDisplay(Context ctx) { + DisplayManager dm = (DisplayManager) ctx.getSystemService(Context.DISPLAY_SERVICE); + return (dm.getWifiDisplayStatus().getFeatureState() != WifiDisplayStatus.FEATURE_STATE_UNAVAILABLE); + } + + public static boolean deviceSupportsMobileData(Context ctx) { + ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE); + return cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE); + } + + public static boolean deviceSupportsBluetooth() { + return (BluetoothAdapter.getDefaultAdapter() != null); + } + + public static boolean systemProfilesEnabled(ContentResolver resolver) { + return (Settings.System.getInt(resolver, Settings.System.SYSTEM_PROFILES_ENABLED, 1) == 1); + } + + public static boolean deviceSupportsNfc(Context ctx) { + return NfcAdapter.getDefaultAdapter(ctx) != null; + } + + public static boolean deviceSupportsCamera() { + return Camera.getNumberOfCameras() > 0; + } + + public static boolean deviceSupportsGps(Context context) { + return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS); + } + + public static boolean adbEnabled(ContentResolver resolver) { + return (Settings.Global.getInt(resolver, Settings.Global.ADB_ENABLED, 0)) == 1; + } + + public static boolean deviceSupportsCompass(Context context) { + SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + return (sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null + && sm.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) != null); + } +} diff --git a/src/com/android/settings/profiles/StreamVolumePreference.java b/src/com/android/settings/profiles/StreamVolumePreference.java new file mode 100644 index 0000000..6788aca --- /dev/null +++ b/src/com/android/settings/profiles/StreamVolumePreference.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2012 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 com.android.settings.profiles; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.media.AudioManager; +import android.preference.Preference; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.LinearLayout; +import android.widget.SeekBar; + +import com.android.settings.R; + +public class StreamVolumePreference extends Preference implements + CompoundButton.OnCheckedChangeListener, View.OnClickListener { + + private boolean mProtectFromCheckedChange = false; + + private CheckBox mCheckBox; + + final static String TAG = "StreamVolumePreference"; + + private ProfileConfig.StreamItem mStreamItem; + + private SeekBar mBar; + + /** + * @param context + * @param attrs + * @param defStyle + */ + public StreamVolumePreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + /** + * @param context + * @param attrs + */ + public StreamVolumePreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + /** + * @param context + */ + public StreamVolumePreference(Context context) { + super(context); + init(); + } + + @Override + public View getView(View convertView, ViewGroup parent) { + View view = super.getView(convertView, parent); + + View widget = view.findViewById(R.id.profile_checkbox); + if ((widget != null) && widget instanceof CheckBox) { + mCheckBox = (CheckBox) widget; + mCheckBox.setOnCheckedChangeListener(this); + + mProtectFromCheckedChange = true; + mCheckBox.setChecked(isChecked()); + mProtectFromCheckedChange = false; + } + + View textLayout = view.findViewById(R.id.text_layout); + if ((textLayout != null) && textLayout instanceof LinearLayout) { + textLayout.setOnClickListener(this); + } + + return view; + } + + private void init() { + setLayoutResource(R.layout.preference_streamvolume); + } + + public boolean isChecked() { + return mStreamItem != null && mStreamItem.mSettings.isOverride(); + } + + public void setStreamItem(ProfileConfig.StreamItem streamItem) { + mStreamItem = streamItem; + + if (mCheckBox != null) { + mCheckBox.setChecked(mStreamItem.mSettings.isOverride()); + } + } + + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (mProtectFromCheckedChange) { + return; + } + + mStreamItem.mSettings.setOverride(isChecked); + + callChangeListener(isChecked); + } + + protected Dialog createVolumeDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + final AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); + builder.setTitle(mStreamItem.mLabel); + mBar = new SeekBar(getContext()); + mBar.setPaddingRelative(32, 16, 32, 16); // TODO: confirm appropriate padding + mBar.setMax(am.getStreamMaxVolume(mStreamItem.mStreamId)); + mBar.setProgress(mStreamItem.mSettings.getValue()); + builder.setView(mBar); + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + int value = mBar.getProgress(); + mStreamItem.mSettings.setValue(value); + setSummary(getContext().getString(R.string.volume_override_summary) + " " + value + "/" + mBar.getMax()); + } + }); + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + } + }); + return builder.create(); + } + + public ProfileConfig.StreamItem getStreamItem() { + return mStreamItem; + } + + @Override + public void onClick(android.view.View v) { + if ((v != null) && (R.id.text_layout == v.getId())) { + createVolumeDialog().show(); + } + } +} diff --git a/src/com/android/settings/profiles/TriggersFragment.java b/src/com/android/settings/profiles/TriggersFragment.java new file mode 100644 index 0000000..5da8186 --- /dev/null +++ b/src/com/android/settings/profiles/TriggersFragment.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2013 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 com.android.settings.profiles; + +import android.app.ActionBar; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.Profile; +import android.app.Profile.ProfileTrigger; +import android.app.Profile.TriggerType; +import android.app.ProfileManager; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceScreen; +import android.util.Log; +import android.widget.ArrayAdapter; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +/** + * Settings Preference to configure triggers to switch profiles base on Wi-Fi events + */ +public class TriggersFragment extends SettingsPreferenceFragment implements ActionBar.OnNavigationListener { + private Profile mProfile; + private Preference mSelectedTrigger; + private ProfileManager mProfileManager; + private WifiManager mWifiManager; + private BluetoothAdapter mBluetoothAdapter; + + private int mTriggerFilter = 0; + private static final int WIFI_TRIGGER = 1; + private static final int BT_TRIGGER = 2; + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mProfileManager = (ProfileManager) getSystemService(Context.PROFILE_SERVICE); + mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();; + addPreferencesFromResource(R.xml.wifi_settings); + } + + @Override + public void onResume() { + super.onResume(); + loadPreferences(); + loadActionBar(); + } + + private void loadActionBar() { + final ActionBar actionBar = getActivity().getActionBar(); + actionBar.setDisplayShowTitleEnabled(true); + actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); + Resources res = getResources(); + + String[] navItems = res.getStringArray(R.array.profile_trigger_filters); + ArrayAdapter<String> navAdapter = new ArrayAdapter<String>( + actionBar.getThemedContext(), android.R.layout.simple_list_item_1, navItems); + + // Set up the dropdown list navigation in the action bar. + actionBar.setListNavigationCallbacks(navAdapter, this); + actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP + | ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Bundle args = getArguments(); + if (args != null) { + mProfile = args.getParcelable("profile"); + } + } + + private void initPreference(AbstractTriggerPreference pref, int state, Resources res, int icon) { + String summary = res.getStringArray(R.array.profile_trigger_wifi_options)[state]; + pref.setSummary(summary); + pref.setTriggerState(state); + pref.setIcon(icon); + } + + private void loadPreferences() { + final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks(); + final Resources res = getResources(); + final List<AbstractTriggerPreference> prefs = new ArrayList<AbstractTriggerPreference>(); + + getPreferenceScreen().removeAll(); + + if (mTriggerFilter == WIFI_TRIGGER || mTriggerFilter == 0) { + if (configs != null ) { + for (WifiConfiguration config : configs) { + WifiTriggerAPPreference accessPoint = + new WifiTriggerAPPreference(getActivity(), config); + int state = mProfile.getTrigger(Profile.TriggerType.WIFI, accessPoint.getSSID()); + initPreference(accessPoint, state, res, R.drawable.ic_wifi_signal_4_dark); + prefs.add(accessPoint); + } + } else { + final List<ProfileTrigger> triggers = mProfile.getTriggersFromType(TriggerType.WIFI); + for (ProfileTrigger trigger : triggers) { + WifiTriggerAPPreference accessPoint = + new WifiTriggerAPPreference(getActivity(), trigger.getName()); + initPreference(accessPoint, trigger.getState(), res, R.drawable.ic_wifi_signal_4_dark); + prefs.add(accessPoint); + } + } + } + + Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); + if (mTriggerFilter == BT_TRIGGER || mTriggerFilter == 0) { + if (!pairedDevices.isEmpty()) { + for (BluetoothDevice device : pairedDevices) { + BluetoothTriggerPreference bt = + new BluetoothTriggerPreference(getActivity(), device); + int state = mProfile.getTrigger(Profile.TriggerType.BLUETOOTH, bt.getAddress()); + initPreference(bt, state, res, R.drawable.ic_settings_bluetooth2); + prefs.add(bt); + } + } else { + final List<ProfileTrigger> triggers = mProfile.getTriggersFromType(TriggerType.BLUETOOTH); + for (ProfileTrigger trigger : triggers) { + BluetoothTriggerPreference bt = new BluetoothTriggerPreference(getActivity(), + trigger.getName(), trigger.getId()); + initPreference(bt, trigger.getState(), res, R.drawable.ic_settings_bluetooth2); + prefs.add(bt); + } + } + } + + Collections.sort(prefs, new Comparator<AbstractTriggerPreference>() { + @Override + public int compare(AbstractTriggerPreference o1, AbstractTriggerPreference o2) { + if (o1.getTriggerState() == o2.getTriggerState()) { + return o1.compareTo(o2); + } + return o1.getTriggerState() < o2.getTriggerState() ? -1 : 1; + } + }); + for (Preference pref: prefs) { + getPreferenceScreen().addPreference(pref); + } + + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) { + mSelectedTrigger = preference; + if (preference instanceof WifiTriggerAPPreference) { + showDialog(WIFI_TRIGGER); + return true; + } else if (preference instanceof BluetoothTriggerPreference) { + showDialog(BT_TRIGGER); + return true; + } + return super.onPreferenceTreeClick(screen, preference); + } + + @Override + public Dialog onCreateDialog(final int dialogId) { + final String id; + final String triggerName = mSelectedTrigger.getTitle().toString(); + final int triggerType; + + switch (dialogId) { + case WIFI_TRIGGER: + WifiTriggerAPPreference pref = (WifiTriggerAPPreference) mSelectedTrigger; + id = pref.getSSID(); + triggerType = Profile.TriggerType.WIFI; + break; + case BT_TRIGGER: + BluetoothTriggerPreference btpref = (BluetoothTriggerPreference) mSelectedTrigger; + id = btpref.getAddress(); + triggerType = Profile.TriggerType.BLUETOOTH; + break; + default: + return super.onCreateDialog(dialogId); + } + + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.profile_trigger_configure) + .setSingleChoiceItems(R.array.profile_trigger_wifi_options, + mProfile.getTrigger(triggerType, id), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mProfile.setTrigger(triggerType, id, which, triggerName); + mProfileManager.updateProfile(mProfile); + loadPreferences(); + dialog.dismiss(); + } + }) + .create(); + } + + @Override + public boolean onNavigationItemSelected(int itemPosition, long itemId) { + mTriggerFilter = itemPosition; + loadPreferences(); + return true; + } +} diff --git a/src/com/android/settings/profiles/WifiTriggerAPPreference.java b/src/com/android/settings/profiles/WifiTriggerAPPreference.java new file mode 100644 index 0000000..239509a --- /dev/null +++ b/src/com/android/settings/profiles/WifiTriggerAPPreference.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2013 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 com.android.settings.profiles; + +import android.content.Context; +import android.net.wifi.WifiConfiguration; + +public class WifiTriggerAPPreference extends AbstractTriggerPreference { + + private String mSSID; + private WifiConfiguration mConfig; + + WifiTriggerAPPreference(Context context, WifiConfiguration config) { + super(context); + loadConfig(config); + setTitle(mSSID); + } + + WifiTriggerAPPreference(Context context, String ssid) { + super(context); + mSSID = ssid; + setTitle(mSSID); + } + + private void loadConfig(WifiConfiguration config) { + mSSID = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID)); + mConfig = config; + } + + public String getSSID() { + return mSSID; + } + + public static String removeDoubleQuotes(String string) { + final int length = string.length(); + if (length >= 2) { + if (string.startsWith("\"") && string.endsWith("\"")) { + return string.substring(1, length - 1); + } + } + return string; + } +} |