diff options
Diffstat (limited to 'src')
68 files changed, 5465 insertions, 1214 deletions
diff --git a/src/com/android/settings/AccessibilitySettings.java b/src/com/android/settings/AccessibilitySettings.java index 826410d..9a0db5d 100644 --- a/src/com/android/settings/AccessibilitySettings.java +++ b/src/com/android/settings/AccessibilitySettings.java @@ -16,6 +16,7 @@ package com.android.settings; +import android.accessibilityservice.AccessibilityServiceInfo; import android.app.AlertDialog; import android.app.Dialog; import android.app.Service; @@ -78,7 +79,7 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements private CheckBoxPreference mToggleAccessibilityCheckBox; private CheckBoxPreference mToggleScriptInjectionCheckBox; - private CheckBoxPreference mToggleAccessibilityServiceCheckBox; + private SettingsCheckBoxPreference mToggleAccessibilityServiceCheckBox; private PreferenceCategory mPowerButtonCategory; private CheckBoxPreference mPowerButtonEndsCallCheckBox; @@ -87,8 +88,8 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements private ListPreference mLongPressTimeoutListPreference; - private Map<String, ServiceInfo> mAccessibilityServices = - new LinkedHashMap<String, ServiceInfo>(); + private Map<String, AccessibilityServiceInfo> mAccessibilityServices = + new LinkedHashMap<String, AccessibilityServiceInfo>(); private TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':'); @@ -157,7 +158,7 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements } } - Map<String, ServiceInfo> accessibilityServices = mAccessibilityServices; + Map<String, AccessibilityServiceInfo> accessibilityServices = mAccessibilityServices; for (String key : accessibilityServices.keySet()) { CheckBoxPreference preference = (CheckBoxPreference) findPreference(key); @@ -230,9 +231,9 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements throw new IllegalArgumentException( KEY_TOGGLE_ACCESSIBILITY_SERVICE_CHECKBOX + " must be mapped to an instance of a " - + CheckBoxPreference.class.getName()); + + SettingsCheckBoxPreference.class.getName()); } - mToggleAccessibilityServiceCheckBox = (CheckBoxPreference) preference; + mToggleAccessibilityServiceCheckBox = (SettingsCheckBoxPreference) preference; } } @@ -274,7 +275,7 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements } else if (TOGGLE_ACCESSIBILITY_SCRIPT_INJECTION_CHECKBOX.equals(key)) { handleToggleAccessibilityScriptInjection((CheckBoxPreference) preference); } else if (preference instanceof CheckBoxPreference) { - handleEnableAccessibilityServiceStateChange((CheckBoxPreference) preference); + handleEnableAccessibilityServiceStateChange((SettingsCheckBoxPreference) preference); } return super.onPreferenceTreeClick(preferenceScreen, preference); @@ -318,7 +319,8 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements * * @param preference The preference. */ - private void handleEnableAccessibilityServiceStateChange(CheckBoxPreference preference) { + private void handleEnableAccessibilityServiceStateChange( + SettingsCheckBoxPreference preference) { if (preference.isChecked()) { mToggleAccessibilityServiceCheckBox = preference; // set right enabled state since the user may press back @@ -357,7 +359,8 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements AccessibilityManager accessibilityManager = (AccessibilityManager) getSystemService(Service.ACCESSIBILITY_SERVICE); - List<ServiceInfo> installedServices = accessibilityManager.getAccessibilityServiceList(); + List<AccessibilityServiceInfo> installedServices = + accessibilityManager.getInstalledAccessibilityServiceList(); if (installedServices.isEmpty()) { getPreferenceScreen().removePreference(mAccessibilityServicesCategory); @@ -367,12 +370,22 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements getPreferenceScreen().addPreference(mAccessibilityServicesCategory); for (int i = 0, count = installedServices.size(); i < count; ++i) { - ServiceInfo serviceInfo = installedServices.get(i); - String key = serviceInfo.packageName + "/" + serviceInfo.name; - - if (mAccessibilityServices.put(key, serviceInfo) == null) { - CheckBoxPreference preference = new CheckBoxPreference(getActivity()); + AccessibilityServiceInfo accessibilityServiceInfo = installedServices.get(i); + String key = accessibilityServiceInfo.getId(); + + if (mAccessibilityServices.put(key, accessibilityServiceInfo) == null) { + String settingsActivityName = accessibilityServiceInfo.getSettingsActivityName(); + Intent settingsIntent = null; + if (!TextUtils.isEmpty(settingsActivityName)) { + String packageName = accessibilityServiceInfo.getResolveInfo() + .serviceInfo.packageName; + settingsIntent = new Intent(Intent.ACTION_MAIN); + settingsIntent.setClassName(packageName, settingsActivityName); + } + SettingsCheckBoxPreference preference = new SettingsCheckBoxPreference( + getActivity(), settingsIntent); preference.setKey(key); + ServiceInfo serviceInfo = accessibilityServiceInfo.getResolveInfo().serviceInfo; preference.setTitle(serviceInfo.loadLabel(getActivity().getPackageManager())); mAccessibilityServicesCategory.addPreference(preference); } @@ -424,7 +437,8 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements .setMessage(getResources().getString( R.string.accessibility_service_security_warning, mAccessibilityServices.get(mToggleAccessibilityServiceCheckBox.getKey()) - .applicationInfo.loadLabel(getActivity().getPackageManager()))) + .getResolveInfo().serviceInfo.applicationInfo.loadLabel( + getActivity().getPackageManager()))) .setCancelable(true) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { diff --git a/src/com/android/settings/ApplicationSettings.java b/src/com/android/settings/ApplicationSettings.java index da417ec..15eb840 100644 --- a/src/com/android/settings/ApplicationSettings.java +++ b/src/com/android/settings/ApplicationSettings.java @@ -18,6 +18,7 @@ package com.android.settings; import android.app.AlertDialog; import android.content.DialogInterface; +import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; import android.preference.CheckBoxPreference; @@ -26,11 +27,13 @@ import android.preference.Preference; import android.preference.PreferenceScreen; import android.preference.Preference.OnPreferenceChangeListener; import android.provider.Settings; +import android.util.Log; public class ApplicationSettings extends SettingsPreferenceFragment implements DialogInterface.OnClickListener { private static final String KEY_TOGGLE_INSTALL_APPLICATIONS = "toggle_install_applications"; + private static final String KEY_TOGGLE_ADVANCED_SETTINGS = "toggle_advanced_settings"; private static final String KEY_APP_INSTALL_LOCATION = "app_install_location"; // App installation location. Default is ask the user. @@ -43,9 +46,8 @@ public class ApplicationSettings extends SettingsPreferenceFragment implements private static final String APP_INSTALL_AUTO_ID = "auto"; private CheckBoxPreference mToggleAppInstallation; - + private CheckBoxPreference mToggleAdvancedSettings; private ListPreference mInstallLocation; - private DialogInterface mWarnInstallApps; @Override @@ -54,9 +56,20 @@ public class ApplicationSettings extends SettingsPreferenceFragment implements addPreferencesFromResource(R.xml.application_settings); - mToggleAppInstallation = (CheckBoxPreference) findPreference(KEY_TOGGLE_INSTALL_APPLICATIONS); + mToggleAppInstallation = (CheckBoxPreference)findPreference( + KEY_TOGGLE_INSTALL_APPLICATIONS); mToggleAppInstallation.setChecked(isNonMarketAppsAllowed()); + mToggleAdvancedSettings = (CheckBoxPreference)findPreference( + KEY_TOGGLE_ADVANCED_SETTINGS); + mToggleAdvancedSettings.setChecked(isAdvancedSettingsEnabled()); + getPreferenceScreen().removePreference(mToggleAdvancedSettings); + + // not ready for prime time yet + if (false) { + getPreferenceScreen().removePreference(mInstallLocation); + } + mInstallLocation = (ListPreference) findPreference(KEY_APP_INSTALL_LOCATION); // Is app default install location set? boolean userSetInstLocation = (Settings.System.getInt(getContentResolver(), @@ -110,6 +123,9 @@ public class ApplicationSettings extends SettingsPreferenceFragment implements } else { setNonMarketAppsAllowed(false); } + } else if (preference == mToggleAdvancedSettings) { + boolean value = mToggleAdvancedSettings.isChecked(); + setAdvancedSettingsEnabled(value); } return super.onPreferenceTreeClick(preferenceScreen, preference); @@ -127,7 +143,23 @@ public class ApplicationSettings extends SettingsPreferenceFragment implements Settings.Secure.putInt(getContentResolver(), Settings.Secure.INSTALL_NON_MARKET_APPS, enabled ? 1 : 0); } - + + private boolean isAdvancedSettingsEnabled() { + return Settings.System.getInt(getContentResolver(), + Settings.System.ADVANCED_SETTINGS, + Settings.System.ADVANCED_SETTINGS_DEFAULT) > 0; + } + + private void setAdvancedSettingsEnabled(boolean enabled) { + int value = enabled ? 1 : 0; + // Change the system setting + Settings.Secure.putInt(getContentResolver(), Settings.System.ADVANCED_SETTINGS, value); + // TODO: the settings thing should broadcast this for thread safety purposes. + Intent intent = new Intent(Intent.ACTION_ADVANCED_SETTINGS_CHANGED); + intent.putExtra("state", value); + getActivity().sendBroadcast(intent); + } + private boolean isNonMarketAppsAllowed() { return Settings.Secure.getInt(getContentResolver(), Settings.Secure.INSTALL_NON_MARKET_APPS, 0) > 0; diff --git a/src/com/android/settings/BrightnessPreference.java b/src/com/android/settings/BrightnessPreference.java index 9bbb66a..df50ada 100644 --- a/src/com/android/settings/BrightnessPreference.java +++ b/src/com/android/settings/BrightnessPreference.java @@ -26,7 +26,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; -import android.preference.SeekBarPreference; +import android.preference.SeekBarDialogPreference; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.util.AttributeSet; @@ -35,7 +35,7 @@ import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.SeekBar; -public class BrightnessPreference extends SeekBarPreference implements +public class BrightnessPreference extends SeekBarDialogPreference implements SeekBar.OnSeekBarChangeListener, CheckBox.OnCheckedChangeListener { private SeekBar mSeekBar; diff --git a/src/com/android/settings/ChooseLockGeneric.java b/src/com/android/settings/ChooseLockGeneric.java index 118bc6f..8311c4a 100644 --- a/src/com/android/settings/ChooseLockGeneric.java +++ b/src/com/android/settings/ChooseLockGeneric.java @@ -27,6 +27,7 @@ import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceCategory; import android.preference.PreferenceScreen; +import android.security.KeyStore; public class ChooseLockGeneric extends PreferenceActivity { @@ -48,9 +49,11 @@ public class ChooseLockGeneric extends PreferenceActivity { private static final int CONFIRM_EXISTING_REQUEST = 100; private static final String PASSWORD_CONFIRMED = "password_confirmed"; private static final String CONFIRM_CREDENTIALS = "confirm_credentials"; + public static final String MINIMUM_QUALITY_KEY = "minimum_quality"; private ChooseLockSettingsHelper mChooseLockSettingsHelper; private DevicePolicyManager mDPM; + private KeyStore mKeyStore; private boolean mPasswordConfirmed = false; @Override @@ -58,6 +61,7 @@ public class ChooseLockGeneric extends PreferenceActivity { super.onCreate(savedInstanceState); mDPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE); + mKeyStore = KeyStore.getInstance(); mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this.getActivity()); if (savedInstanceState != null) { @@ -126,8 +130,8 @@ public class ChooseLockGeneric extends PreferenceActivity { .getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, -1); if (quality == -1) { // If caller didn't specify password quality, show UI and allow the user to choose. - quality = mDPM.getPasswordQuality(null); - quality = upgradeQualityForEncryption(quality); + quality = getActivity().getIntent().getIntExtra(MINIMUM_QUALITY_KEY, -1); + quality = upgradeQuality(quality); final PreferenceScreen prefScreen = getPreferenceScreen(); if (prefScreen != null) { prefScreen.removeAll(); @@ -135,11 +139,26 @@ public class ChooseLockGeneric extends PreferenceActivity { addPreferencesFromResource(R.xml.security_settings_picker); disableUnusablePreferences(quality); } else { - quality = upgradeQualityForEncryption(quality); updateUnlockMethodAndFinish(quality, false); } } + private int upgradeQuality(int quality) { + quality = upgradeQualityForDPM(quality); + quality = upgradeQualityForEncryption(quality); + quality = upgradeQualityForKeyStore(quality); + return quality; + } + + private int upgradeQualityForDPM(int quality) { + // Compare min allowed password quality + int minQuality = mDPM.getPasswordQuality(null); + if (quality < minQuality) { + quality = minQuality; + } + return quality; + } + /** * Mix in "encryption minimums" to any given quality value. This prevents users * from downgrading the pattern/pin/password to a level below the minimums. @@ -152,8 +171,17 @@ public class ChooseLockGeneric extends PreferenceActivity { boolean encrypted = (encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE) || (encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVATING); if (encrypted) { - if (quality < DevicePolicyManager.PASSWORD_QUALITY_NUMERIC) { - quality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; + if (quality < CryptKeeperSettings.MIN_PASSWORD_QUALITY) { + quality = CryptKeeperSettings.MIN_PASSWORD_QUALITY; + } + } + return quality; + } + + private int upgradeQualityForKeyStore(int quality) { + if (!mKeyStore.isEmpty()) { + if (quality < CredentialStorage.MIN_PASSWORD_QUALITY) { + quality = CredentialStorage.MIN_PASSWORD_QUALITY; } } return quality; @@ -208,13 +236,7 @@ public class ChooseLockGeneric extends PreferenceActivity { throw new IllegalStateException("Tried to update password without confirming it"); } - // Compare min allowed password quality and launch appropriate security setting method - int minQuality = mDPM.getPasswordQuality(null); - if (quality < minQuality) { - quality = minQuality; - } - quality = upgradeQualityForEncryption(quality); - + quality = upgradeQuality(quality); if (quality >= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC) { int minLength = mDPM.getPasswordMinimumLength(null); if (minLength < MIN_PASSWORD_LENGTH) { diff --git a/src/com/android/settings/ChooseLockPassword.java b/src/com/android/settings/ChooseLockPassword.java index a0f2346..96255eb 100644 --- a/src/com/android/settings/ChooseLockPassword.java +++ b/src/com/android/settings/ChooseLockPassword.java @@ -405,8 +405,10 @@ public class ChooseLockPassword extends PreferenceActivity { } public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - // Check if this was the result of hitting the enter key - if (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_DOWN) { + // Check if this was the result of hitting the enter or "done" key + if (actionId == EditorInfo.IME_NULL + || actionId == EditorInfo.IME_ACTION_DONE + || actionId == EditorInfo.IME_ACTION_NEXT) { handleNext(); return true; } diff --git a/src/com/android/settings/ChooseLockSettingsHelper.java b/src/com/android/settings/ChooseLockSettingsHelper.java index d31fe3b..a069712 100644 --- a/src/com/android/settings/ChooseLockSettingsHelper.java +++ b/src/com/android/settings/ChooseLockSettingsHelper.java @@ -23,7 +23,10 @@ import android.app.Fragment; import android.app.admin.DevicePolicyManager; import android.content.Intent; -public class ChooseLockSettingsHelper { +public final class ChooseLockSettingsHelper { + + static final String EXTRA_KEY_PASSWORD = "password"; + private LockPatternUtils mLockPatternUtils; private Activity mActivity; private Fragment mFragment; @@ -49,8 +52,7 @@ public class ChooseLockSettingsHelper { * @return true if one exists and we launched an activity to confirm it * @see #onActivityResult(int, int, android.content.Intent) */ - protected boolean launchConfirmationActivity(int request, - CharSequence message, CharSequence details) { + boolean launchConfirmationActivity(int request, CharSequence message, CharSequence details) { boolean launched = false; switch (mLockPatternUtils.getKeyguardStoredPasswordQuality()) { case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: diff --git a/src/com/android/settings/ConfirmLockPassword.java b/src/com/android/settings/ConfirmLockPassword.java index 3f4a4f3..1229046 100644 --- a/src/com/android/settings/ConfirmLockPassword.java +++ b/src/com/android/settings/ConfirmLockPassword.java @@ -27,13 +27,16 @@ import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceActivity; +import android.text.Editable; import android.text.InputType; +import android.text.TextWatcher; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; +import android.widget.Button; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; @@ -58,7 +61,7 @@ public class ConfirmLockPassword extends PreferenceActivity { } public static class ConfirmLockPasswordFragment extends Fragment implements OnClickListener, - OnEditorActionListener { + OnEditorActionListener, TextWatcher { private static final long ERROR_MESSAGE_TIMEOUT = 3000; private TextView mPasswordEntry; private LockPatternUtils mLockPatternUtils; @@ -66,6 +69,7 @@ public class ConfirmLockPassword extends PreferenceActivity { private Handler mHandler = new Handler(); private PasswordEntryKeyboardHelper mKeyboardHelper; private PasswordEntryKeyboardView mKeyboardView; + private Button mContinueButton; // required constructor for fragments @@ -87,9 +91,14 @@ public class ConfirmLockPassword extends PreferenceActivity { // Disable IME on our window since we provide our own keyboard view.findViewById(R.id.cancel_button).setOnClickListener(this); - view.findViewById(R.id.next_button).setOnClickListener(this); + mContinueButton = (Button) view.findViewById(R.id.next_button); + mContinueButton.setOnClickListener(this); + mContinueButton.setEnabled(false); // disable until the user enters at least one char + mPasswordEntry = (TextView) view.findViewById(R.id.password_entry); mPasswordEntry.setOnEditorActionListener(this); + mPasswordEntry.addTextChangedListener(this); + mKeyboardView = (PasswordEntryKeyboardView) view.findViewById(R.id.keyboard); mHeaderText = (TextView) view.findViewById(R.id.headerText); final boolean isAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == storedQuality @@ -140,7 +149,7 @@ public class ConfirmLockPassword extends PreferenceActivity { if (mLockPatternUtils.checkPassword(pin)) { Intent intent = new Intent(); - intent.putExtra("password", pin); + intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pin); getActivity().setResult(RESULT_OK, intent); getActivity().finish(); @@ -172,13 +181,27 @@ public class ConfirmLockPassword extends PreferenceActivity { }, ERROR_MESSAGE_TIMEOUT); } + // {@link OnEditorActionListener} methods. public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - // Check if this was the result of hitting the enter key - if (actionId == EditorInfo.IME_NULL) { + // Check if this was the result of hitting the enter or "done" key + if (actionId == EditorInfo.IME_NULL + || actionId == EditorInfo.IME_ACTION_DONE + || actionId == EditorInfo.IME_ACTION_NEXT) { handleNext(); return true; } return false; } + + // {@link TextWatcher} methods. + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + public void afterTextChanged(Editable s) { + mContinueButton.setEnabled(mPasswordEntry.getText().length() > 0); + } } } diff --git a/src/com/android/settings/ConfirmLockPattern.java b/src/com/android/settings/ConfirmLockPattern.java index 0653d3f..2892930 100644 --- a/src/com/android/settings/ConfirmLockPattern.java +++ b/src/com/android/settings/ConfirmLockPattern.java @@ -256,7 +256,12 @@ public class ConfirmLockPattern extends PreferenceActivity { public void onPatternDetected(List<LockPatternView.Cell> pattern) { if (mLockPatternUtils.checkPattern(pattern)) { - getActivity().setResult(Activity.RESULT_OK); + + Intent intent = new Intent(); + intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, + LockPatternUtils.patternToString(pattern)); + + getActivity().setResult(Activity.RESULT_OK, intent); getActivity().finish(); } else { if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL && diff --git a/src/com/android/settings/CredentialStorage.java b/src/com/android/settings/CredentialStorage.java index 9d5a603..e246fce 100644 --- a/src/com/android/settings/CredentialStorage.java +++ b/src/com/android/settings/CredentialStorage.java @@ -18,213 +18,417 @@ package com.android.settings; import android.app.Activity; import android.app.AlertDialog; +import android.app.admin.DevicePolicyManager; import android.content.DialogInterface; import android.content.Intent; +import android.content.res.Resources; +import android.os.AsyncTask; import android.os.Bundle; +import android.os.RemoteException; +import android.security.KeyChain.KeyChainConnection; +import android.security.KeyChain; import android.security.KeyStore; import android.text.Editable; +import android.text.TextUtils; import android.text.TextWatcher; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; +import com.android.internal.widget.LockPatternUtils; -import java.io.UnsupportedEncodingException; +/** + * CredentialStorage handles KeyStore reset, unlock, and install. + * + * CredentialStorage has a pretty convoluted state machine to migrate + * from the old style separate keystore password to a new key guard + * based password, as well as to deal with setting up the key guard if + * necessary. + * + * KeyStore: UNINITALIZED + * KeyGuard: OFF + * Action: set up key guard + * Notes: factory state + * + * KeyStore: UNINITALIZED + * KeyGuard: ON + * Action: confirm key guard + * Notes: user had key guard but no keystore and upgraded from pre-ICS + * OR user had key guard and pre-ICS keystore password which was then reset + * + * KeyStore: LOCKED + * KeyGuard: OFF/ON + * Action: old unlock dialog + * Notes: assume old password, need to use it to unlock. + * if unlock, ensure key guard before install. + * if reset, treat as UNINITALIZED/OFF + * + * KeyStore: UNLOCKED + * KeyGuard: OFF + * Action: set up key guard + * Notes: ensure key guard, then proceed + * + * KeyStore: UNLOCKED + * keyguard: ON + * Action: normal unlock/install + * Notes: this is the common case + */ +public final class CredentialStorage extends Activity { -public class CredentialStorage extends Activity implements TextWatcher, - DialogInterface.OnClickListener, DialogInterface.OnDismissListener { + private static final String TAG = "CredentialStorage"; public static final String ACTION_UNLOCK = "com.android.credentials.UNLOCK"; - public static final String ACTION_SET_PASSWORD = "com.android.credentials.SET_PASSWORD"; public static final String ACTION_INSTALL = "com.android.credentials.INSTALL"; public static final String ACTION_RESET = "com.android.credentials.RESET"; - private static final String TAG = "CredentialStorage"; + // This is the minimum acceptable password quality. If the current password quality is + // lower than this, keystore should not be activated. + static final int MIN_PASSWORD_QUALITY = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; - private KeyStore mKeyStore = KeyStore.getInstance(); - private boolean mSubmit = false; - private Bundle mBundle; + private static final int CONFIRM_KEY_GUARD_REQUEST = 1; - private TextView mOldPassword; - private TextView mNewPassword; - private TextView mConfirmPassword; - private TextView mError; - private Button mButton; + private final KeyStore mKeyStore = KeyStore.getInstance(); - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + /** + * When non-null, the bundle containing credentials to install. + */ + private Bundle mInstallBundle; + + /** + * After unsuccessful KeyStore.unlock, the number of unlock + * attempts remaining before the KeyStore will reset itself. + * + * Reset to -1 on successful unlock or reset. + */ + private int mRetriesRemaining = -1; + + @Override protected void onResume() { + super.onResume(); Intent intent = getIntent(); String action = intent.getAction(); - int state = mKeyStore.test(); if (ACTION_RESET.equals(action)) { - showResetDialog(); - } else if (ACTION_SET_PASSWORD.equals(action)) { - showPasswordDialog(state == KeyStore.UNINITIALIZED); + new ResetDialog(); } else { if (ACTION_INSTALL.equals(action) && "com.android.certinstaller".equals(getCallingPackage())) { - mBundle = intent.getExtras(); - } - if (state == KeyStore.UNINITIALIZED) { - showPasswordDialog(true); - } else if (state == KeyStore.LOCKED) { - showUnlockDialog(); - } else { - install(); - finish(); + mInstallBundle = intent.getExtras(); } + // ACTION_UNLOCK also handled here in addition to ACTION_INSTALL + handleUnlockOrInstall(); } } - private void install() { - if (mBundle != null && !mBundle.isEmpty()) { - try { - for (String key : mBundle.keySet()) { - byte[] value = mBundle.getByteArray(key); - if (value != null && !mKeyStore.put(key.getBytes("UTF-8"), value)) { - Log.e(TAG, "Failed to install " + key); - return; - } + /** + * Based on the current state of the KeyStore and key guard, try to + * make progress on unlocking or installing to the keystore. + */ + private void handleUnlockOrInstall() { + // something already decided we are done, do not proceed + if (isFinishing()) { + return; + } + switch (mKeyStore.state()) { + case UNINITIALIZED: { + ensureKeyGuard(); + return; + } + case LOCKED: { + new UnlockDialog(); + return; + } + case UNLOCKED: { + if (!checkKeyGuardQuality()) { + new ConfigureKeyGuardDialog(); + return; } - setResult(RESULT_OK); - } catch (UnsupportedEncodingException e) { - // Should never happen. - throw new RuntimeException(e); + installIfAvailable(); + finish(); + return; } } } - private void showResetDialog() { - AlertDialog dialog = new AlertDialog.Builder(this) - .setTitle(android.R.string.dialog_alert_title) - .setIcon(android.R.drawable.ic_dialog_alert) - .setMessage(R.string.credentials_reset_hint) - .setNeutralButton(android.R.string.ok, this) - .setNegativeButton(android.R.string.cancel, this) - .create(); - dialog.setOnDismissListener(this); - dialog.show(); + /** + * Make sure the user enters the key guard to set or change the + * keystore password. This can be used in UNINITIALIZED to set the + * keystore password or UNLOCKED to change the password (as is the + * case after unlocking with an old-style password). + */ + private void ensureKeyGuard() { + if (!checkKeyGuardQuality()) { + // key guard not setup, doing so will initialize keystore + new ConfigureKeyGuardDialog(); + // will return to onResume after Activity + return; + } + // force key guard confirmation + if (confirmKeyGuard()) { + // will return password value via onActivityResult + return; + } + finish(); } - private void showPasswordDialog(boolean firstTime) { - View view = View.inflate(this, R.layout.credentials_dialog, null); + /** + * Returns true if the currently set key guard matches our minimum quality requirements. + */ + private boolean checkKeyGuardQuality() { + int quality = new LockPatternUtils(this).getActivePasswordQuality(); + return (quality >= MIN_PASSWORD_QUALITY); + } - ((TextView) view.findViewById(R.id.hint)).setText(R.string.credentials_password_hint); - if (!firstTime) { - view.findViewById(R.id.old_password_prompt).setVisibility(View.VISIBLE); - mOldPassword = (TextView) view.findViewById(R.id.old_password); - mOldPassword.setVisibility(View.VISIBLE); - mOldPassword.addTextChangedListener(this); + /** + * Install credentials if available, otherwise do nothing. + */ + private void installIfAvailable() { + if (mInstallBundle != null && !mInstallBundle.isEmpty()) { + Bundle bundle = mInstallBundle; + mInstallBundle = null; + for (String key : bundle.keySet()) { + byte[] value = bundle.getByteArray(key); + if (value != null && !mKeyStore.put(key, value)) { + Log.e(TAG, "Failed to install " + key); + return; + } + } + setResult(RESULT_OK); } - view.findViewById(R.id.new_passwords).setVisibility(View.VISIBLE); - mNewPassword = (TextView) view.findViewById(R.id.new_password); - mNewPassword.addTextChangedListener(this); - mConfirmPassword = (TextView) view.findViewById(R.id.confirm_password); - mConfirmPassword.addTextChangedListener(this); - mError = (TextView) view.findViewById(R.id.error); - - AlertDialog dialog = new AlertDialog.Builder(this) - .setView(view) - .setTitle(R.string.credentials_set_password) - .setPositiveButton(android.R.string.ok, this) - .setNegativeButton(android.R.string.cancel, this) - .create(); - dialog.setOnDismissListener(this); - dialog.show(); - mButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); - mButton.setEnabled(false); } - private void showUnlockDialog() { - View view = View.inflate(this, R.layout.credentials_dialog, null); - - ((TextView) view.findViewById(R.id.hint)).setText(R.string.credentials_unlock_hint); - mOldPassword = (TextView) view.findViewById(R.id.old_password); - mOldPassword.setVisibility(View.VISIBLE); - mOldPassword.addTextChangedListener(this); - mError = (TextView) view.findViewById(R.id.error); - - AlertDialog dialog = new AlertDialog.Builder(this) - .setView(view) - .setTitle(R.string.credentials_unlock) - .setPositiveButton(android.R.string.ok, this) - .setNegativeButton(android.R.string.cancel, this) - .create(); - dialog.setOnDismissListener(this); - dialog.show(); - mButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); - mButton.setEnabled(false); - } + /** + * Prompt for reset confirmation, resetting on confirmation, finishing otherwise. + */ + private class ResetDialog + implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener + { + private boolean mResetConfirmed; - public void afterTextChanged(Editable editable) { - if ((mOldPassword == null || mOldPassword.getText().length() > 0) && - (mNewPassword == null || mNewPassword.getText().length() >= 8) && - (mConfirmPassword == null || mConfirmPassword.getText().length() >= 8)) { - mButton.setEnabled(true); - } else { - mButton.setEnabled(false); + private ResetDialog() { + AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this) + .setTitle(android.R.string.dialog_alert_title) + .setIcon(android.R.drawable.ic_dialog_alert) + .setMessage(R.string.credentials_reset_hint) + .setPositiveButton(android.R.string.ok, this) + .setNegativeButton(android.R.string.cancel, this) + .create(); + dialog.setOnDismissListener(this); + dialog.show(); } - } - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } + @Override public void onClick(DialogInterface dialog, int button) { + mResetConfirmed = (button == DialogInterface.BUTTON_POSITIVE); + } - public void onTextChanged(CharSequence s,int start, int before, int count) { + @Override public void onDismiss(DialogInterface dialog) { + if (mResetConfirmed) { + mResetConfirmed = false; + new ResetKeyStoreAndKeyChain().execute(); + return; + } + finish(); + } } - public void onClick(DialogInterface dialog, int button) { - mSubmit = (button == DialogInterface.BUTTON_POSITIVE); - if (button == DialogInterface.BUTTON_NEUTRAL) { + /** + * Background task to handle reset of both keystore and user installed CAs. + */ + private class ResetKeyStoreAndKeyChain extends AsyncTask<Void, Void, Boolean> { + + @Override protected Boolean doInBackground(Void... unused) { + mKeyStore.reset(); - Toast.makeText(this, R.string.credentials_erased, Toast.LENGTH_SHORT).show(); + + try { + KeyChainConnection keyChainConnection = KeyChain.bind(CredentialStorage.this); + try { + return keyChainConnection.getService().reset(); + } catch (RemoteException e) { + return false; + } finally { + keyChainConnection.close(); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } + } + + @Override protected void onPostExecute(Boolean success) { + if (success) { + Toast.makeText(CredentialStorage.this, + R.string.credentials_erased, Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(CredentialStorage.this, + R.string.credentials_not_erased, Toast.LENGTH_SHORT).show(); + } + finish(); } } - public void onDismiss(DialogInterface dialog) { - if (mSubmit) { - mSubmit = false; - mError.setVisibility(View.VISIBLE); + /** + * Prompt for key guard configuration confirmation. + */ + private class ConfigureKeyGuardDialog + implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener + { + private boolean mConfigureConfirmed; - if (mNewPassword == null) { - mKeyStore.unlock(mOldPassword.getText().toString()); - } else { - String newPassword = mNewPassword.getText().toString(); - String confirmPassword = mConfirmPassword.getText().toString(); + private ConfigureKeyGuardDialog() { + AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this) + .setTitle(android.R.string.dialog_alert_title) + .setIcon(android.R.drawable.ic_dialog_alert) + .setMessage(R.string.credentials_configure_lock_screen_hint) + .setPositiveButton(android.R.string.ok, this) + .setNegativeButton(android.R.string.cancel, this) + .create(); + dialog.setOnDismissListener(this); + dialog.show(); + } + + @Override public void onClick(DialogInterface dialog, int button) { + mConfigureConfirmed = (button == DialogInterface.BUTTON_POSITIVE); + } - if (!newPassword.equals(confirmPassword)) { - mError.setText(R.string.credentials_passwords_mismatch); - ((AlertDialog) dialog).show(); + @Override public void onDismiss(DialogInterface dialog) { + if (mConfigureConfirmed) { + mConfigureConfirmed = false; + Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD); + intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY, + MIN_PASSWORD_QUALITY); + startActivity(intent); + return; + } + finish(); + } + } + + /** + * Confirm existing key guard, returning password via onActivityResult. + */ + private boolean confirmKeyGuard() { + Resources res = getResources(); + boolean launched = new ChooseLockSettingsHelper(this) + .launchConfirmationActivity(CONFIRM_KEY_GUARD_REQUEST, + res.getText(R.string.master_clear_gesture_prompt), + res.getText(R.string.master_clear_gesture_explanation)); + return launched; + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + /** + * Receive key guard password initiated by confirmKeyGuard. + */ + if (requestCode == CONFIRM_KEY_GUARD_REQUEST) { + if (resultCode == Activity.RESULT_OK) { + String password = data.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); + if (!TextUtils.isEmpty(password)) { + // success + mKeyStore.password(password); + // return to onResume return; - } else if (mOldPassword == null) { - mKeyStore.password(newPassword); - } else { - mKeyStore.password(mOldPassword.getText().toString(), newPassword); } } + // failed confirmation, bail + finish(); + } + } + + /** + * Prompt for unlock with old-style password. + * + * On successful unlock, ensure migration to key guard before continuing. + * On unsuccessful unlock, retry by calling handleUnlockOrInstall. + */ + private class UnlockDialog implements TextWatcher, + DialogInterface.OnClickListener, DialogInterface.OnDismissListener + { + private boolean mUnlockConfirmed; + + private final Button mButton; + private final TextView mOldPassword; + private final TextView mError; + + private UnlockDialog() { + View view = View.inflate(CredentialStorage.this, R.layout.credentials_dialog, null); + + CharSequence text; + if (mRetriesRemaining == -1) { + text = getResources().getText(R.string.credentials_unlock_hint); + } else if (mRetriesRemaining > 3) { + text = getResources().getText(R.string.credentials_wrong_password); + } else if (mRetriesRemaining == 1) { + text = getResources().getText(R.string.credentials_reset_warning); + } else { + text = getString(R.string.credentials_reset_warning_plural, mRetriesRemaining); + } - int error = mKeyStore.getLastError(); - if (error == KeyStore.NO_ERROR) { - Toast.makeText(this, R.string.credentials_enabled, Toast.LENGTH_SHORT).show(); - install(); - } else if (error == KeyStore.UNINITIALIZED) { - Toast.makeText(this, R.string.credentials_erased, Toast.LENGTH_SHORT).show(); - } else if (error >= KeyStore.WRONG_PASSWORD) { - int count = error - KeyStore.WRONG_PASSWORD + 1; - if (count > 3) { - mError.setText(R.string.credentials_wrong_password); - } else if (count == 1) { - mError.setText(R.string.credentials_reset_warning); - } else { - mError.setText(getString(R.string.credentials_reset_warning_plural, count)); + ((TextView) view.findViewById(R.id.hint)).setText(text); + mOldPassword = (TextView) view.findViewById(R.id.old_password); + mOldPassword.setVisibility(View.VISIBLE); + mOldPassword.addTextChangedListener(this); + mError = (TextView) view.findViewById(R.id.error); + + AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this) + .setView(view) + .setTitle(R.string.credentials_unlock) + .setPositiveButton(android.R.string.ok, this) + .setNegativeButton(android.R.string.cancel, this) + .create(); + dialog.setOnDismissListener(this); + dialog.show(); + mButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); + mButton.setEnabled(false); + } + + @Override public void afterTextChanged(Editable editable) { + mButton.setEnabled(mOldPassword == null || mOldPassword.getText().length() > 0); + } + + @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override public void onTextChanged(CharSequence s,int start, int before, int count) { + } + + @Override public void onClick(DialogInterface dialog, int button) { + mUnlockConfirmed = (button == DialogInterface.BUTTON_POSITIVE); + } + + @Override public void onDismiss(DialogInterface dialog) { + if (mUnlockConfirmed) { + mUnlockConfirmed = false; + mError.setVisibility(View.VISIBLE); + mKeyStore.unlock(mOldPassword.getText().toString()); + int error = mKeyStore.getLastError(); + if (error == KeyStore.NO_ERROR) { + mRetriesRemaining = -1; + Toast.makeText(CredentialStorage.this, + R.string.credentials_enabled, + Toast.LENGTH_SHORT).show(); + // aha, now we are unlocked, switch to key guard. + // we'll end up back in onResume to install + ensureKeyGuard(); + } else if (error == KeyStore.UNINITIALIZED) { + mRetriesRemaining = -1; + Toast.makeText(CredentialStorage.this, + R.string.credentials_erased, + Toast.LENGTH_SHORT).show(); + // we are reset, we can now set new password with key guard + handleUnlockOrInstall(); + } else if (error >= KeyStore.WRONG_PASSWORD) { + // we need to try again + mRetriesRemaining = error - KeyStore.WRONG_PASSWORD + 1; + handleUnlockOrInstall(); } - ((AlertDialog) dialog).show(); return; } + finish(); } - finish(); } } diff --git a/src/com/android/settings/CryptKeeper.java b/src/com/android/settings/CryptKeeper.java index edf00d5..20bf7ce 100644 --- a/src/com/android/settings/CryptKeeper.java +++ b/src/com/android/settings/CryptKeeper.java @@ -342,9 +342,11 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList KeyboardView keyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard); - PasswordEntryKeyboardHelper keyboardHelper = new PasswordEntryKeyboardHelper(this, - keyboardView, mPasswordEntry, false); - keyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA); + if (keyboardView != null) { + PasswordEntryKeyboardHelper keyboardHelper = new PasswordEntryKeyboardHelper(this, + keyboardView, mPasswordEntry, false); + keyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA); + } } private IMountService getMountService() { @@ -357,7 +359,7 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (actionId == EditorInfo.IME_NULL) { + if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE) { // Get the password String password = v.getText().toString(); diff --git a/src/com/android/settings/CryptKeeperConfirm.java b/src/com/android/settings/CryptKeeperConfirm.java index ba8ce10..d177cb4 100644 --- a/src/com/android/settings/CryptKeeperConfirm.java +++ b/src/com/android/settings/CryptKeeperConfirm.java @@ -62,6 +62,8 @@ public class CryptKeeperConfirm extends Fragment { public void run() { IBinder service = ServiceManager.getService("mount"); if (service == null) { + Log.e("CryptKeeper", "Failed to find the mount service"); + finish(); return; } diff --git a/src/com/android/settings/CryptKeeperSettings.java b/src/com/android/settings/CryptKeeperSettings.java index 10fa8ac..a9002fa 100644 --- a/src/com/android/settings/CryptKeeperSettings.java +++ b/src/com/android/settings/CryptKeeperSettings.java @@ -37,16 +37,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Button; -/** - * Confirm and execute a reset of the device to a clean "just out of the box" - * state. Multiple confirmations are required: first, a general "are you sure - * you want to do this?" prompt, followed by a keyguard pattern trace if the user - * has defined one, followed by a final strongly-worded "THIS WILL ERASE EVERYTHING - * ON THE PHONE" prompt. If at any time the phone is allowed to go to sleep, is - * locked, et cetera, then the confirmation sequence is abandoned. - * - * This is the initial screen. - */ public class CryptKeeperSettings extends Fragment { private static final String TAG = "CryptKeeper"; @@ -54,7 +44,7 @@ public class CryptKeeperSettings extends Fragment { // This is the minimum acceptable password quality. If the current password quality is // lower than this, encryption should not be activated. - private static final int MIN_PASSWORD_QUALITY = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; + static final int MIN_PASSWORD_QUALITY = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; // Minimum battery charge level (in percent) to launch encryption. If the battery charge is // lower than this, encryption should not be activated. @@ -163,7 +153,7 @@ public class CryptKeeperSettings extends Fragment { */ private boolean runKeyguardConfirmation(int request) { // 1. Confirm that we have a sufficient PIN/Password to continue - int quality = new LockPatternUtils(getActivity()).getKeyguardStoredPasswordQuality(); + int quality = new LockPatternUtils(getActivity()).getActivePasswordQuality(); if (quality < MIN_PASSWORD_QUALITY) { return false; } @@ -186,7 +176,7 @@ public class CryptKeeperSettings extends Fragment { // If the user entered a valid keyguard trace, present the final // confirmation prompt; otherwise, go back to the initial state. if (resultCode == Activity.RESULT_OK && data != null) { - String password = data.getStringExtra("password"); + String password = data.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); if (!TextUtils.isEmpty(password)) { showFinalConfirmation(password); } diff --git a/src/com/android/settings/DataUsageAppDetail.java b/src/com/android/settings/DataUsageAppDetail.java new file mode 100644 index 0000000..6294ad3 --- /dev/null +++ b/src/com/android/settings/DataUsageAppDetail.java @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings; + +import static android.net.NetworkPolicyManager.POLICY_NONE; +import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; +import static com.android.settings.DataUsageSummary.getHistoryBounds; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.Fragment; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.Color; +import android.net.INetworkPolicyManager; +import android.net.INetworkStatsService; +import android.net.NetworkPolicyManager; +import android.net.NetworkStats; +import android.net.NetworkStatsHistory; +import android.net.NetworkTemplate; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.preference.CheckBoxPreference; +import android.preference.Preference; +import android.text.format.DateUtils; +import android.text.format.Formatter; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.settings.widget.DataUsageChartView; +import com.android.settings.widget.DataUsageChartView.DataUsageChartListener; + +public class DataUsageAppDetail extends Fragment { + private static final String TAG = "DataUsage"; + private static final boolean LOGD = true; + + public static final String EXTRA_UID = "uid"; + public static final String EXTRA_NETWORK_TEMPLATE = "networkTemplate"; + + private int mUid; + private NetworkTemplate mTemplate; + + private Intent mAppSettingsIntent; + + private static final String TAG_CONFIRM_RESTRICT = "confirmRestrict"; + + private INetworkStatsService mStatsService; + private INetworkPolicyManager mPolicyService; + + private CheckBoxPreference mRestrictBackground; + private View mRestrictBackgroundView; + + private FrameLayout mChartContainer; + private TextView mTitle; + private TextView mText1; + private Button mAppSettings; + private LinearLayout mSwitches; + + private DataUsageChartView mChart; + private NetworkStatsHistory mHistory; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mStatsService = INetworkStatsService.Stub.asInterface( + ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); + mPolicyService = INetworkPolicyManager.Stub.asInterface( + ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + final Context context = inflater.getContext(); + final View view = inflater.inflate(R.layout.data_usage_detail, container, false); + + mChartContainer = (FrameLayout) view.findViewById(R.id.chart_container); + mTitle = (TextView) view.findViewById(android.R.id.title); + mText1 = (TextView) view.findViewById(android.R.id.text1); + mAppSettings = (Button) view.findViewById(R.id.data_usage_app_settings); + mSwitches = (LinearLayout) view.findViewById(R.id.switches); + + mRestrictBackground = new CheckBoxPreference(context); + mRestrictBackground.setTitle(R.string.data_usage_app_restrict_background); + mRestrictBackground.setSummary(R.string.data_usage_app_restrict_background_summary); + + // kick refresh once to force-create views + refreshPreferenceViews(); + + mSwitches.addView(mRestrictBackgroundView); + mRestrictBackgroundView.setOnClickListener(mRestrictBackgroundListener); + + mAppSettings.setOnClickListener(mAppSettingsListener); + + mChart = new DataUsageChartView(context); + mChartContainer.addView(mChart); + + mChart.setListener(mChartListener); + mChart.setChartColor(Color.parseColor("#d88d3a"), Color.parseColor("#c0ba7f3e"), + Color.parseColor("#88566abc")); + + return view; + } + + @Override + public void onResume() { + super.onResume(); + + updateBody(); + } + + private void updateBody() { + final PackageManager pm = getActivity().getPackageManager(); + + mUid = getArguments().getInt(EXTRA_UID); + mTemplate = getArguments().getParcelable(EXTRA_NETWORK_TEMPLATE); + + mTitle.setText(pm.getNameForUid(mUid)); + + // enable settings button when package provides it + // TODO: target torwards entire UID instead of just first package + final String[] packageNames = pm.getPackagesForUid(mUid); + if (packageNames != null && packageNames.length > 0) { + mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE); + mAppSettingsIntent.setPackage(packageNames[0]); + mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT); + + final boolean matchFound = pm.resolveActivity(mAppSettingsIntent, 0) != null; + mAppSettings.setEnabled(matchFound); + + } else { + mAppSettingsIntent = null; + mAppSettings.setEnabled(false); + } + + try { + // load stats for current uid and template + // TODO: read template from extras + mHistory = mStatsService.getHistoryForUid(mTemplate, mUid, NetworkStats.TAG_NONE); + } catch (RemoteException e) { + // since we can't do much without history, and we don't want to + // leave with half-baked UI, we bail hard. + throw new RuntimeException("problem reading network stats", e); + } + + // bind chart to historical stats + mChart.bindNetworkStats(mHistory); + + // show entire history known + final long[] bounds = getHistoryBounds(mHistory); + mChart.setVisibleRange(bounds[0], bounds[1] + DateUtils.WEEK_IN_MILLIS, bounds[1]); + updateDetailData(); + + final Context context = getActivity(); + if (NetworkPolicyManager.isUidValidForPolicy(context, mUid)) { + mRestrictBackgroundView.setVisibility(View.VISIBLE); + + final int uidPolicy; + try { + uidPolicy = mPolicyService.getUidPolicy(mUid); + } catch (RemoteException e) { + // since we can't do much without policy, we bail hard. + throw new RuntimeException("problem reading network policy", e); + } + + // update policy checkbox + final boolean restrictBackground = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0; + mRestrictBackground.setChecked(restrictBackground); + + // kick preference views so they rebind from changes above + refreshPreferenceViews(); + + } else { + mRestrictBackgroundView.setVisibility(View.GONE); + } + } + + private void updateDetailData() { + if (LOGD) Log.d(TAG, "updateDetailData()"); + + final Context context = mChart.getContext(); + final long[] range = mChart.getInspectRange(); + final long[] total = mHistory.getTotalData(range[0], range[1], null); + final long totalCombined = total[0] + total[1]; + mText1.setText(Formatter.formatFileSize(context, totalCombined)); + } + + private void setRestrictBackground(boolean restrictBackground) { + if (LOGD) Log.d(TAG, "setRestrictBackground()"); + try { + mPolicyService.setUidPolicy( + mUid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE); + } catch (RemoteException e) { + throw new RuntimeException("unable to save policy", e); + } + + mRestrictBackground.setChecked(restrictBackground); + refreshPreferenceViews(); + } + + /** + * Force rebind of hijacked {@link Preference} views. + */ + private void refreshPreferenceViews() { + mRestrictBackgroundView = mRestrictBackground.getView(mRestrictBackgroundView, mSwitches); + } + + private DataUsageChartListener mChartListener = new DataUsageChartListener() { + /** {@inheritDoc} */ + public void onInspectRangeChanged() { + if (LOGD) Log.d(TAG, "onInspectRangeChanged()"); + updateDetailData(); + } + + /** {@inheritDoc} */ + public void onWarningChanged() { + // ignored + } + + /** {@inheritDoc} */ + public void onLimitChanged() { + // ignored + } + }; + + private OnClickListener mAppSettingsListener = new OnClickListener() { + /** {@inheritDoc} */ + public void onClick(View v) { + // TODO: target torwards entire UID instead of just first package + startActivity(mAppSettingsIntent); + } + }; + + private OnClickListener mRestrictBackgroundListener = new OnClickListener() { + /** {@inheritDoc} */ + public void onClick(View v) { + final boolean restrictBackground = !mRestrictBackground.isChecked(); + + if (restrictBackground) { + // enabling restriction; show confirmation dialog which + // eventually calls setRestrictBackground() once user confirms. + ConfirmRestrictFragment.show(DataUsageAppDetail.this); + } else { + setRestrictBackground(false); + } + } + }; + + /** + * Dialog to request user confirmation before setting + * {@link #POLICY_REJECT_METERED_BACKGROUND}. + */ + public static class ConfirmRestrictFragment extends DialogFragment { + public static void show(DataUsageAppDetail parent) { + final ConfirmRestrictFragment dialog = new ConfirmRestrictFragment(); + dialog.setTargetFragment(parent, 0); + dialog.show(parent.getFragmentManager(), TAG_CONFIRM_RESTRICT); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Context context = getActivity(); + + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.data_usage_app_restrict_dialog_title); + builder.setMessage(R.string.data_usage_app_restrict_dialog); + + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + final DataUsageAppDetail target = (DataUsageAppDetail) getTargetFragment(); + if (target != null) { + target.setRestrictBackground(true); + } + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + + return builder.create(); + } + } + +} diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java new file mode 100644 index 0000000..ef2282a --- /dev/null +++ b/src/com/android/settings/DataUsageSummary.java @@ -0,0 +1,1090 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings; + +import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.ConnectivityManager.TYPE_WIMAX; +import static android.net.NetworkPolicy.LIMIT_DISABLED; +import static android.net.NetworkPolicyManager.ACTION_DATA_USAGE_LIMIT; +import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE; +import static android.net.NetworkPolicyManager.computeLastCycleBoundary; +import static android.net.NetworkPolicyManager.computeNextCycleBoundary; +import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER; +import static android.net.NetworkTemplate.MATCH_MOBILE_4G; +import static android.net.NetworkTemplate.MATCH_MOBILE_ALL; +import static android.net.NetworkTemplate.MATCH_WIFI; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.Fragment; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.net.ConnectivityManager; +import android.net.INetworkPolicyManager; +import android.net.INetworkStatsService; +import android.net.NetworkPolicy; +import android.net.NetworkPolicyManager; +import android.net.NetworkStats; +import android.net.NetworkStatsHistory; +import android.net.NetworkTemplate; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.text.format.DateUtils; +import android.text.format.Formatter; +import android.text.format.Time; +import android.util.Log; +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.AbsListView; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.ArrayAdapter; +import android.widget.BaseAdapter; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.NumberPicker; +import android.widget.Spinner; +import android.widget.Switch; +import android.widget.TabHost; +import android.widget.TabHost.OnTabChangeListener; +import android.widget.TabHost.TabContentFactory; +import android.widget.TabHost.TabSpec; +import android.widget.TabWidget; +import android.widget.TextView; + +import com.android.internal.telephony.Phone; +import com.android.settings.net.NetworkPolicyEditor; +import com.android.settings.widget.DataUsageChartView; +import com.android.settings.widget.DataUsageChartView.DataUsageChartListener; +import com.google.android.collect.Lists; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Locale; + +/** + * Panel show data usage history across various networks, including options to + * inspect based on usage cycle and control through {@link NetworkPolicy}. + */ +public class DataUsageSummary extends Fragment { + private static final String TAG = "DataUsage"; + private static final boolean LOGD = true; + + private static final int TEMPLATE_INVALID = -1; + + private static final String TAB_3G = "3g"; + private static final String TAB_4G = "4g"; + private static final String TAB_MOBILE = "mobile"; + private static final String TAB_WIFI = "wifi"; + + private static final String TAG_CONFIRM_LIMIT = "confirmLimit"; + private static final String TAG_CYCLE_EDITOR = "cycleEditor"; + private static final String TAG_POLICY_LIMIT = "policyLimit"; + + private static final long KB_IN_BYTES = 1024; + private static final long MB_IN_BYTES = KB_IN_BYTES * 1024; + private static final long GB_IN_BYTES = MB_IN_BYTES * 1024; + + private INetworkStatsService mStatsService; + private INetworkPolicyManager mPolicyService; + private ConnectivityManager mConnService; + + private static final String PREF_FILE = "data_usage"; + private static final String PREF_SHOW_WIFI = "show_wifi"; + + private SharedPreferences mPrefs; + + private TabHost mTabHost; + private TabWidget mTabWidget; + private ListView mListView; + private DataUsageAdapter mAdapter; + + private View mHeader; + private LinearLayout mSwitches; + + private Switch mDataEnabled; + private CheckBox mDisableAtLimit; + private View mDataEnabledView; + private View mDisableAtLimitView; + + private DataUsageChartView mChart; + + private Spinner mCycleSpinner; + private CycleAdapter mCycleAdapter; + + private boolean mShowWifi = false; + + private NetworkTemplate mTemplate = null; + + private NetworkPolicyEditor mPolicyEditor; + private NetworkStatsHistory mHistory; + + private String mIntentTab = null; + + /** Flag used to ignore listeners during binding. */ + private boolean mBinding; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mStatsService = INetworkStatsService.Stub.asInterface( + ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); + mPolicyService = INetworkPolicyManager.Stub.asInterface( + ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); + mConnService = (ConnectivityManager) getActivity().getSystemService( + Context.CONNECTIVITY_SERVICE); + mPrefs = getActivity().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); + + mPolicyEditor = new NetworkPolicyEditor(mPolicyService); + mPolicyEditor.read(); + + mShowWifi = mPrefs.getBoolean(PREF_SHOW_WIFI, false); + + setHasOptionsMenu(true); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + final Context context = inflater.getContext(); + final View view = inflater.inflate(R.layout.data_usage_summary, container, false); + + mTabHost = (TabHost) view.findViewById(android.R.id.tabhost); + mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs); + mListView = (ListView) view.findViewById(android.R.id.list); + + mTabHost.setup(); + mTabHost.setOnTabChangedListener(mTabListener); + + mHeader = inflater.inflate(R.layout.data_usage_header, mListView, false); + mListView.addHeaderView(mHeader, null, false); + + mDataEnabled = new Switch(inflater.getContext()); + mDataEnabledView = inflatePreference(inflater, mSwitches, mDataEnabled); + mDataEnabled.setOnCheckedChangeListener(mDataEnabledListener); + + mDisableAtLimit = new CheckBox(inflater.getContext()); + mDisableAtLimitView = inflatePreference(inflater, mSwitches, mDisableAtLimit); + mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener); + + mSwitches = (LinearLayout) mHeader.findViewById(R.id.switches); + mSwitches.addView(mDataEnabledView); + mSwitches.addView(mDisableAtLimitView); + + mCycleSpinner = (Spinner) mHeader.findViewById(R.id.cycles); + mCycleAdapter = new CycleAdapter(context); + mCycleSpinner.setAdapter(mCycleAdapter); + mCycleSpinner.setOnItemSelectedListener(mCycleListener); + + final int chartHeight = getResources().getDimensionPixelSize( + R.dimen.data_usage_chart_height); + mChart = new DataUsageChartView(context); + mChart.setListener(mChartListener); + mChart.setLayoutParams(new AbsListView.LayoutParams(MATCH_PARENT, chartHeight)); + mListView.addHeaderView(mChart, null, false); + + mAdapter = new DataUsageAdapter(); + mListView.setOnItemClickListener(mListListener); + mListView.setAdapter(mAdapter); + + return view; + } + + @Override + public void onResume() { + super.onResume(); + + // pick default tab based on incoming intent + final Intent intent = getActivity().getIntent(); + mIntentTab = computeTabFromIntent(intent); + + // this kicks off chain reaction which creates tabs, binds the body to + // selected network, and binds chart, cycles and detail list. + updateTabs(); + + // template and tab has been selected; show dialog if limit passed + final String action = intent.getAction(); + if (ACTION_DATA_USAGE_LIMIT.equals(action)) { + PolicyLimitFragment.show(this); + } + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.data_usage, menu); + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + final Context context = getActivity(); + + final MenuItem split4g = menu.findItem(R.id.action_split_4g); + split4g.setVisible(hasMobile4gRadio(context)); + split4g.setChecked(isMobilePolicySplit()); + + final MenuItem showWifi = menu.findItem(R.id.action_show_wifi); + showWifi.setVisible(hasMobileRadio(context) && hasWifiRadio(context)); + showWifi.setChecked(mShowWifi); + + final MenuItem settings = menu.findItem(R.id.action_settings); + settings.setVisible(split4g.isVisible() || showWifi.isVisible()); + + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_split_4g: { + final boolean mobileSplit = !item.isChecked(); + setMobilePolicySplit(mobileSplit); + item.setChecked(isMobilePolicySplit()); + updateTabs(); + return true; + } + case R.id.action_show_wifi: { + mShowWifi = !item.isChecked(); + mPrefs.edit().putBoolean(PREF_SHOW_WIFI, mShowWifi).apply(); + item.setChecked(mShowWifi); + updateTabs(); + return true; + } + } + return false; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + + mDataEnabledView = null; + mDisableAtLimitView = null; + } + + /** + * Rebuild all tabs based on {@link NetworkPolicyEditor} and + * {@link #mShowWifi}, hiding the tabs entirely when applicable. Selects + * first tab, and kicks off a full rebind of body contents. + */ + private void updateTabs() { + final Context context = getActivity(); + mTabHost.clearAllTabs(); + + final boolean mobileSplit = isMobilePolicySplit(); + if (mobileSplit && hasMobile4gRadio(context)) { + mTabHost.addTab(buildTabSpec(TAB_3G, R.string.data_usage_tab_3g)); + mTabHost.addTab(buildTabSpec(TAB_4G, R.string.data_usage_tab_4g)); + } + + if (mShowWifi && hasWifiRadio(context) && hasMobileRadio(context)) { + if (!mobileSplit) { + mTabHost.addTab(buildTabSpec(TAB_MOBILE, R.string.data_usage_tab_mobile)); + } + mTabHost.addTab(buildTabSpec(TAB_WIFI, R.string.data_usage_tab_wifi)); + } + + final boolean hasTabs = mTabWidget.getTabCount() > 0; + mTabWidget.setVisibility(hasTabs ? View.VISIBLE : View.GONE); + if (hasTabs) { + if (mIntentTab != null) { + // select default tab, which will kick off updateBody() + mTabHost.setCurrentTabByTag(mIntentTab); + } else { + // select first tab, which will kick off updateBody() + mTabHost.setCurrentTab(0); + } + } else { + // no tabs visible; update body manually + updateBody(); + } + } + + /** + * Factory that provide empty {@link View} to make {@link TabHost} happy. + */ + private TabContentFactory mEmptyTabContent = new TabContentFactory() { + /** {@inheritDoc} */ + public View createTabContent(String tag) { + return new View(mTabHost.getContext()); + } + }; + + /** + * Build {@link TabSpec} with thin indicator, and empty content. + */ + private TabSpec buildTabSpec(String tag, int titleRes) { + final LayoutInflater inflater = LayoutInflater.from(mTabWidget.getContext()); + final View indicator = inflater.inflate( + R.layout.tab_indicator_thin_holo, mTabWidget, false); + final TextView title = (TextView) indicator.findViewById(android.R.id.title); + title.setText(titleRes); + return mTabHost.newTabSpec(tag).setIndicator(indicator).setContent(mEmptyTabContent); + } + + private OnTabChangeListener mTabListener = new OnTabChangeListener() { + /** {@inheritDoc} */ + public void onTabChanged(String tabId) { + // user changed tab; update body + updateBody(); + } + }; + + /** + * Update body content based on current tab. Loads + * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and + * binds them to visible controls. + */ + private void updateBody() { + mBinding = true; + + final Context context = getActivity(); + final String tabTag = mTabHost.getCurrentTabTag(); + + final String currentTab; + if (tabTag != null) { + currentTab = tabTag; + } else if (hasMobileRadio(context)) { + currentTab = TAB_MOBILE; + } else if (hasWifiRadio(context)) { + currentTab = TAB_WIFI; + } else { + throw new IllegalStateException("no mobile or wifi radios"); + } + + if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab); + + if (TAB_WIFI.equals(currentTab)) { + // wifi doesn't have any controls + mDataEnabledView.setVisibility(View.GONE); + mDisableAtLimitView.setVisibility(View.GONE); + mTemplate = new NetworkTemplate(MATCH_WIFI, null); + + } else { + // make sure we show for non-wifi + mDataEnabledView.setVisibility(View.VISIBLE); + mDisableAtLimitView.setVisibility(View.VISIBLE); + } + + final String subscriberId = getActiveSubscriberId(context); + if (TAB_MOBILE.equals(currentTab)) { + setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_mobile); + setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_mobile_limit); + mDataEnabled.setChecked(mConnService.getMobileDataEnabled()); + mTemplate = new NetworkTemplate(MATCH_MOBILE_ALL, subscriberId); + + } else if (TAB_3G.equals(currentTab)) { + setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_3g); + setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_3g_limit); + // TODO: bind mDataEnabled to 3G radio state + mTemplate = new NetworkTemplate(MATCH_MOBILE_3G_LOWER, subscriberId); + + } else if (TAB_4G.equals(currentTab)) { + setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_4g); + setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_4g_limit); + // TODO: bind mDataEnabled to 4G radio state + mTemplate = new NetworkTemplate(MATCH_MOBILE_4G, subscriberId); + } + + try { + // load stats for current template + mHistory = mStatsService.getHistoryForNetwork(mTemplate); + } catch (RemoteException e) { + // since we can't do much without policy or history, and we don't + // want to leave with half-baked UI, we bail hard. + throw new RuntimeException("problem reading network policy or stats", e); + } + + // bind chart to historical stats + mChart.bindNetworkStats(mHistory); + + updatePolicy(true); + + // force scroll to top of body + mListView.smoothScrollToPosition(0); + + mBinding = false; + } + + private void setPolicyCycleDay(int cycleDay) { + if (LOGD) Log.d(TAG, "setPolicyCycleDay()"); + mPolicyEditor.setPolicyCycleDay(mTemplate, cycleDay); + updatePolicy(true); + } + + private void setPolicyWarningBytes(long warningBytes) { + if (LOGD) Log.d(TAG, "setPolicyWarningBytes()"); + mPolicyEditor.setPolicyWarningBytes(mTemplate, warningBytes); + updatePolicy(false); + } + + private void setPolicyLimitBytes(long limitBytes) { + if (LOGD) Log.d(TAG, "setPolicyLimitBytes()"); + mPolicyEditor.setPolicyLimitBytes(mTemplate, limitBytes); + updatePolicy(false); + } + + /** + * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for + * current {@link #mTemplate}. + */ + private void updatePolicy(boolean refreshCycle) { + final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate); + + // reflect policy limit in checkbox + mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED); + mChart.bindNetworkPolicy(policy); + + if (refreshCycle) { + // generate cycle list based on policy and available history + updateCycleList(policy); + } + } + + /** + * Return full time bounds (earliest and latest time recorded) of the given + * {@link NetworkStatsHistory}. + */ + public static long[] getHistoryBounds(NetworkStatsHistory history) { + final long currentTime = System.currentTimeMillis(); + + long start = currentTime; + long end = currentTime; + if (history.bucketCount > 0) { + start = history.bucketStart[0]; + end = history.bucketStart[history.bucketCount - 1]; + } + + return new long[] { start, end }; + } + + /** + * Rebuild {@link #mCycleAdapter} based on {@link NetworkPolicy#cycleDay} + * and available {@link NetworkStatsHistory} data. Always selects the newest + * item, updating the inspection range on {@link #mChart}. + */ + private void updateCycleList(NetworkPolicy policy) { + mCycleAdapter.clear(); + + final Context context = mCycleSpinner.getContext(); + + final long[] bounds = getHistoryBounds(mHistory); + final long historyStart = bounds[0]; + final long historyEnd = bounds[1]; + + if (policy != null) { + // find the next cycle boundary + long cycleEnd = computeNextCycleBoundary(historyEnd, policy); + + int guardCount = 0; + + // walk backwards, generating all valid cycle ranges + while (cycleEnd > historyStart) { + final long cycleStart = computeLastCycleBoundary(cycleEnd, policy); + Log.d(TAG, "generating cs=" + cycleStart + " to ce=" + cycleEnd + " waiting for hs=" + + historyStart); + mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd)); + cycleEnd = cycleStart; + + // TODO: remove this guard once we have better testing + if (guardCount++ > 50) { + Log.wtf(TAG, "stuck generating ranges for bounds=" + Arrays.toString(bounds) + + " and policy=" + policy); + } + } + + // one last cycle entry to modify policy cycle day + mCycleAdapter.add(new CycleChangeItem(context)); + + } else { + // no valid cycle; show all data + // TODO: offer simple ranges like "last week" etc + mCycleAdapter.add(new CycleItem(context, historyStart, historyEnd)); + + } + + // force pick the current cycle (first item) + mCycleSpinner.setSelection(0); + mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0); + } + + private OnCheckedChangeListener mDataEnabledListener = new OnCheckedChangeListener() { + /** {@inheritDoc} */ + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (mBinding) return; + + final boolean dataEnabled = isChecked; + mDataEnabled.setChecked(dataEnabled); + + switch (mTemplate.getMatchRule()) { + case MATCH_MOBILE_ALL: { + mConnService.setMobileDataEnabled(dataEnabled); + } + } + } + }; + + private View.OnClickListener mDisableAtLimitListener = new View.OnClickListener() { + /** {@inheritDoc} */ + public void onClick(View v) { + final boolean disableAtLimit = !mDisableAtLimit.isChecked(); + if (disableAtLimit) { + // enabling limit; show confirmation dialog which eventually + // calls setPolicyLimitBytes() once user confirms. + ConfirmLimitFragment.show(DataUsageSummary.this); + } else { + setPolicyLimitBytes(LIMIT_DISABLED); + } + } + }; + + private OnItemClickListener mListListener = new OnItemClickListener() { + /** {@inheritDoc} */ + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position); + + final Bundle args = new Bundle(); + args.putParcelable(DataUsageAppDetail.EXTRA_NETWORK_TEMPLATE, mTemplate); + args.putInt(DataUsageAppDetail.EXTRA_UID, app.uid); + + final PreferenceActivity activity = (PreferenceActivity) getActivity(); + activity.startPreferencePanel(DataUsageAppDetail.class.getName(), args, + R.string.data_usage_summary_title, null, null, 0); + } + }; + + private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() { + /** {@inheritDoc} */ + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + final CycleItem cycle = (CycleItem) parent.getItemAtPosition(position); + if (cycle instanceof CycleChangeItem) { + // show cycle editor; will eventually call setPolicyCycleDay() + // when user finishes editing. + CycleEditorFragment.show(DataUsageSummary.this); + + // reset spinner to something other than "change cycle..." + mCycleSpinner.setSelection(0); + + } else { + if (LOGD) { + Log.d(TAG, "showing cycle " + cycle + ", start=" + cycle.start + ", end=" + + cycle.end + "]"); + } + + // update chart to show selected cycle, and update detail data + // to match updated sweep bounds. + final long[] bounds = getHistoryBounds(mHistory); + mChart.setVisibleRange(cycle.start, cycle.end, bounds[1]); + + updateDetailData(); + } + } + + /** {@inheritDoc} */ + public void onNothingSelected(AdapterView<?> parent) { + // ignored + } + }; + + /** + * Update {@link #mAdapter} with sorted list of applications data usage, + * based on current inspection from {@link #mChart}. + */ + private void updateDetailData() { + if (LOGD) Log.d(TAG, "updateDetailData()"); + + new AsyncTask<Void, Void, NetworkStats>() { + @Override + protected NetworkStats doInBackground(Void... params) { + try { + final long[] range = mChart.getInspectRange(); + return mStatsService.getSummaryForAllUid(mTemplate, range[0], range[1], false); + } catch (RemoteException e) { + Log.w(TAG, "problem reading stats"); + } + return null; + } + + @Override + protected void onPostExecute(NetworkStats stats) { + if (stats != null) { + mAdapter.bindStats(stats); + } + } + }.execute(); + } + + private boolean isMobilePolicySplit() { + final String subscriberId = getActiveSubscriberId(getActivity()); + return mPolicyEditor.isMobilePolicySplit(subscriberId); + } + + private void setMobilePolicySplit(boolean split) { + final String subscriberId = getActiveSubscriberId(getActivity()); + mPolicyEditor.setMobilePolicySplit(subscriberId, split); + } + + private static String getActiveSubscriberId(Context context) { + final TelephonyManager telephony = (TelephonyManager) context.getSystemService( + Context.TELEPHONY_SERVICE); + return telephony.getSubscriberId(); + } + + private DataUsageChartListener mChartListener = new DataUsageChartListener() { + /** {@inheritDoc} */ + public void onInspectRangeChanged() { + if (LOGD) Log.d(TAG, "onInspectRangeChanged()"); + updateDetailData(); + } + + /** {@inheritDoc} */ + public void onWarningChanged() { + setPolicyWarningBytes(mChart.getWarningBytes()); + } + + /** {@inheritDoc} */ + public void onLimitChanged() { + setPolicyLimitBytes(mChart.getLimitBytes()); + } + }; + + + /** + * List item that reflects a specific data usage cycle. + */ + public static class CycleItem { + public CharSequence label; + public long start; + public long end; + + private static final StringBuilder sBuilder = new StringBuilder(50); + private static final java.util.Formatter sFormatter = new java.util.Formatter( + sBuilder, Locale.getDefault()); + + CycleItem(CharSequence label) { + this.label = label; + } + + public CycleItem(Context context, long start, long end) { + this.label = formatDateRangeUtc(context, start, end); + this.start = start; + this.end = end; + } + + private static String formatDateRangeUtc(Context context, long start, long end) { + synchronized (sBuilder) { + sBuilder.setLength(0); + return DateUtils.formatDateRange(context, sFormatter, start, end, + DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH, + Time.TIMEZONE_UTC).toString(); + } + } + + @Override + public String toString() { + return label.toString(); + } + } + + /** + * Special-case data usage cycle that triggers dialog to change + * {@link NetworkPolicy#cycleDay}. + */ + public static class CycleChangeItem extends CycleItem { + public CycleChangeItem(Context context) { + super(context.getString(R.string.data_usage_change_cycle)); + } + } + + public static class CycleAdapter extends ArrayAdapter<CycleItem> { + public CycleAdapter(Context context) { + super(context, android.R.layout.simple_spinner_item); + setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + } + } + + private static class AppUsageItem implements Comparable<AppUsageItem> { + public int uid; + public long total; + + /** {@inheritDoc} */ + public int compareTo(AppUsageItem another) { + return Long.compare(another.total, total); + } + } + + /** + * Adapter of applications, sorted by total usage descending. + */ + public static class DataUsageAdapter extends BaseAdapter { + private ArrayList<AppUsageItem> mItems = Lists.newArrayList(); + + public void bindStats(NetworkStats stats) { + mItems.clear(); + + for (int i = 0; i < stats.size; i++) { + final long total = stats.rx[i] + stats.tx[i]; + final AppUsageItem item = new AppUsageItem(); + item.uid = stats.uid[i]; + item.total = total; + mItems.add(item); + } + + Collections.sort(mItems); + notifyDataSetChanged(); + } + + @Override + public int getCount() { + return mItems.size(); + } + + @Override + public Object getItem(int position) { + return mItems.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = LayoutInflater.from(parent.getContext()).inflate( + android.R.layout.simple_list_item_2, parent, false); + } + + final Context context = parent.getContext(); + final PackageManager pm = context.getPackageManager(); + + final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1); + final TextView text2 = (TextView) convertView.findViewById(android.R.id.text2); + + final AppUsageItem item = mItems.get(position); + text1.setText(resolveLabelForUid(pm, item.uid)); + text2.setText(Formatter.formatFileSize(context, item.total)); + + return convertView; + } + + } + + /** + * Dialog to request user confirmation before setting + * {@link NetworkPolicy#limitBytes}. + */ + public static class ConfirmLimitFragment extends DialogFragment { + public static final String EXTRA_MESSAGE_ID = "messageId"; + public static final String EXTRA_LIMIT_BYTES = "limitBytes"; + + public static void show(DataUsageSummary parent) { + final Bundle args = new Bundle(); + + // TODO: customize default limits based on network template + switch (parent.mTemplate.getMatchRule()) { + case MATCH_MOBILE_3G_LOWER: { + args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_3g); + args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES); + break; + } + case MATCH_MOBILE_4G: { + args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_4g); + args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES); + break; + } + case MATCH_MOBILE_ALL: { + args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_mobile); + args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES); + break; + } + } + + final ConfirmLimitFragment dialog = new ConfirmLimitFragment(); + dialog.setArguments(args); + dialog.setTargetFragment(parent, 0); + dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Context context = getActivity(); + + final int messageId = getArguments().getInt(EXTRA_MESSAGE_ID); + final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES); + + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.data_usage_limit_dialog_title); + builder.setMessage(messageId); + + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); + if (target != null) { + target.setPolicyLimitBytes(limitBytes); + } + } + }); + + return builder.create(); + } + } + + /** + * Dialog to edit {@link NetworkPolicy#cycleDay}. + */ + public static class CycleEditorFragment extends DialogFragment { + public static final String EXTRA_CYCLE_DAY = "cycleDay"; + + public static void show(DataUsageSummary parent) { + final NetworkPolicy policy = parent.mPolicyEditor.getPolicy(parent.mTemplate); + final Bundle args = new Bundle(); + args.putInt(CycleEditorFragment.EXTRA_CYCLE_DAY, policy.cycleDay); + + final CycleEditorFragment dialog = new CycleEditorFragment(); + dialog.setArguments(args); + dialog.setTargetFragment(parent, 0); + dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Context context = getActivity(); + + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext()); + + final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false); + final NumberPicker cycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day); + + final int oldCycleDay = getArguments().getInt(EXTRA_CYCLE_DAY, 1); + + cycleDayPicker.setMinValue(1); + cycleDayPicker.setMaxValue(31); + cycleDayPicker.setValue(oldCycleDay); + cycleDayPicker.setWrapSelectorWheel(true); + + builder.setTitle(R.string.data_usage_cycle_editor_title); + builder.setView(view); + + builder.setPositiveButton(R.string.data_usage_cycle_editor_positive, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + final int cycleDay = cycleDayPicker.getValue(); + final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); + if (target != null) { + target.setPolicyCycleDay(cycleDay); + } + } + }); + + return builder.create(); + } + } + + /** + * Dialog explaining that {@link NetworkPolicy#limitBytes} has been passed, + * and giving the user an option to bypass. + */ + public static class PolicyLimitFragment extends DialogFragment { + public static final String EXTRA_TITLE_ID = "titleId"; + + public static void show(DataUsageSummary parent) { + final Bundle args = new Bundle(); + + switch (parent.mTemplate.getMatchRule()) { + case MATCH_MOBILE_3G_LOWER: { + args.putInt(EXTRA_TITLE_ID, R.string.data_usage_disabled_dialog_3g_title); + break; + } + case MATCH_MOBILE_4G: { + args.putInt(EXTRA_TITLE_ID, R.string.data_usage_disabled_dialog_4g_title); + break; + } + case MATCH_MOBILE_ALL: { + args.putInt(EXTRA_TITLE_ID, R.string.data_usage_disabled_dialog_mobile_title); + break; + } + } + + final PolicyLimitFragment dialog = new PolicyLimitFragment(); + dialog.setArguments(args); + dialog.setTargetFragment(parent, 0); + dialog.show(parent.getFragmentManager(), TAG_POLICY_LIMIT); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Context context = getActivity(); + + final int titleId = getArguments().getInt(EXTRA_TITLE_ID); + + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(titleId); + builder.setMessage(R.string.data_usage_disabled_dialog); + + builder.setPositiveButton(android.R.string.ok, null); + builder.setNegativeButton(R.string.data_usage_disabled_dialog_enable, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); + if (target != null) { + // TODO: consider "allow 100mb more data", or + // only bypass limit for current cycle. + target.setPolicyLimitBytes(LIMIT_DISABLED); + } + } + }); + + return builder.create(); + } + } + + /** + * Compute default tab that should be selected, based on + * {@link NetworkPolicyManager#EXTRA_NETWORK_TEMPLATE} extra. + */ + private static String computeTabFromIntent(Intent intent) { + final int networkTemplate = intent.getIntExtra(EXTRA_NETWORK_TEMPLATE, TEMPLATE_INVALID); + switch (networkTemplate) { + case MATCH_MOBILE_3G_LOWER: + return TAB_3G; + case MATCH_MOBILE_4G: + return TAB_4G; + case MATCH_MOBILE_ALL: + return TAB_MOBILE; + case MATCH_WIFI: + return TAB_WIFI; + default: + return null; + } + } + + /** + * Resolve best descriptive label for the given UID. + */ + public static CharSequence resolveLabelForUid(PackageManager pm, int uid) { + final String[] packageNames = pm.getPackagesForUid(uid); + final int length = packageNames != null ? packageNames.length : 0; + + CharSequence label = pm.getNameForUid(uid); + try { + if (length == 1) { + final ApplicationInfo info = pm.getApplicationInfo(packageNames[0], 0); + label = info.loadLabel(pm); + } else if (length > 1) { + for (String packageName : packageNames) { + final PackageInfo info = pm.getPackageInfo(packageName, 0); + if (info.sharedUserLabel != 0) { + label = pm.getText(packageName, info.sharedUserLabel, info.applicationInfo); + if (!TextUtils.isEmpty(label)) { + break; + } + } + } + } + } catch (NameNotFoundException e) { + } + + if (TextUtils.isEmpty(label)) { + label = Integer.toString(uid); + } + return label; + } + + /** + * Test if device has a mobile data radio. + */ + private static boolean hasMobileRadio(Context context) { + final ConnectivityManager conn = (ConnectivityManager) context.getSystemService( + Context.CONNECTIVITY_SERVICE); + + // mobile devices should have MOBILE network tracker regardless of + // connection status. + return conn.getNetworkInfo(TYPE_MOBILE) != null; + } + + /** + * Test if device has a mobile 4G data radio. + */ + private static boolean hasMobile4gRadio(Context context) { + final ConnectivityManager conn = (ConnectivityManager) context.getSystemService( + Context.CONNECTIVITY_SERVICE); + final TelephonyManager telephony = (TelephonyManager) context.getSystemService( + Context.TELEPHONY_SERVICE); + + // WiMAX devices should have WiMAX network tracker regardless of + // connection status. + final boolean hasWimax = conn.getNetworkInfo(TYPE_WIMAX) != null; + final boolean hasLte = telephony.getLteOnCdmaMode() == Phone.LTE_ON_CDMA_TRUE; + return hasWimax || hasLte; + } + + /** + * Test if device has a Wi-Fi data radio. + */ + private static boolean hasWifiRadio(Context context) { + return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI); + } + + /** + * Inflate a {@link Preference} style layout, adding the given {@link View} + * widget into {@link android.R.id#widget_frame}. + */ + private static View inflatePreference(LayoutInflater inflater, ViewGroup root, View widget) { + final View view = inflater.inflate(R.layout.preference, root, false); + final LinearLayout widgetFrame = (LinearLayout) view.findViewById( + android.R.id.widget_frame); + widgetFrame.addView(widget, new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + return view; + } + + /** + * Set {@link android.R.id#title} for a preference view inflated with + * {@link #inflatePreference(LayoutInflater, View, View)}. + */ + private static void setPreferenceTitle(View parent, int resId) { + final TextView title = (TextView) parent.findViewById(android.R.id.title); + title.setText(resId); + } + +} diff --git a/src/com/android/settings/DateTimeSettingsSetupWizard.java b/src/com/android/settings/DateTimeSettingsSetupWizard.java index 4ff7fc8..e63153e 100644 --- a/src/com/android/settings/DateTimeSettingsSetupWizard.java +++ b/src/com/android/settings/DateTimeSettingsSetupWizard.java @@ -31,7 +31,6 @@ import android.preference.Preference; import android.preference.PreferenceFragment; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; -import android.text.format.DateFormat; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; @@ -46,6 +45,7 @@ import android.widget.DatePicker; import android.widget.LinearLayout; import android.widget.ListPopupWindow; import android.widget.SimpleAdapter; +import android.widget.TextView; import android.widget.TimePicker; import java.util.Calendar; @@ -136,8 +136,6 @@ public class DateTimeSettingsSetupWizard extends Activity mAutoDateTimeButton = (CompoundButton)findViewById(R.id.date_time_auto_button); mAutoDateTimeButton.setChecked(autoDateTimeEnabled); - mAutoDateTimeButton.setText(autoDateTimeEnabled ? R.string.date_time_auto_summaryOn : - R.string.date_time_auto_summaryOff); mAutoDateTimeButton.setOnCheckedChangeListener(this); mTimePicker = (TimePicker)findViewById(R.id.time_picker); diff --git a/src/com/android/settings/DefaultRingtonePreference.java b/src/com/android/settings/DefaultRingtonePreference.java index 0933d62..0801b1f 100644 --- a/src/com/android/settings/DefaultRingtonePreference.java +++ b/src/com/android/settings/DefaultRingtonePreference.java @@ -23,7 +23,6 @@ import android.media.RingtoneManager; import android.net.Uri; import android.preference.RingtonePreference; import android.util.AttributeSet; -import android.util.Config; import android.util.Log; public class DefaultRingtonePreference extends RingtonePreference { diff --git a/src/com/android/settings/DisplaySettings.java b/src/com/android/settings/DisplaySettings.java index cdb0147..682184e 100644 --- a/src/com/android/settings/DisplaySettings.java +++ b/src/com/android/settings/DisplaySettings.java @@ -18,9 +18,17 @@ package com.android.settings; import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; +import android.app.ActivityManagerNative; import android.app.admin.DevicePolicyManager; import android.content.ContentResolver; import android.content.Context; +import android.content.res.Configuration; + +import android.app.ActivityManagerNative; +import android.app.admin.DevicePolicyManager; +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Configuration; import android.database.ContentObserver; import android.os.Bundle; import android.os.Handler; @@ -46,11 +54,15 @@ public class DisplaySettings extends SettingsPreferenceFragment implements private static final String KEY_SCREEN_TIMEOUT = "screen_timeout"; private static final String KEY_ANIMATIONS = "animations"; private static final String KEY_ACCELEROMETER = "accelerometer"; + private static final String KEY_FONT_SIZE = "font_size"; private ListPreference mAnimations; private CheckBoxPreference mAccelerometer; private float[] mAnimationScales; + private ListPreference mFontSizePref; + private final Configuration mCurConfig = new Configuration(); + private IWindowManager mWindowManager; private ListPreference mScreenTimeoutPreference; @@ -70,6 +82,12 @@ public class DisplaySettings extends SettingsPreferenceFragment implements addPreferencesFromResource(R.xml.display_settings); + // Fetch this once before attaching a listener for changes. + try { + mAnimationScales = mWindowManager.getAnimationScales(); + } catch (RemoteException e) { + // Shouldn't happen and not much can be done anyway. + } mAnimations = (ListPreference) findPreference(KEY_ANIMATIONS); mAnimations.setOnPreferenceChangeListener(this); mAccelerometer = (CheckBoxPreference) findPreference(KEY_ACCELEROMETER); @@ -81,22 +99,42 @@ public class DisplaySettings extends SettingsPreferenceFragment implements mScreenTimeoutPreference.setValue(String.valueOf(currentTimeout)); mScreenTimeoutPreference.setOnPreferenceChangeListener(this); disableUnusableTimeouts(mScreenTimeoutPreference); - updateTimeoutPreferenceDescription(resolver, currentTimeout); + updateTimeoutPreferenceDescription(resolver, mScreenTimeoutPreference, + R.string.screen_timeout_summary, currentTimeout); + + mFontSizePref = (ListPreference) findPreference(KEY_FONT_SIZE); + mFontSizePref.setOnPreferenceChangeListener(this); } - private void updateTimeoutPreferenceDescription(ContentResolver resolver, long currentTimeout) { - final CharSequence[] entries = mScreenTimeoutPreference.getEntries(); - final CharSequence[] values = mScreenTimeoutPreference.getEntryValues(); - int best = 0; - for (int i = 0; i < values.length; i++) { - long timeout = Long.valueOf(values[i].toString()); - if (currentTimeout >= timeout) { - best = i; + private void updateTimeoutPreferenceDescription( + ContentResolver resolver, + ListPreference pref, + int summaryStrings, + long currentTimeout) { + updateTimeoutPreferenceDescription(resolver, pref, summaryStrings, 0, currentTimeout); + } + private void updateTimeoutPreferenceDescription( + ContentResolver resolver, + ListPreference pref, + int summaryStrings, + int zeroString, + long currentTimeout) { + String summary; + if (currentTimeout == 0) { + summary = pref.getContext().getString(zeroString); + } else { + final CharSequence[] entries = pref.getEntries(); + final CharSequence[] values = pref.getEntryValues(); + int best = 0; + for (int i = 0; i < values.length; i++) { + long timeout = Long.valueOf(values[i].toString()); + if (currentTimeout >= timeout) { + best = i; + } } + summary = pref.getContext().getString(summaryStrings, entries[best]); } - String summary = mScreenTimeoutPreference.getContext() - .getString(R.string.screen_timeout_summary, entries[best]); - mScreenTimeoutPreference.setSummary(summary); + pref.setSummary(summary); } private void disableUnusableTimeouts(ListPreference screenTimeoutPreference) { @@ -135,6 +173,29 @@ public class DisplaySettings extends SettingsPreferenceFragment implements screenTimeoutPreference.setEnabled(revisedEntries.size() > 0); } + int floatToIndex(float val, int resid) { + String[] indices = getResources().getStringArray(resid); + float lastVal = Float.parseFloat(indices[0]); + for (int i=1; i<indices.length; i++) { + float thisVal = Float.parseFloat(indices[i]); + if (val < (lastVal + (thisVal-lastVal)*.5f)) { + return i-1; + } + lastVal = thisVal; + } + return indices.length-1; + } + + public void readFontSizePreference(ListPreference pref) { + try { + mCurConfig.updateFrom( + ActivityManagerNative.getDefault().getConfiguration()); + } catch (RemoteException e) { + } + pref.setValueIndex(floatToIndex(mCurConfig.fontScale, + R.array.entryvalues_font_size)); + } + @Override public void onResume() { super.onResume(); @@ -157,6 +218,7 @@ public class DisplaySettings extends SettingsPreferenceFragment implements try { mAnimationScales = mWindowManager.getAnimationScales(); } catch (RemoteException e) { + // Shouldn't happen and not much can be done anyway. } if (mAnimationScales != null) { if (mAnimationScales.length >= 1) { @@ -179,6 +241,7 @@ public class DisplaySettings extends SettingsPreferenceFragment implements mAnimations.setValueIndex(idx); updateAnimationsSummary(mAnimations.getValue()); updateAccelerometerRotationCheckbox(); + readFontSizePreference(mFontSizePref); } private void updateAccelerometerRotationCheckbox() { @@ -200,6 +263,14 @@ public class DisplaySettings extends SettingsPreferenceFragment implements } } + public void writeFontSizePreference(Object objValue) { + try { + mCurConfig.fontScale = Float.parseFloat(objValue.toString()); + ActivityManagerNative.getDefault().updateConfiguration(mCurConfig); + } catch (RemoteException e) { + } + } + @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { if (preference == mAccelerometer) { @@ -207,7 +278,7 @@ public class DisplaySettings extends SettingsPreferenceFragment implements Settings.System.ACCELEROMETER_ROTATION, mAccelerometer.isChecked() ? 1 : 0); } - return true; + return super.onPreferenceTreeClick(preferenceScreen, preference); } public boolean onPreferenceChange(Preference preference, Object objValue) { @@ -236,11 +307,15 @@ public class DisplaySettings extends SettingsPreferenceFragment implements try { Settings.System.putInt(getContentResolver(), SCREEN_OFF_TIMEOUT, value); - updateTimeoutPreferenceDescription(getContentResolver(), value); + updateTimeoutPreferenceDescription(getContentResolver(), mScreenTimeoutPreference, + R.string.screen_timeout_summary, value); } catch (NumberFormatException e) { Log.e(TAG, "could not persist screen timeout setting", e); } } + if (KEY_FONT_SIZE.equals(key)) { + writeFontSizePreference(objValue); + } return true; } diff --git a/src/com/android/settings/DreamComponentPreference.java b/src/com/android/settings/DreamComponentPreference.java new file mode 100644 index 0000000..3bc0eb4 --- /dev/null +++ b/src/com/android/settings/DreamComponentPreference.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings; + +import static android.provider.Settings.Secure.DREAM_COMPONENT; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.preference.Preference; +import android.provider.Settings; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +public class DreamComponentPreference extends Preference { + private static final String TAG = "DreamComponentPreference"; + + private final PackageManager pm; + private final ContentResolver resolver; + + public DreamComponentPreference(Context context, AttributeSet attrs) { + super(context, attrs); + pm = getContext().getPackageManager(); + resolver = getContext().getContentResolver(); + + refreshFromSettings(); + } + + private void refreshFromSettings() { + String component = Settings.Secure.getString(resolver, DREAM_COMPONENT); + if (component != null) { + ComponentName cn = ComponentName.unflattenFromString(component); + try { + setSummary(pm.getActivityInfo(cn, 0).loadLabel(pm)); + } catch (PackageManager.NameNotFoundException ex) { + setSummary("(unknown)"); + } + } + } + + public class DreamListAdapter extends BaseAdapter implements ListAdapter { + private ArrayList<ResolveInfo> results; + private final LayoutInflater inflater; + + public DreamListAdapter(Context context) { + Intent choosy = new Intent(Intent.ACTION_MAIN) + .addCategory("android.intent.category.DREAM"); + + inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + results = new ArrayList<ResolveInfo>(pm.queryIntentActivities(choosy, 0)); + } + + @Override + public int getCount() { + return results.size(); + } + + @Override + public Object getItem(int position) { + return results.get(position); + } + + @Override + public long getItemId (int position) { + return (long) position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View row = (convertView != null) + ? convertView + : inflater.inflate(R.layout.dream_picker_row, parent, false); + ResolveInfo ri = results.get(position); + ((TextView)row.findViewById(R.id.title)).setText(ri.loadLabel(pm)); + ((ImageView)row.findViewById(R.id.icon)).setImageDrawable(ri.loadIcon(pm)); + return row; + } + } + + @Override + protected void onClick() { + final DreamListAdapter list = new DreamListAdapter(getContext()); + AlertDialog alert = new AlertDialog.Builder(getContext()) + .setAdapter( + list, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + ResolveInfo ri = (ResolveInfo)list.getItem(which); + ActivityInfo act = ri.activityInfo; + ComponentName cn = new ComponentName( + act.applicationInfo.packageName, + act.name); + Intent intent = new Intent(Intent.ACTION_MAIN).setComponent(cn); + + setSummary(ri.loadLabel(pm)); + //getContext().startActivity(intent); + + Settings.Secure.putString(resolver, DREAM_COMPONENT, cn.flattenToString()); + } + }) + .create(); + alert.show(); + } +} diff --git a/src/com/android/settings/DreamSettings.java b/src/com/android/settings/DreamSettings.java new file mode 100644 index 0000000..7ddab31 --- /dev/null +++ b/src/com/android/settings/DreamSettings.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings; + +import static android.provider.Settings.Secure.DREAM_TIMEOUT; + +import android.app.ActivityManagerNative; +import android.app.admin.DevicePolicyManager; +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Configuration; +import android.database.ContentObserver; +import android.os.Bundle; +import android.os.Handler; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.preference.CheckBoxPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceScreen; +import android.provider.Settings; +import android.util.Log; +import android.view.IWindowManager; + +import java.util.ArrayList; + +public class DreamSettings extends SettingsPreferenceFragment implements + Preference.OnPreferenceChangeListener { + private static final String TAG = "DreamSettings"; + + private static final String KEY_DREAM_TIMEOUT = "dream_timeout"; + + private ListPreference mScreenTimeoutPreference; + private ListPreference mDreamTimeoutPreference; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ContentResolver resolver = getActivity().getContentResolver(); + + addPreferencesFromResource(R.xml.dream_settings); + + mDreamTimeoutPreference = (ListPreference) findPreference(KEY_DREAM_TIMEOUT); + final long currentSaverTimeout = Settings.Secure.getLong(resolver, DREAM_TIMEOUT, + 0); + mDreamTimeoutPreference.setValue(String.valueOf(currentSaverTimeout)); + mDreamTimeoutPreference.setOnPreferenceChangeListener(this); + updateTimeoutPreferenceDescription(resolver, mDreamTimeoutPreference, + R.string.dream_timeout_summary, + R.string.dream_timeout_zero_summary, + currentSaverTimeout); + } + + private void updateTimeoutPreferenceDescription( + ContentResolver resolver, + ListPreference pref, + int summaryStrings, + long currentTimeout) { + updateTimeoutPreferenceDescription(resolver, pref, summaryStrings, 0, currentTimeout); + } + private void updateTimeoutPreferenceDescription( + ContentResolver resolver, + ListPreference pref, + int summaryStrings, + int zeroString, + long currentTimeout) { + String summary; + if (currentTimeout == 0) { + summary = pref.getContext().getString(zeroString); + } else { + final CharSequence[] entries = pref.getEntries(); + final CharSequence[] values = pref.getEntryValues(); + int best = 0; + for (int i = 0; i < values.length; i++) { + long timeout = Long.valueOf(values[i].toString()); + if (currentTimeout >= timeout) { + best = i; + } + } + summary = pref.getContext().getString(summaryStrings, entries[best]); + } + pref.setSummary(summary); + } + + @Override + public void onResume() { + super.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + } + + public boolean onPreferenceChange(Preference preference, Object objValue) { + final String key = preference.getKey(); + if (KEY_DREAM_TIMEOUT.equals(key)) { + int value = Integer.parseInt((String) objValue); + try { + Settings.Secure.putInt(getContentResolver(), + DREAM_TIMEOUT, value); + updateTimeoutPreferenceDescription(getContentResolver(), + mDreamTimeoutPreference, + R.string.dream_timeout_summary, + R.string.dream_timeout_zero_summary, + value); + } catch (NumberFormatException e) { + Log.e(TAG, "could not persist dream timeout setting", e); + } + } + + return true; + } +} diff --git a/src/com/android/settings/DreamTesterPreference.java b/src/com/android/settings/DreamTesterPreference.java new file mode 100644 index 0000000..7ff5667 --- /dev/null +++ b/src/com/android/settings/DreamTesterPreference.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings; + +import static android.provider.Settings.Secure.DREAM_COMPONENT; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.preference.Preference; +import android.provider.Settings; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +public class DreamTesterPreference extends Preference { + private static final String TAG = "DreamTesterPreference"; + + private final PackageManager pm; + private final ContentResolver resolver; + + public DreamTesterPreference(Context context, AttributeSet attrs) { + super(context, attrs); + pm = getContext().getPackageManager(); + resolver = getContext().getContentResolver(); + } + + @Override + protected void onClick() { + String component = Settings.Secure.getString(resolver, DREAM_COMPONENT); + if (component != null) { + ComponentName cn = ComponentName.unflattenFromString(component); + Intent intent = new Intent(Intent.ACTION_MAIN) + .setComponent(cn) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS + | Intent.FLAG_ACTIVITY_SINGLE_TOP) + .putExtra("android.dreams.TEST", true); + getContext().startActivity(intent); + } + } +} diff --git a/src/com/android/settings/GoogleLocationSettingHelper.java b/src/com/android/settings/GoogleLocationSettingHelper.java index 0d4861e..be4a02c 100644 --- a/src/com/android/settings/GoogleLocationSettingHelper.java +++ b/src/com/android/settings/GoogleLocationSettingHelper.java @@ -122,6 +122,7 @@ public class GoogleLocationSettingHelper { try { context.startActivity(i); } catch (ActivityNotFoundException e) { + Log.e("GoogleLocationSettingHelper", "Problem while starting GSF location activity"); } } diff --git a/src/com/android/settings/LocationSettings.java b/src/com/android/settings/LocationSettings.java new file mode 100644 index 0000000..645e6d7 --- /dev/null +++ b/src/com/android/settings/LocationSettings.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings; + + +import android.content.ContentQueryMap; +import android.content.ContentResolver; +import android.content.Intent; +import android.database.Cursor; +import android.location.LocationManager; +import android.preference.CheckBoxPreference; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.preference.PreferenceScreen; +import android.provider.Settings; + +import java.util.Observable; +import java.util.Observer; + +/** + * Gesture lock pattern settings. + */ +public class LocationSettings extends SettingsPreferenceFragment + implements OnPreferenceChangeListener { + + // Location Settings + private static final String KEY_LOCATION_NETWORK = "location_network"; + private static final String KEY_LOCATION_GPS = "location_gps"; + private static final String KEY_ASSISTED_GPS = "assisted_gps"; + private static final String KEY_USE_LOCATION = "location_use_for_services"; + + private CheckBoxPreference mNetwork; + private CheckBoxPreference mGps; + private CheckBoxPreference mAssistedGps; + private CheckBoxPreference mUseLocation; + + // These provide support for receiving notification when Location Manager settings change. + // This is necessary because the Network Location Provider can change settings + // if the user does not confirm enabling the provider. + private ContentQueryMap mContentQueryMap; + + private Observer mSettingsObserver; + + @Override + public void onStart() { + super.onStart(); + // listen for Location Manager settings changes + Cursor settingsCursor = getContentResolver().query(Settings.Secure.CONTENT_URI, null, + "(" + Settings.System.NAME + "=?)", + new String[]{Settings.Secure.LOCATION_PROVIDERS_ALLOWED}, + null); + mContentQueryMap = new ContentQueryMap(settingsCursor, Settings.System.NAME, true, null); + } + + @Override + public void onStop() { + super.onStop(); + if (mSettingsObserver != null) { + mContentQueryMap.deleteObserver(mSettingsObserver); + } + } + + private PreferenceScreen createPreferenceHierarchy() { + PreferenceScreen root = getPreferenceScreen(); + if (root != null) { + root.removeAll(); + } + addPreferencesFromResource(R.xml.location_settings); + root = getPreferenceScreen(); + + mNetwork = (CheckBoxPreference) root.findPreference(KEY_LOCATION_NETWORK); + mGps = (CheckBoxPreference) root.findPreference(KEY_LOCATION_GPS); + mAssistedGps = (CheckBoxPreference) root.findPreference(KEY_ASSISTED_GPS); + if (GoogleLocationSettingHelper.isAvailable(getActivity())) { + // GSF present, Add setting for 'Use My Location' + CheckBoxPreference useLocation = new CheckBoxPreference(getActivity()); + useLocation.setKey(KEY_USE_LOCATION); + useLocation.setTitle(R.string.use_location_title); + useLocation.setSummaryOn(R.string.use_location_summary_enabled); + useLocation.setSummaryOff(R.string.use_location_summary_disabled); + useLocation.setChecked( + GoogleLocationSettingHelper.getUseLocationForServices(getActivity()) + == GoogleLocationSettingHelper.USE_LOCATION_FOR_SERVICES_ON); + useLocation.setPersistent(false); + useLocation.setOnPreferenceChangeListener(this); + getPreferenceScreen().addPreference(useLocation); + mUseLocation = useLocation; + } + + // Change the summary for wifi-only devices + if (Utils.isWifiOnly()) { + mNetwork.setSummaryOn(R.string.location_neighborhood_level_wifi); + } + + return root; + } + + @Override + public void onResume() { + super.onResume(); + + // Make sure we reload the preference hierarchy since some of these settings + // depend on others... + createPreferenceHierarchy(); + updateLocationToggles(); + + if (mSettingsObserver == null) { + mSettingsObserver = new Observer() { + public void update(Observable o, Object arg) { + updateLocationToggles(); + } + }; + mContentQueryMap.addObserver(mSettingsObserver); + } + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + + if (preference == mNetwork) { + Settings.Secure.setLocationProviderEnabled(getContentResolver(), + LocationManager.NETWORK_PROVIDER, mNetwork.isChecked()); + } else if (preference == mGps) { + boolean enabled = mGps.isChecked(); + Settings.Secure.setLocationProviderEnabled(getContentResolver(), + LocationManager.GPS_PROVIDER, enabled); + if (mAssistedGps != null) { + mAssistedGps.setEnabled(enabled); + } + } else if (preference == mAssistedGps) { + Settings.Secure.putInt(getContentResolver(), Settings.Secure.ASSISTED_GPS_ENABLED, + mAssistedGps.isChecked() ? 1 : 0); + } else { + // If we didn't handle it, let preferences handle it. + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + + return true; + } + + /* + * Creates toggles for each available location provider + */ + private void updateLocationToggles() { + ContentResolver res = getContentResolver(); + boolean gpsEnabled = Settings.Secure.isLocationProviderEnabled( + res, LocationManager.GPS_PROVIDER); + mNetwork.setChecked(Settings.Secure.isLocationProviderEnabled( + res, LocationManager.NETWORK_PROVIDER)); + mGps.setChecked(gpsEnabled); + if (mAssistedGps != null) { + mAssistedGps.setChecked(Settings.Secure.getInt(res, + Settings.Secure.ASSISTED_GPS_ENABLED, 2) == 1); + mAssistedGps.setEnabled(gpsEnabled); + } + } + + /** + * see confirmPatternThenDisableAndClear + */ + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + createPreferenceHierarchy(); + } + + public boolean onPreferenceChange(Preference preference, Object value) { + if (preference == mUseLocation) { + boolean newValue = (value == null ? false : (Boolean) value); + GoogleLocationSettingHelper.setUseLocationForServices(getActivity(), newValue); + // We don't want to change the value immediately here, since the user may click + // disagree in the dialog that pops up. When the activity we just launched exits, this + // activity will be restated and the new value re-read, so the checkbox will get its + // new value then. + return false; + } + return true; + } +} diff --git a/src/com/android/settings/PointerSpeedPreference.java b/src/com/android/settings/PointerSpeedPreference.java index e9704fd..6a6a71f 100644 --- a/src/com/android/settings/PointerSpeedPreference.java +++ b/src/com/android/settings/PointerSpeedPreference.java @@ -25,7 +25,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; -import android.preference.SeekBarPreference; +import android.preference.SeekBarDialogPreference; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.util.AttributeSet; @@ -33,7 +33,7 @@ import android.view.IWindowManager; import android.view.View; import android.widget.SeekBar; -public class PointerSpeedPreference extends SeekBarPreference implements +public class PointerSpeedPreference extends SeekBarDialogPreference implements SeekBar.OnSeekBarChangeListener { private SeekBar mSeekBar; diff --git a/src/com/android/settings/RadioInfo.java b/src/com/android/settings/RadioInfo.java index ba07fb5..d45616e 100644 --- a/src/com/android/settings/RadioInfo.java +++ b/src/com/android/settings/RadioInfo.java @@ -24,11 +24,11 @@ import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.LinkProperties; +import android.net.TrafficStats; import android.net.Uri; import android.os.AsyncResult; import android.os.Bundle; import android.os.Handler; -import android.os.INetStatService; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; @@ -133,7 +133,6 @@ public class RadioInfo extends Activity { private TelephonyManager mTelephonyManager; private Phone phone = null; private PhoneStateIntentReceiver mPhoneStateReceiver; - private INetStatService netstat; private String mPingIpAddrResult; private String mPingHostnameResult; @@ -311,8 +310,6 @@ public class RadioInfo extends Activity { phone.getNeighboringCids( mHandler.obtainMessage(EVENT_QUERY_NEIGHBORING_CIDS_DONE)); - netstat = INetStatService.Stub.asInterface(ServiceManager.getService("netstat")); - CellLocation.requestLocationUpdate(); } @@ -625,19 +622,16 @@ public class RadioInfo extends Activity { private final void updateDataStats2() { Resources r = getResources(); - try { - long txPackets = netstat.getMobileTxPackets(); - long rxPackets = netstat.getMobileRxPackets(); - long txBytes = netstat.getMobileTxBytes(); - long rxBytes = netstat.getMobileRxBytes(); + long txPackets = TrafficStats.getMobileTxPackets(); + long rxPackets = TrafficStats.getMobileRxPackets(); + long txBytes = TrafficStats.getMobileTxBytes(); + long rxBytes = TrafficStats.getMobileRxBytes(); - String packets = r.getString(R.string.radioInfo_display_packets); - String bytes = r.getString(R.string.radioInfo_display_bytes); + String packets = r.getString(R.string.radioInfo_display_packets); + String bytes = r.getString(R.string.radioInfo_display_bytes); - sent.setText(txPackets + " " + packets + ", " + txBytes + " " + bytes); - received.setText(rxPackets + " " + packets + ", " + rxBytes + " " + bytes); - } catch (RemoteException e) { - } + sent.setText(txPackets + " " + packets + ", " + txBytes + " " + bytes); + received.setText(rxPackets + " " + packets + ", " + rxBytes + " " + bytes); } /** diff --git a/src/com/android/settings/SecuritySettings.java b/src/com/android/settings/SecuritySettings.java index dc4c42b..fc91e78 100644 --- a/src/com/android/settings/SecuritySettings.java +++ b/src/com/android/settings/SecuritySettings.java @@ -19,15 +19,9 @@ package com.android.settings; import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; -import com.android.internal.widget.LockPatternUtils; - import android.app.admin.DevicePolicyManager; -import android.content.ContentQueryMap; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.database.Cursor; -import android.location.LocationManager; import android.os.Bundle; import android.os.Vibrator; import android.preference.CheckBoxPreference; @@ -41,9 +35,9 @@ import android.security.KeyStore; import android.telephony.TelephonyManager; import android.util.Log; +import com.android.internal.widget.LockPatternUtils; + import java.util.ArrayList; -import java.util.Observable; -import java.util.Observer; /** * Gesture lock pattern settings. @@ -60,45 +54,22 @@ public class SecuritySettings extends SettingsPreferenceFragment private static final String KEY_LOCK_AFTER_TIMEOUT = "lock_after_timeout"; private static final int SET_OR_CHANGE_LOCK_METHOD_REQUEST = 123; - // Location Settings - private static final String KEY_LOCATION_CATEGORY = "location_category"; - private static final String KEY_LOCATION_NETWORK = "location_network"; - private static final String KEY_LOCATION_GPS = "location_gps"; - private static final String KEY_ASSISTED_GPS = "assisted_gps"; - private static final String KEY_USE_LOCATION = "location_use_for_services"; - // Misc Settings private static final String KEY_SIM_LOCK = "sim_lock"; private static final String KEY_SHOW_PASSWORD = "show_password"; - private static final String KEY_ENABLE_CREDENTIALS = "enable_credentials"; private static final String KEY_RESET_CREDENTIALS = "reset_credentials"; - private static final String TAG = "SecuritySettings"; - - private CheckBoxPreference mNetwork; - private CheckBoxPreference mGps; - private CheckBoxPreference mAssistedGps; - private CheckBoxPreference mUseLocation; - DevicePolicyManager mDPM; - // These provide support for receiving notification when Location Manager settings change. - // This is necessary because the Network Location Provider can change settings - // if the user does not confirm enabling the provider. - private ContentQueryMap mContentQueryMap; - private ChooseLockSettingsHelper mChooseLockSettingsHelper; private LockPatternUtils mLockPatternUtils; private ListPreference mLockAfter; - private Observer mSettingsObserver; - private CheckBoxPreference mVisiblePattern; private CheckBoxPreference mTactileFeedback; private CheckBoxPreference mShowPassword; - private CheckBoxPreference mEnableCredentials; private Preference mResetCredentials; @Override @@ -112,25 +83,6 @@ public class SecuritySettings extends SettingsPreferenceFragment mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity()); } - @Override - public void onStart() { - super.onStart(); - // listen for Location Manager settings changes - Cursor settingsCursor = getContentResolver().query(Settings.Secure.CONTENT_URI, null, - "(" + Settings.System.NAME + "=?)", - new String[]{Settings.Secure.LOCATION_PROVIDERS_ALLOWED}, - null); - mContentQueryMap = new ContentQueryMap(settingsCursor, Settings.System.NAME, true, null); - } - - @Override - public void onStop() { - super.onStop(); - if (mSettingsObserver != null) { - mContentQueryMap.deleteObserver(mSettingsObserver); - } - } - private PreferenceScreen createPreferenceHierarchy() { PreferenceScreen root = getPreferenceScreen(); if (root != null) { @@ -139,32 +91,6 @@ public class SecuritySettings extends SettingsPreferenceFragment addPreferencesFromResource(R.xml.security_settings); root = getPreferenceScreen(); - mNetwork = (CheckBoxPreference) root.findPreference(KEY_LOCATION_NETWORK); - mGps = (CheckBoxPreference) root.findPreference(KEY_LOCATION_GPS); - mAssistedGps = (CheckBoxPreference) root.findPreference(KEY_ASSISTED_GPS); - if (GoogleLocationSettingHelper.isAvailable(getActivity())) { - // GSF present, Add setting for 'Use My Location' - PreferenceGroup locationCat = - (PreferenceGroup) root.findPreference(KEY_LOCATION_CATEGORY); - CheckBoxPreference useLocation = new CheckBoxPreference(getActivity()); - useLocation.setKey(KEY_USE_LOCATION); - useLocation.setTitle(R.string.use_location_title); - useLocation.setSummaryOn(R.string.use_location_summary_enabled); - useLocation.setSummaryOff(R.string.use_location_summary_disabled); - useLocation.setChecked( - GoogleLocationSettingHelper.getUseLocationForServices(getActivity()) - == GoogleLocationSettingHelper.USE_LOCATION_FOR_SERVICES_ON); - useLocation.setPersistent(false); - useLocation.setOnPreferenceChangeListener(this); - locationCat.addPreference(useLocation); - mUseLocation = useLocation; - } - - // Change the summary for wifi-only devices - if (Utils.isWifiOnly()) { - mNetwork.setSummaryOn(R.string.location_neighborhood_level_wifi); - } - // Add options for lock/unlock screen int resid = 0; if (!mLockPatternUtils.isSecure()) { @@ -239,8 +165,6 @@ public class SecuritySettings extends SettingsPreferenceFragment mShowPassword = (CheckBoxPreference) root.findPreference(KEY_SHOW_PASSWORD); // Credential storage - mEnableCredentials = (CheckBoxPreference) root.findPreference(KEY_ENABLE_CREDENTIALS); - mEnableCredentials.setOnPreferenceChangeListener(this); mResetCredentials = root.findPreference(KEY_RESET_CREDENTIALS); return root; @@ -315,16 +239,6 @@ public class SecuritySettings extends SettingsPreferenceFragment // Make sure we reload the preference hierarchy since some of these settings // depend on others... createPreferenceHierarchy(); - updateLocationToggles(); - - if (mSettingsObserver == null) { - mSettingsObserver = new Observer() { - public void update(Observable o, Object arg) { - updateLocationToggles(); - } - }; - mContentQueryMap.addObserver(mSettingsObserver); - } final LockPatternUtils lockPatternUtils = mChooseLockSettingsHelper.utils(); if (mVisiblePattern != null) { @@ -337,15 +251,12 @@ public class SecuritySettings extends SettingsPreferenceFragment mShowPassword.setChecked(Settings.System.getInt(getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD, 1) != 0); - int state = KeyStore.getInstance().test(); - mEnableCredentials.setChecked(state == KeyStore.NO_ERROR); - mEnableCredentials.setEnabled(state != KeyStore.UNINITIALIZED); - mResetCredentials.setEnabled(state != KeyStore.UNINITIALIZED); + KeyStore.State state = KeyStore.getInstance().state(); + mResetCredentials.setEnabled(state != KeyStore.State.UNINITIALIZED); } @Override - public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, - Preference preference) { + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { final String key = preference.getKey(); final LockPatternUtils lockPatternUtils = mChooseLockSettingsHelper.utils(); @@ -361,19 +272,6 @@ public class SecuritySettings extends SettingsPreferenceFragment } else if (preference == mShowPassword) { Settings.System.putInt(getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD, mShowPassword.isChecked() ? 1 : 0); - } else if (preference == mNetwork) { - Settings.Secure.setLocationProviderEnabled(getContentResolver(), - LocationManager.NETWORK_PROVIDER, mNetwork.isChecked()); - } else if (preference == mGps) { - boolean enabled = mGps.isChecked(); - Settings.Secure.setLocationProviderEnabled(getContentResolver(), - LocationManager.GPS_PROVIDER, enabled); - if (mAssistedGps != null) { - mAssistedGps.setEnabled(enabled); - } - } else if (preference == mAssistedGps) { - Settings.Secure.putInt(getContentResolver(), Settings.Secure.ASSISTED_GPS_ENABLED, - mAssistedGps.isChecked() ? 1 : 0); } else { // If we didn't handle it, let preferences handle it. return super.onPreferenceTreeClick(preferenceScreen, preference); @@ -382,29 +280,12 @@ public class SecuritySettings extends SettingsPreferenceFragment return true; } - /* - * Creates toggles for each available location provider - */ - private void updateLocationToggles() { - ContentResolver res = getContentResolver(); - boolean gpsEnabled = Settings.Secure.isLocationProviderEnabled( - res, LocationManager.GPS_PROVIDER); - mNetwork.setChecked(Settings.Secure.isLocationProviderEnabled( - res, LocationManager.NETWORK_PROVIDER)); - mGps.setChecked(gpsEnabled); - if (mAssistedGps != null) { - mAssistedGps.setChecked(Settings.Secure.getInt(res, - Settings.Secure.ASSISTED_GPS_ENABLED, 2) == 1); - mAssistedGps.setEnabled(gpsEnabled); - } - } - private boolean isToggled(Preference pref) { return ((CheckBoxPreference) pref).isChecked(); } /** - * @see #confirmPatternThenDisableAndClear + * see confirmPatternThenDisableAndClear */ @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { @@ -422,21 +303,6 @@ public class SecuritySettings extends SettingsPreferenceFragment Log.e("SecuritySettings", "could not persist lockAfter timeout setting", e); } updateLockAfterPreferenceSummary(); - } else if (preference == mUseLocation) { - boolean newValue = (value == null ? false : (Boolean) value); - GoogleLocationSettingHelper.setUseLocationForServices(getActivity(), newValue); - // We don't want to change the value immediately here, since the user may click - // disagree in the dialog that pops up. When the activity we just launched exits, this - // activity will be restated and the new value re-read, so the checkbox will get its - // new value then. - return false; - } else if (preference == mEnableCredentials) { - if (value != null && (Boolean) value) { - getActivity().startActivity(new Intent(CredentialStorage.ACTION_UNLOCK)); - return false; - } else { - KeyStore.getInstance().lock(); - } } return true; } diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index 2532747..cbdd37b 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -17,17 +17,30 @@ package com.android.settings; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Bundle; import android.preference.PreferenceActivity; +import android.text.TextUtils; import android.util.Log; +import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; import android.widget.Button; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.Switch; +import android.widget.TextView; +import com.android.settings.bluetooth.BluetoothEnabler; +import com.android.settings.wifi.WifiEnabler; + +import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -36,10 +49,11 @@ import java.util.List; */ public class Settings extends PreferenceActivity implements ButtonBarHandler { + private static final String LOG_TAG = "Settings"; private static final String META_DATA_KEY_HEADER_ID = - "com.android.settings.TOP_LEVEL_HEADER_ID"; + "com.android.settings.TOP_LEVEL_HEADER_ID"; private static final String META_DATA_KEY_FRAGMENT_CLASS = - "com.android.settings.FRAGMENT_CLASS"; + "com.android.settings.FRAGMENT_CLASS"; private static final String META_DATA_KEY_PARENT_TITLE = "com.android.settings.PARENT_FRAGMENT_TITLE"; private static final String META_DATA_KEY_PARENT_FRAGMENT_CLASS = @@ -58,6 +72,7 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { // TODO: Update Call Settings based on airplane mode state. protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>(); + private List<Header> mHeaders; @Override protected void onCreate(Bundle savedInstanceState) { @@ -107,6 +122,26 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { } } + @Override + public void onResume() { + super.onResume(); + + ListAdapter listAdapter = getListAdapter(); + if (listAdapter instanceof HeaderAdapter) { + ((HeaderAdapter) listAdapter).resume(); + } + } + + @Override + public void onPause() { + super.onPause(); + + ListAdapter listAdapter = getListAdapter(); + if (listAdapter instanceof HeaderAdapter) { + ((HeaderAdapter) listAdapter).pause(); + } + } + private void switchToHeaderLocal(Header header) { mInLocalHeaderSwitch = true; switchToHeader(header); @@ -148,7 +183,7 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { mParentHeader.title = parentInfo.metaData.getString(META_DATA_KEY_PARENT_TITLE); } } catch (NameNotFoundException nnfe) { - Log.w("Settings", "Could not find parent activity : " + className); + Log.w(LOG_TAG, "Could not find parent activity : " + className); } } @@ -174,21 +209,22 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { @Override public Intent getIntent() { - String startingFragment = getStartingFragmentClass(super.getIntent()); + Intent superIntent = super.getIntent(); + String startingFragment = getStartingFragmentClass(superIntent); if (startingFragment != null && !onIsMultiPane()) { - Intent modIntent = new Intent(super.getIntent()); + Intent modIntent = new Intent(superIntent); modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment); - Bundle args = super.getIntent().getExtras(); + Bundle args = superIntent.getExtras(); if (args != null) { args = new Bundle(args); } else { args = new Bundle(); } - args.putParcelable("intent", super.getIntent()); - modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, super.getIntent().getExtras()); + args.putParcelable("intent", superIntent); + modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras()); return modIntent; } - return super.getIntent(); + return superIntent; } /** @@ -204,7 +240,7 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { if ("com.android.settings.ManageApplications".equals(intentClass) || "com.android.settings.RunningServices".equals(intentClass) || "com.android.settings.applications.StorageUse".equals(intentClass)) { - // Old name of manage apps. + // Old names of manage apps. intentClass = com.android.settings.applications.ManageApplications.class.getName(); } @@ -226,17 +262,39 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { mCurrentHeader = header; return header; } - return super.onGetInitialHeader(); + + // Find first non-category header + int position = 0; + while (position < mHeaders.size()) { + Header header = mHeaders.get(position); + if (HeaderAdapter.getHeaderType(header) != HeaderAdapter.HEADER_TYPE_CATEGORY) + return header; + position++; + } + + Log.e(LOG_TAG, "Unable to find a non-category header"); + return null; } + @Override + public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args, + int titleRes, int shortTitleRes) { + Intent intent = super.onBuildStartFragmentIntent(fragmentName, args, + titleRes, shortTitleRes); + intent.setClass(this, SubSettings.class); + return intent; + } + /** * Populate the activity with the top-level headers. */ @Override - public void onBuildHeaders(List<Header> target) { - loadHeadersFromResource(R.xml.settings_headers, target); + public void onBuildHeaders(List<Header> headers) { + loadHeadersFromResource(R.xml.settings_headers, headers); - updateHeaderList(target); + updateHeaderList(headers); + + mHeaders = headers; } private void updateHeaderList(List<Header> target) { @@ -253,7 +311,18 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { } else if (id == R.id.call_settings) { if (!Utils.isVoiceCapable(this)) target.remove(header); + } else if (id == R.id.wifi_settings) { + // Remove WiFi Settings if WiFi service is not available. + if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) { + target.remove(header); + } + } else if (id == R.id.bluetooth_settings) { + // Remove Bluetooth Settings if Bluetooth service is not available. + if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) { + target.remove(header); + } } + // Increment if the current one wasn't removed by the Utils code. if (target.get(i) == header) { // Hold on to the first header, when we need to reset to the top-level @@ -287,6 +356,7 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { } } } catch (NameNotFoundException nnfe) { + // No recovery } } @@ -300,38 +370,197 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { return super.getNextButton(); } + private static class HeaderAdapter extends ArrayAdapter<Header> { + static final int HEADER_TYPE_CATEGORY = 0; + static final int HEADER_TYPE_NORMAL = 1; + static final int HEADER_TYPE_SWITCH = 2; + private static final int HEADER_TYPE_COUNT = HEADER_TYPE_SWITCH + 1; + + private final WifiEnabler mWifiEnabler; + private final BluetoothEnabler mBluetoothEnabler; + + private static class HeaderViewHolder { + ImageView icon; + TextView title; + TextView summary; + Switch switch_; + } + + private LayoutInflater mInflater; + + static int getHeaderType(Header header) { + if (header.fragment == null && header.intent == null) { + return HEADER_TYPE_CATEGORY; + } else if (header.id == R.id.wifi_settings || header.id == R.id.bluetooth_settings) { + return HEADER_TYPE_SWITCH; + } else { + return HEADER_TYPE_NORMAL; + } + } + + @Override + public int getItemViewType(int position) { + Header header = getItem(position); + return getHeaderType(header); + } + + @Override + public boolean areAllItemsEnabled() { + return false; // because of categories + } + + @Override + public boolean isEnabled(int position) { + return getItemViewType(position) != HEADER_TYPE_CATEGORY; + } + + @Override + public int getViewTypeCount() { + return HEADER_TYPE_COUNT; + } + + @Override + public boolean hasStableIds() { + return true; + } + + public HeaderAdapter(Context context, List<Header> objects) { + super(context, 0, objects); + mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + // These Switches are provided as placeholder until the adapter replaces these with actual + // Switches inflated from their layouts. Must be done before adapter is set in super + mWifiEnabler = new WifiEnabler(context, new Switch(context)); + mBluetoothEnabler = new BluetoothEnabler(context, new Switch(context)); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + HeaderViewHolder holder; + Header header = getItem(position); + int headerType = getHeaderType(header); + View view = null; + + if (convertView == null) { + holder = new HeaderViewHolder(); + switch (headerType) { + case HEADER_TYPE_CATEGORY: + view = new TextView(getContext(), null, android.R.attr.listSeparatorTextViewStyle); + holder.title = (TextView) view; + break; + + case HEADER_TYPE_SWITCH: + view = mInflater.inflate(R.layout.preference_header_switch_item, parent, false); + holder.icon = (ImageView) view.findViewById(R.id.icon); + holder.title = (TextView) view.findViewById(com.android.internal.R.id.title); + holder.summary = (TextView) view.findViewById(com.android.internal.R.id.summary); + holder.switch_ = (Switch) view.findViewById(R.id.switchWidget); + break; + + case HEADER_TYPE_NORMAL: + view = mInflater.inflate(com.android.internal.R.layout.preference_header_item, parent, false); + holder.icon = (ImageView) view.findViewById(com.android.internal.R.id.icon); + holder.title = (TextView) view.findViewById(com.android.internal.R.id.title); + holder.summary = (TextView) view.findViewById(com.android.internal.R.id.summary); + break; + } + view.setTag(holder); + } else { + view = convertView; + holder = (HeaderViewHolder) view.getTag(); + } + + // All view fields must be updated every time, because the view may be recycled + switch (headerType) { + case HEADER_TYPE_CATEGORY: + holder.title.setText(header.getTitle(getContext().getResources())); + break; + + case HEADER_TYPE_SWITCH: + // Would need a different treatment if the main menu had more switches + if (header.id == R.id.wifi_settings) { + mWifiEnabler.setSwitch(holder.switch_); + } else { + mBluetoothEnabler.setSwitch(holder.switch_); + } + // No break, fall through on purpose to update common fields + + //$FALL-THROUGH$ + case HEADER_TYPE_NORMAL: + holder.icon.setImageResource(header.iconRes); + holder.title.setText(header.getTitle(getContext().getResources())); + CharSequence summary = header.getSummary(getContext().getResources()); + if (!TextUtils.isEmpty(summary)) { + holder.summary.setVisibility(View.VISIBLE); + holder.summary.setText(summary); + } else { + holder.summary.setVisibility(View.GONE); + } + break; + } + + return view; + } + + public void resume() { + mWifiEnabler.resume(); + mBluetoothEnabler.resume(); + } + + public void pause() { + mWifiEnabler.pause(); + mBluetoothEnabler.pause(); + } + } + + @Override + public void setListAdapter(ListAdapter adapter) { + if (mHeaders == null) { + mHeaders = new ArrayList<Header>(); + // When the saved state provides the list of headers, onBuildHeaders is not called + // Copy the list of Headers from the adapter, preserving their order + for (int i = 0; i < adapter.getCount(); i++) { + mHeaders.add((Header) adapter.getItem(i)); + } + } + + // Ignore the adapter provided by PreferenceActivity and substitute ours instead + super.setListAdapter(new HeaderAdapter(this, mHeaders)); + } + /* * Settings subclasses for launching independently. */ - - public static class BluetoothSettingsActivity extends Settings { } - public static class WirelessSettingsActivity extends Settings { } - public static class TetherSettingsActivity extends Settings { } - public static class VpnSettingsActivity extends Settings { } - public static class DateTimeSettingsActivity extends Settings { } - public static class StorageSettingsActivity extends Settings { } - public static class WifiSettingsActivity extends Settings { } - public static class InputMethodAndLanguageSettingsActivity extends Settings { } - public static class InputMethodConfigActivity extends Settings { } - public static class InputMethodAndSubtypeEnablerActivity extends Settings { } - public static class LocalePickerActivity extends Settings { } - public static class UserDictionarySettingsActivity extends Settings { } - public static class SoundSettingsActivity extends Settings { } - public static class DisplaySettingsActivity extends Settings { } - public static class DeviceInfoSettingsActivity extends Settings { } - public static class ApplicationSettingsActivity extends Settings { } - public static class ManageApplicationsActivity extends Settings { } - public static class StorageUseActivity extends Settings { } - public static class DevelopmentSettingsActivity extends Settings { } - public static class AccessibilitySettingsActivity extends Settings { } - public static class SecuritySettingsActivity extends Settings { } - public static class PrivacySettingsActivity extends Settings { } - public static class DockSettingsActivity extends Settings { } - public static class RunningServicesActivity extends Settings { } - public static class ManageAccountsSettingsActivity extends Settings { } - public static class PowerUsageSummaryActivity extends Settings { } - public static class AccountSyncSettingsActivity extends Settings { } - public static class AccountSyncSettingsInAddAccountActivity extends Settings { } - public static class CryptKeeperSettingsActivity extends Settings { } - public static class DeviceAdminSettingsActivity extends Settings { } + public static class BluetoothSettingsActivity extends Settings { /* empty */ } + public static class WirelessSettingsActivity extends Settings { /* empty */ } + public static class TetherSettingsActivity extends Settings { /* empty */ } + public static class VpnSettingsActivity extends Settings { /* empty */ } + public static class DateTimeSettingsActivity extends Settings { /* empty */ } + public static class StorageSettingsActivity extends Settings { /* empty */ } + public static class WifiSettingsActivity extends Settings { /* empty */ } + public static class InputMethodAndLanguageSettingsActivity extends Settings { /* empty */ } + public static class InputMethodConfigActivity extends Settings { /* empty */ } + public static class InputMethodAndSubtypeEnablerActivity extends Settings { /* empty */ } + public static class LocalePickerActivity extends Settings { /* empty */ } + public static class UserDictionarySettingsActivity extends Settings { /* empty */ } + public static class SoundSettingsActivity extends Settings { /* empty */ } + public static class DisplaySettingsActivity extends Settings { /* empty */ } + public static class DeviceInfoSettingsActivity extends Settings { /* empty */ } + public static class ApplicationSettingsActivity extends Settings { /* empty */ } + public static class ManageApplicationsActivity extends Settings { /* empty */ } + public static class StorageUseActivity extends Settings { /* empty */ } + public static class DevelopmentSettingsActivity extends Settings { /* empty */ } + public static class AccessibilitySettingsActivity extends Settings { /* empty */ } + public static class SecuritySettingsActivity extends Settings { /* empty */ } + public static class LocationSettingsActivity extends Settings { /* empty */ } + public static class PrivacySettingsActivity extends Settings { /* empty */ } + public static class DockSettingsActivity extends Settings { /* empty */ } + public static class RunningServicesActivity extends Settings { /* empty */ } + public static class ManageAccountsSettingsActivity extends Settings { /* empty */ } + public static class PowerUsageSummaryActivity extends Settings { /* empty */ } + public static class AccountSyncSettingsActivity extends Settings { /* empty */ } + public static class AccountSyncSettingsInAddAccountActivity extends Settings { /* empty */ } + public static class CryptKeeperSettingsActivity extends Settings { /* empty */ } + public static class DeviceAdminSettingsActivity extends Settings { /* empty */ } + public static class DataUsageSummaryActivity extends Settings { /* empty */ } } diff --git a/src/com/android/settings/SettingsCheckBoxPreference.java b/src/com/android/settings/SettingsCheckBoxPreference.java new file mode 100644 index 0000000..6acdfe8 --- /dev/null +++ b/src/com/android/settings/SettingsCheckBoxPreference.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings; + +import android.content.Context; +import android.content.Intent; +import android.preference.CheckBoxPreference; +import android.util.TypedValue; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.CheckBox; +import android.widget.ImageView; + +/** + * CheckBox preference that optionally shows an icon for launching a settings + * {@link android.app.Activity}. The settings activity, if intent for launching + * it was provided, can be stared only if the CheckBox in is checked. + */ +public class SettingsCheckBoxPreference extends CheckBoxPreference { + + // Integer.MIN_VALUE means not initalized + private static int sDimAlpha = Integer.MIN_VALUE; + + private final Intent mSettingsIntent; + + /** + * Creates a new instance. + * + * @param context Context for accessing resources. + * @param settingsIntent Intent to use as settings for the item represented by + * this preference. Pass <code>null</code> if there is no associated + * settings activity. + */ + public SettingsCheckBoxPreference(Context context, Intent settingsIntent) { + super(context); + + if (sDimAlpha == Integer.MIN_VALUE) { + TypedValue outValue = new TypedValue(); + context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true); + sDimAlpha = (int) (outValue.getFloat() * 255); + } + + mSettingsIntent = settingsIntent; + setWidgetLayoutResource(R.layout.preference_settings_checkbox_widget); + } + + @Override + protected void onBindView(View view) { + super.onBindView(view); + + ImageView settingsButton = (ImageView) view.findViewById(R.id.settings_button); + if (settingsButton == null) { + return; + } + if (mSettingsIntent != null) { + CheckBox checkbox = (CheckBox) view.findViewById(com.android.internal.R.id.checkbox); + if (checkbox == null) { + return; + } + if (checkbox.isChecked()) { + settingsButton.setOnClickListener(new OnClickListener() { + public void onClick(View view) { + getContext().startActivity(mSettingsIntent); + } + }); + } + settingsButton.setVisibility(View.VISIBLE); + if (checkbox.isChecked() && isEnabled()) { + settingsButton.setAlpha(255); + } else { + settingsButton.setAlpha(sDimAlpha); + } + } else { + settingsButton.setVisibility(View.GONE); + } + } +} diff --git a/src/com/android/settings/SettingsLicenseActivity.java b/src/com/android/settings/SettingsLicenseActivity.java index 99828ce..2960180 100644 --- a/src/com/android/settings/SettingsLicenseActivity.java +++ b/src/com/android/settings/SettingsLicenseActivity.java @@ -21,7 +21,6 @@ import android.os.Handler; import android.os.Message; import android.os.SystemProperties; import android.text.TextUtils; -import android.util.Config; import android.util.Log; import android.webkit.WebView; import android.webkit.WebViewClient; @@ -46,7 +45,7 @@ import java.util.zip.GZIPInputStream; public class SettingsLicenseActivity extends Activity { private static final String TAG = "SettingsLicenseActivity"; - private static final boolean LOGV = false || Config.LOGV; + private static final boolean LOGV = false || false; private static final String DEFAULT_LICENSE_PATH = "/system/etc/NOTICE.html.gz"; private static final String PROPERTY_LICENSE_PATH = "ro.config.license_path"; diff --git a/src/com/android/settings/SubSettings.java b/src/com/android/settings/SubSettings.java new file mode 100644 index 0000000..9cd3c31 --- /dev/null +++ b/src/com/android/settings/SubSettings.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings; + +/** + * Stub class for showing sub-settings; we can't use the main Settings class + * since for our app it is a special singleTask class. + */ +public class SubSettings extends Settings { +} diff --git a/src/com/android/settings/TestingSettingsBroadcastReceiver.java b/src/com/android/settings/TestingSettingsBroadcastReceiver.java index c6cd7e1..cea12c5 100644 --- a/src/com/android/settings/TestingSettingsBroadcastReceiver.java +++ b/src/com/android/settings/TestingSettingsBroadcastReceiver.java @@ -6,7 +6,6 @@ import static android.provider.Telephony.Intents.SECRET_CODE_ACTION; import android.content.Context; import android.content.Intent; import android.content.BroadcastReceiver; -import android.util.Config; import android.util.Log; import android.view.KeyEvent; diff --git a/src/com/android/settings/TextToSpeechSettings.java b/src/com/android/settings/TextToSpeechSettings.java index 62edac9..f7945d8 100644 --- a/src/com/android/settings/TextToSpeechSettings.java +++ b/src/com/android/settings/TextToSpeechSettings.java @@ -21,28 +21,26 @@ import static android.provider.Settings.Secure.TTS_DEFAULT_LANG; import static android.provider.Settings.Secure.TTS_DEFAULT_RATE; import static android.provider.Settings.Secure.TTS_DEFAULT_SYNTH; import static android.provider.Settings.Secure.TTS_DEFAULT_VARIANT; -import static android.provider.Settings.Secure.TTS_ENABLED_PLUGINS; import static android.provider.Settings.Secure.TTS_USE_DEFAULTS; -import android.app.Activity; import android.app.AlertDialog; +import android.content.ActivityNotFoundException; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.os.Bundle; -import android.preference.CheckBoxPreference; import android.preference.ListPreference; import android.preference.Preference; -import android.preference.PreferenceGroup; -import android.preference.PreferenceScreen; import android.preference.Preference.OnPreferenceClickListener; +import android.preference.PreferenceGroup; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.speech.tts.TextToSpeech; +import android.speech.tts.TextToSpeech.EngineInfo; +import android.speech.tts.TtsEngines; +import android.text.TextUtils; import android.util.Log; import java.util.ArrayList; @@ -56,7 +54,6 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements private static final String TAG = "TextToSpeechSettings"; - private static final String SYSTEM_TTS = "com.svox.pico"; private static final String KEY_TTS_PLAY_EXAMPLE = "tts_play_example"; private static final String KEY_TTS_INSTALL_DATA = "tts_install_data"; private static final String KEY_TTS_USE_DEFAULT = "toggle_use_default_tts_settings"; @@ -65,6 +62,7 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements private static final String KEY_TTS_DEFAULT_COUNTRY = "tts_default_country"; private static final String KEY_TTS_DEFAULT_VARIANT = "tts_default_variant"; private static final String KEY_TTS_DEFAULT_SYNTH = "tts_default_synth"; + private static final String KEY_TTS_ENGINE_SETTINGS = "tts_engine_settings"; private static final String KEY_PLUGIN_ENABLED_PREFIX = "ENABLED_"; private static final String KEY_PLUGIN_SETTINGS_PREFIX = "SETTINGS_"; @@ -76,19 +74,18 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements private static final String LOCALE_DELIMITER = "-"; - private static final String FALLBACK_TTS_DEFAULT_SYNTH = - TextToSpeech.Engine.DEFAULT_SYNTH; + private Preference mPlayExample = null; + + private ListPreference mDefaultRatePref = null; + private ListPreference mDefaultLocPref = null; + private ListPreference mDefaultSynthPref = null; + + private Preference mInstallData = null; + private Preference mEngineSettings = null; - private Preference mPlayExample = null; - private Preference mInstallData = null; - private CheckBoxPreference mUseDefaultPref = null; - private ListPreference mDefaultRatePref = null; - private ListPreference mDefaultLocPref = null; - private ListPreference mDefaultSynthPref = null; private String mDefaultLanguage = null; private String mDefaultCountry = null; private String mDefaultLocVariant = null; - private String mDefaultEng = ""; private int mDefaultRate = TextToSpeech.Engine.DEFAULT_RATE; // Index of the current string to use for the demo. @@ -98,6 +95,7 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements private boolean mVoicesMissing = false; private TextToSpeech mTts = null; + private TtsEngines mEnginesHelper = null; private boolean mTtsStarted = false; /** @@ -112,10 +110,7 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.tts_settings); - final Activity activity = getActivity(); - addEngineSpecificSettings(activity); - - activity.setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM); + getActivity().setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM); mEnableDemo = false; mTtsStarted = false; @@ -125,10 +120,25 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements mDefaultCountry = currentLocale.getISO3Country(); mDefaultLocVariant = currentLocale.getVariant(); - mTts = new TextToSpeech(activity, this); - initClickers(); - } + mPlayExample = findPreference(KEY_TTS_PLAY_EXAMPLE); + mPlayExample.setOnPreferenceClickListener(this); + mInstallData = findPreference(KEY_TTS_INSTALL_DATA); + mInstallData.setOnPreferenceClickListener(this); + + mDefaultSynthPref = (ListPreference) findPreference(KEY_TTS_DEFAULT_SYNTH); + mDefaultRatePref = (ListPreference) findPreference(KEY_TTS_DEFAULT_RATE); + mDefaultLocPref = (ListPreference) findPreference(KEY_TTS_DEFAULT_LANG); + + mEngineSettings = findPreference(KEY_TTS_ENGINE_SETTINGS); + mEngineSettings.setEnabled(false); + + mTts = new TextToSpeech(getActivity().getApplicationContext(), this); + mEnginesHelper = new TtsEngines(getActivity().getApplicationContext()); + + initDefaultSettings(); + initEngineSpecificSettings(); + } @Override public void onStart() { @@ -142,7 +152,6 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements } } - @Override public void onDestroy() { super.onDestroy(); @@ -165,97 +174,39 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements } } - private void addEngineSpecificSettings(Context context) { - PreferenceGroup enginesCategory = (PreferenceGroup) findPreference("tts_engines_section"); - Intent intent = new Intent("android.intent.action.START_TTS_ENGINE"); - ResolveInfo[] enginesArray = new ResolveInfo[0]; - PackageManager pm = getPackageManager(); - enginesArray = pm.queryIntentActivities(intent, 0).toArray(enginesArray); - for (int i = 0; i < enginesArray.length; i++) { - String prefKey = ""; - final String pluginPackageName = enginesArray[i].activityInfo.packageName; - if (!enginesArray[i].activityInfo.packageName.equals(SYSTEM_TTS)) { - CheckBoxPreference chkbxPref = new CheckBoxPreference(context); - prefKey = KEY_PLUGIN_ENABLED_PREFIX + pluginPackageName; - chkbxPref.setKey(prefKey); - chkbxPref.setTitle(enginesArray[i].loadLabel(pm)); - enginesCategory.addPreference(chkbxPref); - } - if (pluginHasSettings(pluginPackageName)) { - Preference pref = new Preference(context); - prefKey = KEY_PLUGIN_SETTINGS_PREFIX + pluginPackageName; - pref.setKey(prefKey); - pref.setTitle(enginesArray[i].loadLabel(pm)); - CharSequence settingsLabel = getResources().getString( - R.string.tts_engine_name_settings, enginesArray[i].loadLabel(pm)); - pref.setSummary(settingsLabel); - pref.setOnPreferenceClickListener(new OnPreferenceClickListener(){ - public boolean onPreferenceClick(Preference preference){ - Intent i = new Intent(); - i.setClassName(pluginPackageName, - pluginPackageName + ".EngineSettings"); - startActivity(i); - return true; - } - }); - enginesCategory.addPreference(pref); - } - } - } - - private boolean pluginHasSettings(String pluginPackageName) { - PackageManager pm = getPackageManager(); - Intent i = new Intent(); - i.setClassName(pluginPackageName, pluginPackageName + ".EngineSettings"); - if (pm.resolveActivity(i, PackageManager.MATCH_DEFAULT_ONLY) != null){ - return true; - } - return false; - } + private void initEngineSpecificSettings() { + final String engineName = mEnginesHelper.getDefaultEngine(); + final EngineInfo engine = mEnginesHelper.getEngineInfo(engineName); + mEngineSettings.setTitle(getResources().getString(R.string.tts_engine_settings_title, + engine.label)); - private void initClickers() { - mPlayExample = findPreference(KEY_TTS_PLAY_EXAMPLE); - mPlayExample.setOnPreferenceClickListener(this); + final Intent settingsIntent = mEnginesHelper.getSettingsIntent(engineName); + if (settingsIntent != null) { + mEngineSettings.setOnPreferenceClickListener(new OnPreferenceClickListener() { + public boolean onPreferenceClick(Preference preference) { + startActivity(settingsIntent); + return true; + } + }); + mEngineSettings.setEnabled(true); + } else { + mEngineSettings.setEnabled(false); + } - mInstallData = findPreference(KEY_TTS_INSTALL_DATA); - mInstallData.setOnPreferenceClickListener(this); } - private void initDefaultSettings() { ContentResolver resolver = getContentResolver(); // Find the default TTS values in the settings, initialize and store the // settings if they are not found. - // "Use Defaults" - int useDefault = 0; - mUseDefaultPref = (CheckBoxPreference) findPreference(KEY_TTS_USE_DEFAULT); - try { - useDefault = Settings.Secure.getInt(resolver, TTS_USE_DEFAULTS); - } catch (SettingNotFoundException e) { - // "use default" setting not found, initialize it - useDefault = TextToSpeech.Engine.USE_DEFAULTS; - Settings.Secure.putInt(resolver, TTS_USE_DEFAULTS, useDefault); - } - mUseDefaultPref.setChecked(useDefault == 1); - mUseDefaultPref.setOnPreferenceChangeListener(this); - // Default synthesis engine - mDefaultSynthPref = (ListPreference) findPreference(KEY_TTS_DEFAULT_SYNTH); loadEngines(); mDefaultSynthPref.setOnPreferenceChangeListener(this); - String engine = Settings.Secure.getString(resolver, TTS_DEFAULT_SYNTH); - if (engine == null) { - // TODO move FALLBACK_TTS_DEFAULT_SYNTH to TextToSpeech - engine = FALLBACK_TTS_DEFAULT_SYNTH; - Settings.Secure.putString(resolver, TTS_DEFAULT_SYNTH, engine); - } - mDefaultEng = engine; // Default rate - mDefaultRatePref = (ListPreference) findPreference(KEY_TTS_DEFAULT_RATE); try { mDefaultRate = Settings.Secure.getInt(resolver, TTS_DEFAULT_RATE); } catch (SettingNotFoundException e) { @@ -265,34 +216,27 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements } mDefaultRatePref.setValue(String.valueOf(mDefaultRate)); mDefaultRatePref.setOnPreferenceChangeListener(this); - // apply the default rate so the TTS demo in the Settings screen uses it, even if - // the use of default settings is not enforced - mTts.setSpeechRate(mDefaultRate/100.0f); // Default language / country / variant : these three values map to a single ListPref // representing the matching Locale - mDefaultLocPref = (ListPreference) findPreference(KEY_TTS_DEFAULT_LANG); initDefaultLang(); mDefaultLocPref.setOnPreferenceChangeListener(this); } - /** * Ask the current default engine to launch the matching CHECK_TTS_DATA activity * to check the required TTS files are properly installed. */ private void checkVoiceData() { - PackageManager pm = getPackageManager(); - Intent intent = new Intent(); - intent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA); - List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, 0); - // query only the package that matches that of the default engine - for (int i = 0; i < resolveInfos.size(); i++) { - ActivityInfo currentActivityInfo = resolveInfos.get(i).activityInfo; - if (mDefaultEng.equals(currentActivityInfo.packageName)) { - intent.setClassName(mDefaultEng, currentActivityInfo.name); - this.startActivityForResult(intent, VOICE_DATA_INTEGRITY_CHECK); - } + String defaultEngine = mTts.getDefaultEngine(); + if (TextUtils.isEmpty(defaultEngine)) return; + Intent intent = new Intent(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA); + intent.setPackage(defaultEngine); + try { + Log.v(TAG, "Checking voice data: " + intent.toUri(0)); + startActivityForResult(intent, VOICE_DATA_INTEGRITY_CHECK); + } catch (ActivityNotFoundException ex) { + Log.e(TAG, "Failed to check TTS data, no acitivty found for " + intent + ")"); } } @@ -302,18 +246,16 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements * so the required TTS files are properly installed. */ private void installVoiceData() { - PackageManager pm = getPackageManager(); - Intent intent = new Intent(); - intent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA); + String defaultEngine = mTts.getDefaultEngine(); + if (TextUtils.isEmpty(defaultEngine)) return; + Intent intent = new Intent(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, 0); - // query only the package that matches that of the default engine - for (int i = 0; i < resolveInfos.size(); i++) { - ActivityInfo currentActivityInfo = resolveInfos.get(i).activityInfo; - if (mDefaultEng.equals(currentActivityInfo.packageName)) { - intent.setClassName(mDefaultEng, currentActivityInfo.name); - this.startActivity(intent); - } + intent.setPackage(defaultEngine); + try { + Log.v(TAG, "Installing voice data: " + intent.toUri(0)); + startActivity(intent); + } catch (ActivityNotFoundException ex) { + Log.e(TAG, "Failed to install TTS data, no acitivty found for " + intent + ")"); } } @@ -322,26 +264,22 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements * spoken to the user. */ private void getSampleText() { - PackageManager pm = getPackageManager(); - Intent intent = new Intent(); - // TODO (clchen): Replace Intent string with the actual - // Intent defined in the list of platform Intents. - intent.setAction("android.speech.tts.engine.GET_SAMPLE_TEXT"); + String defaultEngine = mTts.getDefaultEngine(); + if (TextUtils.isEmpty(defaultEngine)) return; + Intent intent = new Intent(TextToSpeech.Engine.ACTION_GET_SAMPLE_TEXT); intent.putExtra("language", mDefaultLanguage); intent.putExtra("country", mDefaultCountry); intent.putExtra("variant", mDefaultLocVariant); - List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, 0); - // query only the package that matches that of the default engine - for (int i = 0; i < resolveInfos.size(); i++) { - ActivityInfo currentActivityInfo = resolveInfos.get(i).activityInfo; - if (mDefaultEng.equals(currentActivityInfo.packageName)) { - intent.setClassName(mDefaultEng, currentActivityInfo.name); - this.startActivityForResult(intent, GET_SAMPLE_TEXT); - } + intent.setPackage(defaultEngine); + + try { + Log.v(TAG, "Getting sample text: " + intent.toUri(0)); + startActivityForResult(intent, GET_SAMPLE_TEXT); + } catch (ActivityNotFoundException ex) { + Log.e(TAG, "Failed to get sample text, no acitivty found for " + intent + ")"); } } - /** * Called when the TTS engine is initialized. */ @@ -358,7 +296,6 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements mDefaultLocVariant = new String(); } mTts.setLanguage(new Locale(mDefaultLanguage, mDefaultCountry, mDefaultLocVariant)); - initDefaultSettings(); updateWidgetState(); checkVoiceData(); mTtsStarted = true; @@ -370,142 +307,155 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements updateWidgetState(); } - /** * Called when voice data integrity check returns */ @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == VOICE_DATA_INTEGRITY_CHECK) { - if (data == null){ - // The CHECK_TTS_DATA activity for the plugin did not run properly; - // disable the preview and install controls and return. - mEnableDemo = false; - mVoicesMissing = false; - updateWidgetState(); - return; - } - ArrayList<String> available = - data.getStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES); - ArrayList<String> unavailable = - data.getStringArrayListExtra(TextToSpeech.Engine.EXTRA_UNAVAILABLE_VOICES); - if ((available == null) || (unavailable == null)){ - // The CHECK_TTS_DATA activity for the plugin did not run properly; - // disable the preview and install controls and return. - mEnableDemo = false; - mVoicesMissing = false; - updateWidgetState(); - return; + onVoiceDataIntegrityCheckDone(data); + } else if (requestCode == GET_SAMPLE_TEXT) { + onSampleTextReceived(resultCode, data); + } + } + + private void onVoiceDataIntegrityCheckDone(Intent data) { + if (data == null){ + Log.e(TAG, "TTS data check failed data = null"); + // The CHECK_TTS_DATA activity for the plugin did not run properly; + // disable the preview and install controls and return. + mEnableDemo = false; + mVoicesMissing = false; + updateWidgetState(); + return; + } + Log.v(TAG, "TTS data check completed, data = " + data.toUri(0)); + ArrayList<String> available = + data.getStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES); + ArrayList<String> unavailable = + data.getStringArrayListExtra(TextToSpeech.Engine.EXTRA_UNAVAILABLE_VOICES); + if (available == null || unavailable == null){ + Log.e(TAG, "TTS data check failed (available == == null)"); + // The CHECK_TTS_DATA activity for the plugin did not run properly; + // disable the preview and install controls and return. + mEnableDemo = false; + mVoicesMissing = false; + updateWidgetState(); + return; + } + if (available.size() > 0){ + if (mTts == null) { + mTts = new TextToSpeech(getActivity(), this); } - if (available.size() > 0){ - if (mTts == null) { - mTts = new TextToSpeech(getActivity(), this); - } - ListPreference ttsLanguagePref = - (ListPreference) findPreference("tts_default_lang"); - CharSequence[] entries = new CharSequence[available.size()]; - CharSequence[] entryValues = new CharSequence[available.size()]; - int selectedLanguageIndex = -1; - String selectedLanguagePref = mDefaultLanguage; - if (mDefaultCountry.length() > 0) { - selectedLanguagePref = selectedLanguagePref + LOCALE_DELIMITER + - mDefaultCountry; - } - if (mDefaultLocVariant.length() > 0) { - selectedLanguagePref = selectedLanguagePref + LOCALE_DELIMITER + - mDefaultLocVariant; - } - for (int i = 0; i < available.size(); i++) { - String[] langCountryVariant = available.get(i).split("-"); - Locale loc = null; - if (langCountryVariant.length == 1){ - loc = new Locale(langCountryVariant[0]); - } else if (langCountryVariant.length == 2){ - loc = new Locale(langCountryVariant[0], langCountryVariant[1]); - } else if (langCountryVariant.length == 3){ - loc = new Locale(langCountryVariant[0], langCountryVariant[1], - langCountryVariant[2]); - } - if (loc != null){ - entries[i] = loc.getDisplayName(); - entryValues[i] = available.get(i); - if (entryValues[i].equals(selectedLanguagePref)) { - selectedLanguageIndex = i; - } - } - } - ttsLanguagePref.setEntries(entries); - ttsLanguagePref.setEntryValues(entryValues); - if (selectedLanguageIndex > -1) { - ttsLanguagePref.setValueIndex(selectedLanguageIndex); - } - mEnableDemo = true; - // Make sure that the default language can be used. - int languageResult = mTts.setLanguage( + + updateDefaultLocPref(available); + + mEnableDemo = true; + // Make sure that the default language can be used. + int languageResult = mTts.setLanguage( + new Locale(mDefaultLanguage, mDefaultCountry, mDefaultLocVariant)); + if (languageResult < TextToSpeech.LANG_AVAILABLE){ + Locale currentLocale = Locale.getDefault(); + mDefaultLanguage = currentLocale.getISO3Language(); + mDefaultCountry = currentLocale.getISO3Country(); + mDefaultLocVariant = currentLocale.getVariant(); + languageResult = mTts.setLanguage( new Locale(mDefaultLanguage, mDefaultCountry, mDefaultLocVariant)); + // If the default Locale isn't supported, just choose the first available + // language so that there is at least something. if (languageResult < TextToSpeech.LANG_AVAILABLE){ - Locale currentLocale = Locale.getDefault(); - mDefaultLanguage = currentLocale.getISO3Language(); - mDefaultCountry = currentLocale.getISO3Country(); - mDefaultLocVariant = currentLocale.getVariant(); - languageResult = mTts.setLanguage( + parseLocaleInfo(mDefaultLocPref.getEntryValues()[0].toString()); + mTts.setLanguage( new Locale(mDefaultLanguage, mDefaultCountry, mDefaultLocVariant)); - // If the default Locale isn't supported, just choose the first available - // language so that there is at least something. - if (languageResult < TextToSpeech.LANG_AVAILABLE){ - parseLocaleInfo(ttsLanguagePref.getEntryValues()[0].toString()); - mTts.setLanguage( - new Locale(mDefaultLanguage, mDefaultCountry, mDefaultLocVariant)); - } - ContentResolver resolver = getContentResolver(); - Settings.Secure.putString(resolver, TTS_DEFAULT_LANG, mDefaultLanguage); - Settings.Secure.putString(resolver, TTS_DEFAULT_COUNTRY, mDefaultCountry); - Settings.Secure.putString(resolver, TTS_DEFAULT_VARIANT, mDefaultLocVariant); } - } else { - mEnableDemo = false; + ContentResolver resolver = getContentResolver(); + Settings.Secure.putString(resolver, TTS_DEFAULT_LANG, mDefaultLanguage); + Settings.Secure.putString(resolver, TTS_DEFAULT_COUNTRY, mDefaultCountry); + Settings.Secure.putString(resolver, TTS_DEFAULT_VARIANT, mDefaultLocVariant); } + } else { + mEnableDemo = false; + } - if (unavailable.size() > 0){ - mVoicesMissing = true; - } else { - mVoicesMissing = false; - } + if (unavailable.size() > 0){ + mVoicesMissing = true; + } else { + mVoicesMissing = false; + } - updateWidgetState(); - } else if (requestCode == GET_SAMPLE_TEXT) { - if (resultCode == TextToSpeech.LANG_AVAILABLE) { - String sample = getActivity().getString(R.string.tts_demo); - if ((data != null) && (data.getStringExtra("sampleText") != null)) { - sample = data.getStringExtra("sampleText"); - } - if (mTts != null) { - mTts.speak(sample, TextToSpeech.QUEUE_FLUSH, null); + updateWidgetState(); + } + + private void updateDefaultLocPref(ArrayList<String> availableLangs) { + CharSequence[] entries = new CharSequence[availableLangs.size()]; + CharSequence[] entryValues = new CharSequence[availableLangs.size()]; + int selectedLanguageIndex = -1; + String selectedLanguagePref = mDefaultLanguage; + if (mDefaultCountry.length() > 0) { + selectedLanguagePref = selectedLanguagePref + LOCALE_DELIMITER + + mDefaultCountry; + } + if (mDefaultLocVariant.length() > 0) { + selectedLanguagePref = selectedLanguagePref + LOCALE_DELIMITER + + mDefaultLocVariant; + } + for (int i = 0; i < availableLangs.size(); i++) { + String[] langCountryVariant = availableLangs.get(i).split("-"); + Locale loc = null; + if (langCountryVariant.length == 1){ + loc = new Locale(langCountryVariant[0]); + } else if (langCountryVariant.length == 2){ + loc = new Locale(langCountryVariant[0], langCountryVariant[1]); + } else if (langCountryVariant.length == 3){ + loc = new Locale(langCountryVariant[0], langCountryVariant[1], + langCountryVariant[2]); + } + if (loc != null){ + entries[i] = loc.getDisplayName(); + entryValues[i] = availableLangs.get(i); + if (entryValues[i].equals(selectedLanguagePref)) { + selectedLanguageIndex = i; } - } else { - // TODO: Display an error here to the user. - Log.e(TAG, "Did not have a sample string for the requested language"); } } + mDefaultLocPref.setEntries(entries); + mDefaultLocPref.setEntryValues(entryValues); + if (selectedLanguageIndex > -1) { + mDefaultLocPref.setValueIndex(selectedLanguageIndex); + } + } + + private void onSampleTextReceived(int resultCode, Intent data) { + if (resultCode == TextToSpeech.LANG_AVAILABLE) { + String sample = getActivity().getString(R.string.tts_demo); + if (data != null && data.getStringExtra("sampleText") != null) { + sample = data.getStringExtra("sampleText"); + } + Log.v(TAG, "Got sample text: " + sample); + if (mTts != null) { + mTts.speak(sample, TextToSpeech.QUEUE_FLUSH, null); + } + } else { + // TODO: Display an error here to the user. + Log.e(TAG, "Did not have a sample string for the requested language"); + } } public boolean onPreferenceChange(Preference preference, Object objValue) { if (KEY_TTS_USE_DEFAULT.equals(preference.getKey())) { // "Use Defaults" - int value = (Boolean)objValue ? 1 : 0; - Settings.Secure.putInt(getContentResolver(), TTS_USE_DEFAULTS, - value); - Log.i(TAG, "TTS use default settings is "+objValue.toString()); + int value = ((Boolean) objValue) ? 1 : 0; + Settings.Secure.putInt(getContentResolver(), TTS_USE_DEFAULTS, value); + Log.i(TAG, "TTS 'use default' settings changed, now " + value); } else if (KEY_TTS_DEFAULT_RATE.equals(preference.getKey())) { // Default rate mDefaultRate = Integer.parseInt((String) objValue); try { - Settings.Secure.putInt(getContentResolver(), - TTS_DEFAULT_RATE, mDefaultRate); + Settings.Secure.putInt(getContentResolver(), TTS_DEFAULT_RATE, mDefaultRate); if (mTts != null) { - mTts.setSpeechRate(mDefaultRate/100.0f); + mTts.setSpeechRate(mDefaultRate / 100.0f); } - Log.i(TAG, "TTS default rate is " + mDefaultRate); + Log.v(TAG, "TTS default rate changed, now " + mDefaultRate); } catch (NumberFormatException e) { Log.e(TAG, "could not persist default TTS rate setting", e); } @@ -522,19 +472,24 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements mTts.setLanguage(new Locale(mDefaultLanguage, mDefaultCountry, mDefaultLocVariant)); } int newIndex = mDefaultLocPref.findIndexOfValue((String)objValue); - Log.v("Settings", " selected is " + newIndex); + Log.v(TAG, " selected is " + newIndex); mDemoStringIndex = newIndex > -1 ? newIndex : 0; } else if (KEY_TTS_DEFAULT_SYNTH.equals(preference.getKey())) { - mDefaultEng = objValue.toString(); - Settings.Secure.putString(getContentResolver(), TTS_DEFAULT_SYNTH, mDefaultEng); - if (mTts != null) { - mTts.setEngineByPackageName(mDefaultEng); - mEnableDemo = false; - mVoicesMissing = false; - updateWidgetState(); - checkVoiceData(); + final String name = objValue.toString(); + final EngineInfo info = mEnginesHelper.getEngineInfo(name); + + if (info.system) { + // For system engines, do away with the alert dialog. + updateDefaultEngine(name); + initEngineSpecificSettings(); + } else { + // For all other engines, display a warning message before + // turning them on. + displayDataAlert(preference, name); } - Log.v("Settings", "The default synth is: " + objValue.toString()); + + // We'll deal with updating the UI ourselves. + return false; } return true; @@ -550,61 +505,18 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements // the actual speaking getSampleText(); return true; - } - if (preference == mInstallData) { + } else if (preference == mInstallData) { installVoiceData(); // quit this activity so it needs to be restarted after installation of the voice data finish(); return true; } - return false; - } - - @Override - public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { - if (Utils.isMonkeyRunning()) { - return false; - } - if (preference instanceof CheckBoxPreference) { - final CheckBoxPreference chkPref = (CheckBoxPreference) preference; - if (!chkPref.getKey().equals(KEY_TTS_USE_DEFAULT)){ - if (chkPref.isChecked()) { - chkPref.setChecked(false); - AlertDialog d = (new AlertDialog.Builder(getActivity())) - .setTitle(android.R.string.dialog_alert_title) - .setIcon(android.R.drawable.ic_dialog_alert) - .setMessage( - getActivity().getString(R.string.tts_engine_security_warning, - chkPref.getTitle())) - .setCancelable(true) - .setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - chkPref.setChecked(true); - loadEngines(); - } - }) - .setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - } - }) - .create(); - d.show(); - } else { - loadEngines(); - } - return true; - } - } return false; } - private void updateWidgetState() { mPlayExample.setEnabled(mEnableDemo); - mUseDefaultPref.setEnabled(mEnableDemo); mDefaultRatePref.setEnabled(mEnableDemo); mDefaultLocPref.setEnabled(mEnableDemo); @@ -617,14 +529,18 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements mDefaultLanguage = ""; mDefaultCountry = ""; mDefaultLocVariant = ""; - if (tokenizer.hasMoreTokens()) { - mDefaultLanguage = tokenizer.nextToken().trim(); - } - if (tokenizer.hasMoreTokens()) { - mDefaultCountry = tokenizer.nextToken().trim(); - } - if (tokenizer.hasMoreTokens()) { - mDefaultLocVariant = tokenizer.nextToken().trim(); + + if (locale != null) { + String[] components = locale.split(LOCALE_DELIMITER); + if (components.length > 0) { + mDefaultLanguage = components[0]; + } + if (components.length > 1) { + mDefaultCountry = components[1]; + } + if (components.length > 2) { + mDefaultLocVariant = components[2]; + } } } @@ -716,50 +632,78 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements Settings.Secure.putString(resolver, TTS_DEFAULT_VARIANT, DEFAULT_VARIANT_VAL); } - private void loadEngines() { - mDefaultSynthPref = (ListPreference) findPreference(KEY_TTS_DEFAULT_SYNTH); - - // TODO (clchen): Try to see if it is possible to be more efficient here - // and not search for plugins again. - Intent intent = new Intent("android.intent.action.START_TTS_ENGINE"); - ResolveInfo[] enginesArray = new ResolveInfo[0]; - PackageManager pm = getPackageManager(); - enginesArray = pm.queryIntentActivities(intent, 0).toArray(enginesArray); - ArrayList<CharSequence> entries = new ArrayList<CharSequence>(); - ArrayList<CharSequence> values = new ArrayList<CharSequence>(); - String enabledEngines = ""; - for (int i = 0; i < enginesArray.length; i++) { - String pluginPackageName = enginesArray[i].activityInfo.packageName; - if (pluginPackageName.equals(SYSTEM_TTS)) { - entries.add(enginesArray[i].loadLabel(pm)); - values.add(pluginPackageName); - } else { - CheckBoxPreference pref = (CheckBoxPreference) findPreference( - KEY_PLUGIN_ENABLED_PREFIX + pluginPackageName); - if ((pref != null) && pref.isChecked()){ - entries.add(enginesArray[i].loadLabel(pm)); - values.add(pluginPackageName); - enabledEngines = enabledEngines + pluginPackageName + " "; - } - } + List<EngineInfo> engines = mEnginesHelper.getEngines(); + CharSequence entries[] = new CharSequence[engines.size()]; + CharSequence values[] = new CharSequence[engines.size()]; + + final int count = engines.size(); + for (int i = 0; i < count; ++i) { + final EngineInfo engine = engines.get(i); + entries[i] = engine.label; + values[i] = engine.name; } - ContentResolver resolver = getContentResolver(); - Settings.Secure.putString(resolver, TTS_ENABLED_PLUGINS, enabledEngines); - - CharSequence entriesArray[] = new CharSequence[entries.size()]; - CharSequence valuesArray[] = new CharSequence[values.size()]; - mDefaultSynthPref.setEntries(entries.toArray(entriesArray)); - mDefaultSynthPref.setEntryValues(values.toArray(valuesArray)); + mDefaultSynthPref.setEntries(entries); + mDefaultSynthPref.setEntryValues(values); // Set the selected engine based on the saved preference String selectedEngine = Settings.Secure.getString(getContentResolver(), TTS_DEFAULT_SYNTH); int selectedEngineIndex = mDefaultSynthPref.findIndexOfValue(selectedEngine); if (selectedEngineIndex == -1){ - selectedEngineIndex = mDefaultSynthPref.findIndexOfValue(SYSTEM_TTS); + selectedEngineIndex = mDefaultSynthPref.findIndexOfValue( + mEnginesHelper.getHighestRankedEngineName()); + } + if (selectedEngineIndex >= 0) { + mDefaultSynthPref.setValueIndex(selectedEngineIndex); + } + } + + private void displayDataAlert(Preference pref, final String key) { + Log.v(TAG, "Displaying data alert for :" + key); + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(android.R.string.dialog_alert_title); + builder.setIcon(android.R.drawable.ic_dialog_alert); + builder.setMessage(getActivity().getString( + R.string.tts_engine_security_warning, pref.getTitle())); + builder.setCancelable(true); + builder.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + updateDefaultEngine(key); + loadEngines(); + initEngineSpecificSettings(); + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + + AlertDialog dialog = builder.create(); + dialog.show(); + } + + private void updateDefaultEngine(String engine) { + Log.v(TAG, "Updating default synth to : " + engine); + if (mTts != null) { + try { + mTts.shutdown(); + mTts = null; + } catch (Exception e) { + Log.e(TAG, "Error shutting down TTS engine" + e); + } } - mDefaultSynthPref.setValueIndex(selectedEngineIndex); + + mTts = new TextToSpeech(getActivity().getApplicationContext(), this, engine); + mEnableDemo = false; + mVoicesMissing = false; + updateWidgetState(); + checkVoiceData(); + + // Finally, persist this value to settings. + Settings.Secure.putString(getContentResolver(), TTS_DEFAULT_SYNTH, engine); + // .. and update the UI. + mDefaultSynthPref.setValue(engine); + + Log.v(TAG, "The default synth is now: " + engine); } } diff --git a/src/com/android/settings/UserDictionarySettings.java b/src/com/android/settings/UserDictionarySettings.java index b13126a..22112d7 100644 --- a/src/com/android/settings/UserDictionarySettings.java +++ b/src/com/android/settings/UserDictionarySettings.java @@ -46,6 +46,7 @@ import android.widget.TextView; import com.android.settings.SettingsPreferenceFragment.SettingsDialogFragment; +import java.util.Arrays; import java.util.Locale; public class UserDictionarySettings extends ListFragment implements DialogCreatable { @@ -63,23 +64,29 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata // Either the locale is empty (means the word is applicable to all locales) // or the word equals our current locale - private static final String QUERY_SELECTION = UserDictionary.Words.LOCALE + "=? OR " - + UserDictionary.Words.LOCALE + " is null"; + private static final String QUERY_SELECTION = + UserDictionary.Words.LOCALE + "=?"; + private static final String QUERY_SELECTION_ALL_LOCALES = + UserDictionary.Words.LOCALE + " is null"; private static final String DELETE_SELECTION = UserDictionary.Words.WORD + "=?"; private static final String EXTRA_WORD = "word"; - + private static final int OPTIONS_MENU_ADD = Menu.FIRST; private static final int DIALOG_ADD_OR_EDIT = 0; - + + private static final int FREQUENCY_FOR_USER_DICTIONARY_ADDS = 250; + /** The word being edited in the dialog (null means the user is adding a word). */ private String mDialogEditingWord; private View mView; private Cursor mCursor; - + + protected String mLocale; + private boolean mAddedWordAlready; private boolean mAutoReturn; @@ -101,7 +108,25 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mCursor = createCursor(); + final Intent intent = getActivity().getIntent(); + final String localeFromIntent = + null == intent ? null : intent.getStringExtra("locale"); + + final Bundle arguments = getArguments(); + final String localeFromArguments = + null == arguments ? null : arguments.getString("locale"); + + final String locale; + if (null != localeFromArguments) { + locale = localeFromArguments; + } else if (null != localeFromIntent) { + locale = localeFromIntent; + } else { + locale = null; + } + + mLocale = locale; + mCursor = createCursor(locale); TextView emptyView = (TextView)mView.findViewById(R.id.empty); emptyView.setText(R.string.user_dict_settings_empty_text); @@ -117,12 +142,12 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata mAddedWordAlready = savedInstanceState.getBoolean(INSTANCE_KEY_ADDED_WORD, false); } } - + @Override public void onResume() { super.onResume(); final Intent intent = getActivity().getIntent(); - if (!mAddedWordAlready + if (!mAddedWordAlready && intent.getAction().equals("com.android.settings.USER_DICTIONARY_INSERT")) { final String word = intent.getStringExtra(EXTRA_WORD); mAutoReturn = true; @@ -139,12 +164,28 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata outState.putBoolean(INSTANCE_KEY_ADDED_WORD, mAddedWordAlready); } - private Cursor createCursor() { - String currentLocale = Locale.getDefault().toString(); - // Case-insensitive sort - return getActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION, - QUERY_SELECTION, new String[] { currentLocale }, - "UPPER(" + UserDictionary.Words.WORD + ")"); + private Cursor createCursor(final String locale) { + // Locale can be any of: + // - The string representation of a locale, as returned by Locale#toString() + // - The empty string. This means we want a cursor returning words valid for all locales. + // - null. This means we want a cursor for the current locale, whatever this is. + // Note that this contrasts with the data inside the database, where NULL means "all + // locales" and there should never be an empty string. The confusion is called by the + // historical use of null for "all locales". + // TODO: it should be easy to make this more readable by making the special values + // human-readable, like "all_locales" and "current_locales" strings, provided they + // can be guaranteed not to match locales that may exist. + if ("".equals(locale)) { + // Case-insensitive sort + return getActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION, + QUERY_SELECTION_ALL_LOCALES, null, + "UPPER(" + UserDictionary.Words.WORD + ")"); + } else { + final String queryLocale = null != locale ? locale : Locale.getDefault().toString(); + return getActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION, + QUERY_SELECTION, new String[] { queryLocale }, + "UPPER(" + UserDictionary.Words.WORD + ")"); + } } private ListAdapter createAdapter() { @@ -153,7 +194,7 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata new String[] { UserDictionary.Words.WORD, UserDictionary.Words._ID }, new int[] { android.R.id.text1, R.id.delete_button }, this); } - + @Override public void onListItemClick(ListView l, View v, int position, long id) { String word = getWord(position); @@ -235,13 +276,28 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata // The user was editing a word, so do a delete/add deleteWord(mDialogEditingWord); } - + // Disallow duplicates deleteWord(word); - + // TODO: present UI for picking whether to add word to all locales, or current. - UserDictionary.Words.addWord(getActivity(), word.toString(), - 250, UserDictionary.Words.LOCALE_TYPE_ALL); + if (null == mLocale) { + // Null means insert with the default system locale. + UserDictionary.Words.addWord(getActivity(), word.toString(), + FREQUENCY_FOR_USER_DICTIONARY_ADDS, UserDictionary.Words.LOCALE_TYPE_CURRENT); + } else if ("".equals(mLocale)) { + // Empty string means insert for all languages. + UserDictionary.Words.addWord(getActivity(), word.toString(), + FREQUENCY_FOR_USER_DICTIONARY_ADDS, UserDictionary.Words.LOCALE_TYPE_ALL); + } else { + // TODO: fix the framework so that it can accept a locale when we add a word + // to the user dictionary instead of querying the system locale. + final Locale prevLocale = Locale.getDefault(); + Locale.setDefault(Utils.createLocaleFromString(mLocale)); + UserDictionary.Words.addWord(getActivity(), word.toString(), + FREQUENCY_FOR_USER_DICTIONARY_ADDS, UserDictionary.Words.LOCALE_TYPE_CURRENT); + Locale.setDefault(prevLocale); + } if (!mCursor.requery()) { throw new IllegalStateException("can't requery on already-closed cursor."); } diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index 18c6159..422ae90 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -36,8 +36,10 @@ import android.telephony.TelephonyManager; import android.text.TextUtils; import java.net.InetAddress; +import java.util.Arrays; import java.util.Iterator; import java.util.List; +import java.util.Locale; public class Utils { @@ -308,4 +310,24 @@ public class Utils { } return addresses; } + + public static Locale createLocaleFromString(String localeStr) { + // TODO: is there a better way to actually construct a locale that will match? + // The main problem is, on top of Java specs, locale.toString() and + // new Locale(locale.toString()).toString() do not return equal() strings in + // many cases, because the constructor takes the only string as the language + // code. So : new Locale("en", "US").toString() => "en_US" + // And : new Locale("en_US").toString() => "en_us" + if (null == localeStr) + return Locale.getDefault(); + String[] brokenDownLocale = localeStr.split("_", 3); + // split may not return a 0-length array. + if (1 == brokenDownLocale.length) { + return new Locale(brokenDownLocale[0]); + } else if (2 == brokenDownLocale.length) { + return new Locale(brokenDownLocale[0], brokenDownLocale[1]); + } else { + return new Locale(brokenDownLocale[0], brokenDownLocale[1], brokenDownLocale[2]); + } + } } diff --git a/src/com/android/settings/WallpaperTypeSettings.java b/src/com/android/settings/WallpaperTypeSettings.java new file mode 100644 index 0000000..fa0b4e4 --- /dev/null +++ b/src/com/android/settings/WallpaperTypeSettings.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.preference.Preference; + +import java.util.List; + +public class WallpaperTypeSettings extends SettingsPreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.wallpaper_settings); + + populateWallpaperTypes(); + } + + private void populateWallpaperTypes() { + // Search for activities that satisfy the ACTION_SET_WALLPAPER action + Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER); + final PackageManager pm = getPackageManager(); + List<ResolveInfo> rList = pm.queryIntentActivities(intent, + PackageManager.MATCH_DEFAULT_ONLY); + + // Add Preference items for each of the matching activities + for (ResolveInfo info : rList) { + Preference pref = new Preference(getActivity()); + Intent prefIntent = new Intent(intent); + prefIntent.setComponent(new ComponentName( + info.activityInfo.packageName, info.activityInfo.name)); + pref.setIntent(prefIntent); + CharSequence label = info.loadLabel(pm); + if (label == null) label = info.activityInfo.packageName; + pref.setTitle(label); + getPreferenceScreen().addPreference(pref); + } + } +} diff --git a/src/com/android/settings/WirelessSettings.java b/src/com/android/settings/WirelessSettings.java index 2844f3b..d09fcc5 100644 --- a/src/com/android/settings/WirelessSettings.java +++ b/src/com/android/settings/WirelessSettings.java @@ -16,35 +16,27 @@ package com.android.settings; -import com.android.internal.telephony.TelephonyIntents; -import com.android.internal.telephony.TelephonyProperties; -import com.android.settings.bluetooth.BluetoothEnabler; -import com.android.settings.wifi.WifiEnabler; -import com.android.settings.nfc.NfcEnabler; - import android.app.Activity; import android.app.admin.DevicePolicyManager; -import android.bluetooth.BluetoothAdapter; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; import android.nfc.NfcAdapter; import android.os.Bundle; -import android.os.ServiceManager; import android.os.SystemProperties; import android.preference.CheckBoxPreference; import android.preference.Preference; import android.preference.PreferenceScreen; import android.provider.Settings; +import com.android.internal.telephony.TelephonyIntents; +import com.android.internal.telephony.TelephonyProperties; +import com.android.settings.nfc.NfcEnabler; + public class WirelessSettings extends SettingsPreferenceFragment { private static final String KEY_TOGGLE_AIRPLANE = "toggle_airplane"; - private static final String KEY_TOGGLE_BLUETOOTH = "toggle_bluetooth"; - private static final String KEY_TOGGLE_WIFI = "toggle_wifi"; private static final String KEY_TOGGLE_NFC = "toggle_nfc"; - private static final String KEY_WIFI_SETTINGS = "wifi_settings"; - private static final String KEY_BT_SETTINGS = "bt_settings"; private static final String KEY_VPN_SETTINGS = "vpn_settings"; private static final String KEY_TETHER_SETTINGS = "tether_settings"; private static final String KEY_PROXY_SETTINGS = "proxy_settings"; @@ -55,9 +47,7 @@ public class WirelessSettings extends SettingsPreferenceFragment { private AirplaneModeEnabler mAirplaneModeEnabler; private CheckBoxPreference mAirplaneModePreference; - private WifiEnabler mWifiEnabler; private NfcEnabler mNfcEnabler; - private BluetoothEnabler mBtEnabler; /** * Invoked on each preference click in this hierarchy, overrides @@ -95,15 +85,10 @@ public class WirelessSettings extends SettingsPreferenceFragment { addPreferencesFromResource(R.xml.wireless_settings); final Activity activity = getActivity(); - CheckBoxPreference airplane = (CheckBoxPreference) findPreference(KEY_TOGGLE_AIRPLANE); - CheckBoxPreference wifi = (CheckBoxPreference) findPreference(KEY_TOGGLE_WIFI); - CheckBoxPreference bt = (CheckBoxPreference) findPreference(KEY_TOGGLE_BLUETOOTH); + mAirplaneModePreference = (CheckBoxPreference) findPreference(KEY_TOGGLE_AIRPLANE); CheckBoxPreference nfc = (CheckBoxPreference) findPreference(KEY_TOGGLE_NFC); - mAirplaneModeEnabler = new AirplaneModeEnabler(activity, airplane); - mAirplaneModePreference = (CheckBoxPreference) findPreference(KEY_TOGGLE_AIRPLANE); - mWifiEnabler = new WifiEnabler(activity, wifi); - mBtEnabler = new BluetoothEnabler(activity, bt); + mAirplaneModeEnabler = new AirplaneModeEnabler(activity, mAirplaneModePreference); mNfcEnabler = new NfcEnabler(activity, nfc); String toggleable = Settings.System.getString(activity.getContentResolver(), @@ -111,21 +96,12 @@ public class WirelessSettings extends SettingsPreferenceFragment { // Manually set dependencies for Wifi when not toggleable. if (toggleable == null || !toggleable.contains(Settings.System.RADIO_WIFI)) { - wifi.setDependency(KEY_TOGGLE_AIRPLANE); - findPreference(KEY_WIFI_SETTINGS).setDependency(KEY_TOGGLE_AIRPLANE); findPreference(KEY_VPN_SETTINGS).setDependency(KEY_TOGGLE_AIRPLANE); } // Manually set dependencies for Bluetooth when not toggleable. if (toggleable == null || !toggleable.contains(Settings.System.RADIO_BLUETOOTH)) { - bt.setDependency(KEY_TOGGLE_AIRPLANE); - findPreference(KEY_BT_SETTINGS).setDependency(KEY_TOGGLE_AIRPLANE); - } - - // Remove Bluetooth Settings if Bluetooth service is not available. - if (ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE) == null) { - getPreferenceScreen().removePreference(bt); - getPreferenceScreen().removePreference(findPreference(KEY_BT_SETTINGS)); + // No bluetooth-dependent items in the list. Code kept in case one is added later. } // Remove NFC if its not available @@ -191,8 +167,6 @@ public class WirelessSettings extends SettingsPreferenceFragment { super.onResume(); mAirplaneModeEnabler.resume(); - mWifiEnabler.resume(); - mBtEnabler.resume(); mNfcEnabler.resume(); } @@ -201,8 +175,6 @@ public class WirelessSettings extends SettingsPreferenceFragment { super.onPause(); mAirplaneModeEnabler.pause(); - mWifiEnabler.pause(); - mBtEnabler.pause(); mNfcEnabler.pause(); } diff --git a/src/com/android/settings/accounts/AccountPreferenceBase.java b/src/com/android/settings/accounts/AccountPreferenceBase.java index a84bece..a0d6a7f 100644 --- a/src/com/android/settings/accounts/AccountPreferenceBase.java +++ b/src/com/android/settings/accounts/AccountPreferenceBase.java @@ -32,6 +32,7 @@ import android.content.Context; import android.content.SyncAdapterType; import android.content.SyncStatusObserver; import android.content.pm.PackageManager; +import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; @@ -113,7 +114,7 @@ class AccountPreferenceBase extends SettingsPreferenceFragment mAccountTypeToAuthorities.put(sa.accountType, authorities); } if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.d(TAG, "added authority " + sa.authority + " to accountType " + Log.d(TAG, "added authority " + sa.authority + " to accountType " + sa.accountType); } authorities.add(sa.authority); @@ -136,7 +137,10 @@ class AccountPreferenceBase extends SettingsPreferenceFragment icon = authContext.getResources().getDrawable(desc.iconId); } catch (PackageManager.NameNotFoundException e) { // TODO: place holder icon for missing account icons? - Log.w(TAG, "No icon for account type " + accountType); + Log.w(TAG, "No icon name for account type " + accountType); + } catch (Resources.NotFoundException e) { + // TODO: place holder icon for missing account icons? + Log.w(TAG, "No icon resource for account type " + accountType); } } return icon; @@ -155,7 +159,9 @@ class AccountPreferenceBase extends SettingsPreferenceFragment Context authContext = getActivity().createPackageContext(desc.packageName, 0); label = authContext.getResources().getText(desc.labelId); } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "No label for account type " + ", type " + accountType); + Log.w(TAG, "No label name for account type " + accountType); + } catch (Resources.NotFoundException e) { + Log.w(TAG, "No label icon for account type " + accountType); } } return label; @@ -179,6 +185,8 @@ class AccountPreferenceBase extends SettingsPreferenceFragment } } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Couldn't load preferences.xml file from " + desc.packageName); + } catch (Resources.NotFoundException e) { + Log.w(TAG, "Couldn't load preferences.xml file from " + desc.packageName); } } return prefs; diff --git a/src/com/android/settings/accounts/ChooseAccountActivity.java b/src/com/android/settings/accounts/ChooseAccountActivity.java index 9576dee..631fe47 100644 --- a/src/com/android/settings/accounts/ChooseAccountActivity.java +++ b/src/com/android/settings/accounts/ChooseAccountActivity.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.Intent; import android.content.SyncAdapterType; import android.content.pm.PackageManager; +import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.preference.Preference; @@ -199,7 +200,10 @@ public class ChooseAccountActivity extends PreferenceActivity { icon = authContext.getResources().getDrawable(desc.iconId); } catch (PackageManager.NameNotFoundException e) { // TODO: place holder icon for missing account icons? - Log.w(TAG, "No icon for account type " + accountType); + Log.w(TAG, "No icon name for account type " + accountType); + } catch (Resources.NotFoundException e) { + // TODO: place holder icon for missing account icons? + Log.w(TAG, "No icon resource for account type " + accountType); } } return icon; @@ -218,7 +222,9 @@ public class ChooseAccountActivity extends PreferenceActivity { Context authContext = createPackageContext(desc.packageName, 0); label = authContext.getResources().getText(desc.labelId); } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "No label for account type " + ", type " + accountType); + Log.w(TAG, "No label name for account type " + accountType); + } catch (Resources.NotFoundException e) { + Log.w(TAG, "No label resource for account type " + accountType); } } return label; diff --git a/src/com/android/settings/applications/ApplicationsState.java b/src/com/android/settings/applications/ApplicationsState.java index 11e4aae..31fd078 100644 --- a/src/com/android/settings/applications/ApplicationsState.java +++ b/src/com/android/settings/applications/ApplicationsState.java @@ -392,6 +392,14 @@ public class ApplicationsState { for (int i=0; i<mApplications.size(); i++) { final ApplicationInfo info = mApplications.get(i); + // Need to trim out any applications that are disabled by + // something different than the user. + if (!info.enabled && info.enabledSetting + != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { + mApplications.remove(i); + i--; + continue; + } final AppEntry entry = mEntriesMap.get(info.packageName); if (entry != null) { entry.info = info; diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java index a0a568b..caf7aef 100644 --- a/src/com/android/settings/applications/InstalledAppDetails.java +++ b/src/com/android/settings/applications/InstalledAppDetails.java @@ -79,7 +79,7 @@ public class InstalledAppDetails extends Fragment implements View.OnClickListener, CompoundButton.OnCheckedChangeListener, ApplicationsState.Callbacks { private static final String TAG="InstalledAppDetails"; - static final boolean SUPPORT_DISABLE_APPS = false; + static final boolean SUPPORT_DISABLE_APPS = true; private static final boolean localLOGV = false; public static final String ARG_PACKAGE_NAME = "package"; @@ -829,7 +829,7 @@ public class InstalledAppDetails extends Fragment } else { if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { new DisableChanger(this, mAppEntry.info, mAppEntry.info.enabled ? - PackageManager.COMPONENT_ENABLED_STATE_DISABLED + PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER : PackageManager.COMPONENT_ENABLED_STATE_DEFAULT).execute((Object)null); } else { uninstallPkg(packageName); diff --git a/src/com/android/settings/bluetooth/AdvancedBluetoothSettings.java b/src/com/android/settings/bluetooth/AdvancedBluetoothSettings.java new file mode 100644 index 0000000..83371cd --- /dev/null +++ b/src/com/android/settings/bluetooth/AdvancedBluetoothSettings.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import android.content.Intent; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; + +public class AdvancedBluetoothSettings extends SettingsPreferenceFragment + implements Preference.OnPreferenceChangeListener { + + private static final String KEY_BT_DISCOVERABLE = "bt_discoverable"; + private static final String KEY_BT_DISCOVERABLE_TIMEOUT = "bt_discoverable_timeout"; + private static final String KEY_BT_NAME = "bt_name"; + private static final String KEY_BT_SHOW_RECEIVED = "bt_show_received_files"; + + /* Private intent to show the list of received files */ + private static final String BTOPP_ACTION_OPEN_RECEIVED_FILES = + "android.btopp.intent.action.OPEN_RECEIVED_FILES"; + + private BluetoothDiscoverableEnabler mDiscoverableEnabler; + private BluetoothNamePreference mNamePreference; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.bluetooth_advanced_settings); + + LocalBluetoothManager localManager = LocalBluetoothManager.getInstance(getActivity()); + if (localManager != null) { + LocalBluetoothAdapter localAdapter = localManager.getBluetoothAdapter(); + mDiscoverableEnabler = new BluetoothDiscoverableEnabler(getActivity(), + localAdapter, + (CheckBoxPreference) findPreference(KEY_BT_DISCOVERABLE), + (ListPreference) findPreference(KEY_BT_DISCOVERABLE_TIMEOUT)); + } + + mNamePreference = (BluetoothNamePreference) findPreference(KEY_BT_NAME); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + } + + @Override + public void onResume() { + super.onResume(); + mDiscoverableEnabler.resume(); + mNamePreference.resume(); + } + + @Override + public void onPause() { + super.onPause(); + + mNamePreference.pause(); + mDiscoverableEnabler.pause(); + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + if (KEY_BT_SHOW_RECEIVED.equals(preference.getKey())) { + Intent intent = new Intent(BTOPP_ACTION_OPEN_RECEIVED_FILES); + getActivity().sendBroadcast(intent); + return true; + } + + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + return true; + } +} diff --git a/src/com/android/settings/bluetooth/BluetoothEnabler.java b/src/com/android/settings/bluetooth/BluetoothEnabler.java index 79f23bb..f08e083 100644 --- a/src/com/android/settings/bluetooth/BluetoothEnabler.java +++ b/src/com/android/settings/bluetooth/BluetoothEnabler.java @@ -16,28 +16,27 @@ package com.android.settings.bluetooth; -import com.android.settings.R; -import com.android.settings.WirelessSettings; - import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.preference.Preference; -import android.preference.CheckBoxPreference; import android.provider.Settings; +import android.widget.CompoundButton; +import android.widget.Switch; import android.widget.Toast; +import com.android.settings.R; +import com.android.settings.WirelessSettings; + /** * BluetoothEnabler is a helper to manage the Bluetooth on/off checkbox * preference. It turns on/off Bluetooth and ensures the summary of the * preference reflects the current state. */ -public final class BluetoothEnabler implements Preference.OnPreferenceChangeListener { +public final class BluetoothEnabler implements CompoundButton.OnCheckedChangeListener { private final Context mContext; - private final CheckBoxPreference mCheckBox; - private final CharSequence mOriginalSummary; + private Switch mSwitch; private final LocalBluetoothAdapter mLocalAdapter; private final IntentFilter mIntentFilter; @@ -50,17 +49,15 @@ public final class BluetoothEnabler implements Preference.OnPreferenceChangeList } }; - public BluetoothEnabler(Context context, CheckBoxPreference checkBox) { + public BluetoothEnabler(Context context, Switch switch_) { mContext = context; - mCheckBox = checkBox; - mOriginalSummary = checkBox.getSummary(); - checkBox.setPersistent(false); + mSwitch = switch_; LocalBluetoothManager manager = LocalBluetoothManager.getInstance(context); if (manager == null) { // Bluetooth is not supported mLocalAdapter = null; - checkBox.setEnabled(false); + mSwitch.setEnabled(false); } else { mLocalAdapter = manager.getBluetoothAdapter(); } @@ -69,6 +66,7 @@ public final class BluetoothEnabler implements Preference.OnPreferenceChangeList public void resume() { if (mLocalAdapter == null) { + mSwitch.setEnabled(false); return; } @@ -76,7 +74,7 @@ public final class BluetoothEnabler implements Preference.OnPreferenceChangeList handleStateChanged(mLocalAdapter.getBluetoothState()); mContext.registerReceiver(mReceiver, mIntentFilter); - mCheckBox.setOnPreferenceChangeListener(this); + mSwitch.setOnCheckedChangeListener(this); } public void pause() { @@ -85,51 +83,57 @@ public final class BluetoothEnabler implements Preference.OnPreferenceChangeList } mContext.unregisterReceiver(mReceiver); - mCheckBox.setOnPreferenceChangeListener(null); + mSwitch.setOnCheckedChangeListener(null); } - public boolean onPreferenceChange(Preference preference, Object value) { - boolean enable = (Boolean) value; + public void setSwitch(Switch switch_) { + if (mSwitch == switch_) return; + mSwitch.setOnCheckedChangeListener(null); + mSwitch = switch_; + mSwitch.setOnCheckedChangeListener(this); + + int bluetoothState = BluetoothAdapter.STATE_OFF; + if (mLocalAdapter != null) bluetoothState = mLocalAdapter.getBluetoothState(); + boolean isOn = bluetoothState == BluetoothAdapter.STATE_ON; + boolean isOff = bluetoothState == BluetoothAdapter.STATE_OFF; + mSwitch.setChecked(isOn); + mSwitch.setEnabled(isOn || isOff); + } + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { // Show toast message if Bluetooth is not allowed in airplane mode - if (enable && !WirelessSettings - .isRadioAllowed(mContext, Settings.System.RADIO_BLUETOOTH)) { - Toast.makeText(mContext, R.string.wifi_in_airplane_mode, - Toast.LENGTH_SHORT).show(); - return false; + if (isChecked && + !WirelessSettings.isRadioAllowed(mContext, Settings.System.RADIO_BLUETOOTH)) { + Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show(); + // Reset switch to off + buttonView.setChecked(false); } - mLocalAdapter.setBluetoothEnabled(enable); - mCheckBox.setEnabled(false); - - // Don't update UI to opposite state until we're sure - return false; + if (mLocalAdapter != null) { + mLocalAdapter.setBluetoothEnabled(isChecked); + } + mSwitch.setEnabled(false); } void handleStateChanged(int state) { switch (state) { case BluetoothAdapter.STATE_TURNING_ON: - mCheckBox.setSummary(R.string.wifi_starting); - mCheckBox.setEnabled(false); + mSwitch.setEnabled(false); break; case BluetoothAdapter.STATE_ON: - mCheckBox.setChecked(true); - mCheckBox.setSummary(null); - mCheckBox.setEnabled(true); + mSwitch.setChecked(true); + mSwitch.setEnabled(true); break; case BluetoothAdapter.STATE_TURNING_OFF: - mCheckBox.setSummary(R.string.wifi_stopping); - mCheckBox.setEnabled(false); + mSwitch.setEnabled(false); break; case BluetoothAdapter.STATE_OFF: - mCheckBox.setChecked(false); - mCheckBox.setSummary(mOriginalSummary); - mCheckBox.setEnabled(true); + mSwitch.setChecked(false); + mSwitch.setEnabled(true); break; default: - mCheckBox.setChecked(false); - mCheckBox.setSummary(R.string.wifi_error); - mCheckBox.setEnabled(true); + mSwitch.setChecked(false); + mSwitch.setEnabled(true); } } } diff --git a/src/com/android/settings/bluetooth/BluetoothSettings.java b/src/com/android/settings/bluetooth/BluetoothSettings.java index 5e4e130..9c90b20 100644 --- a/src/com/android/settings/bluetooth/BluetoothSettings.java +++ b/src/com/android/settings/bluetooth/BluetoothSettings.java @@ -16,15 +16,20 @@ package com.android.settings.bluetooth; +import android.app.ActionBar; +import android.app.Activity; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; -import android.content.Intent; -import android.preference.CheckBoxPreference; -import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceScreen; import android.util.Log; +import android.view.Gravity; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; +import android.widget.Switch; import com.android.settings.R; @@ -35,77 +40,120 @@ import com.android.settings.R; public final class BluetoothSettings extends DeviceListPreferenceFragment { private static final String TAG = "BluetoothSettings"; - private static final String KEY_BT_CHECKBOX = "bt_checkbox"; - private static final String KEY_BT_DISCOVERABLE = "bt_discoverable"; - private static final String KEY_BT_DISCOVERABLE_TIMEOUT = "bt_discoverable_timeout"; - private static final String KEY_BT_NAME = "bt_name"; - private static final String KEY_BT_SHOW_RECEIVED = "bt_show_received_files"; + private static final int MENU_ID_MAKE_DISCOVERABLE = Menu.FIRST; + private static final int MENU_ID_SCAN = Menu.FIRST + 1; + private static final int MENU_ID_ADVANCED = Menu.FIRST + 2; - private BluetoothEnabler mEnabler; - private BluetoothDiscoverableEnabler mDiscoverableEnabler; - private BluetoothNamePreference mNamePreference; - - /* Private intent to show the list of received files */ - private static final String BTOPP_ACTION_OPEN_RECEIVED_FILES = - "android.btopp.intent.action.OPEN_RECEIVED_FILES"; + private BluetoothEnabler mBluetoothEnabler; /** Initialize the filter to show bonded devices only. */ - public BluetoothSettings() { - super(BluetoothDeviceFilter.BONDED_DEVICE_FILTER); - } + //public BluetoothSettings() { + // super(BluetoothDeviceFilter.BONDED_DEVICE_FILTER); + //} @Override void addPreferencesForActivity() { addPreferencesFromResource(R.xml.bluetooth_settings); - mEnabler = new BluetoothEnabler(getActivity(), - (CheckBoxPreference) findPreference(KEY_BT_CHECKBOX)); + Activity activity = getActivity(); + + Switch actionBarSwitch = 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); + actionBarSwitch.setPadding(0, 0, padding, 0); + activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, + ActionBar.DISPLAY_SHOW_CUSTOM); + activity.getActionBar().setCustomView(actionBarSwitch, new ActionBar.LayoutParams( + ActionBar.LayoutParams.WRAP_CONTENT, + ActionBar.LayoutParams.WRAP_CONTENT, + Gravity.CENTER_VERTICAL | Gravity.RIGHT)); + } + } + + mBluetoothEnabler = new BluetoothEnabler(activity, actionBarSwitch); - mDiscoverableEnabler = new BluetoothDiscoverableEnabler(getActivity(), - mLocalAdapter, - (CheckBoxPreference) findPreference(KEY_BT_DISCOVERABLE), - (ListPreference) findPreference(KEY_BT_DISCOVERABLE_TIMEOUT)); + if (mLocalAdapter != null && mLocalAdapter.isEnabled()) { + activity.getActionBar().setSubtitle(mLocalAdapter.getName()); + } + + // TODO activity.setTheme(android.R.style.Theme_Holo_SplitActionBarWhenNarrow); - mNamePreference = (BluetoothNamePreference) findPreference(KEY_BT_NAME); + setHasOptionsMenu(true); } @Override public void onResume() { super.onResume(); - // Repopulate (which isn't too bad since it's cached in the settings - // bluetooth manager) - addDevices(); + mBluetoothEnabler.resume(); - mEnabler.resume(); - mDiscoverableEnabler.resume(); - mNamePreference.resume(); + updateContent(mLocalAdapter.getBluetoothState()); } @Override public void onPause() { super.onPause(); - mNamePreference.pause(); - mDiscoverableEnabler.pause(); - mEnabler.pause(); + mBluetoothEnabler.pause(); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + boolean bluetoothIsEnabled = mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON; + menu.add(Menu.NONE, MENU_ID_MAKE_DISCOVERABLE, 0, R.string.bluetooth_visibility) + .setEnabled(bluetoothIsEnabled); + menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.bluetooth_preference_find_nearby_title) + .setIcon(R.drawable.ic_menu_scan_network).setEnabled(bluetoothIsEnabled); + menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.bluetooth_menu_advanced) + .setIcon(android.R.drawable.ic_menu_manage); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_ID_MAKE_DISCOVERABLE: + // TODO +// if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON) { +// onAddNetworkPressed(); +// } + return true; + case MENU_ID_SCAN: + if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON) { + mLocalAdapter.startScanning(true); + } + return true; + case MENU_ID_ADVANCED: + if (getActivity() instanceof PreferenceActivity) { + ((PreferenceActivity) getActivity()).startPreferencePanel( + AdvancedBluetoothSettings.class.getCanonicalName(), + null, + R.string.bluetooth_advanced_titlebar, null, + this, 0); + } else { + startFragment(this, AdvancedBluetoothSettings.class.getCanonicalName(), -1, null); + } + return true; + } + return super.onOptionsItemSelected(item); } private final View.OnClickListener mListener = new View.OnClickListener() { public void onClick(View v) { // User clicked on advanced options icon for a device in the list if (v.getTag() instanceof CachedBluetoothDevice) { - CachedBluetoothDevice - device = (CachedBluetoothDevice) v.getTag(); + CachedBluetoothDevice device = (CachedBluetoothDevice) v.getTag(); Preference pref = new Preference(getActivity()); pref.setTitle(device.getName()); pref.setFragment(DeviceProfilesSettings.class.getName()); pref.getExtras().putParcelable(DeviceProfilesSettings.EXTRA_DEVICE, device.getDevice()); - ((PreferenceActivity) getActivity()) - .onPreferenceStartFragment(BluetoothSettings.this, - pref); + ((PreferenceActivity) getActivity()).onPreferenceStartFragment( + BluetoothSettings.this, pref); } else { Log.w(TAG, "onClick() called for other View: " + v); } @@ -113,19 +161,54 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment { }; @Override - public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, - Preference preference) { - if (KEY_BT_SHOW_RECEIVED.equals(preference.getKey())) { - Intent intent = new Intent(BTOPP_ACTION_OPEN_RECEIVED_FILES); - getActivity().sendBroadcast(intent); - return true; + void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { + mLocalAdapter.stopScanning(); + super.onDevicePreferenceClick(btPreference); + } + + @Override + public void onBluetoothStateChanged(int bluetoothState) { + super.onBluetoothStateChanged(bluetoothState); + updateContent(bluetoothState); + } + + private void updateContent(int bluetoothState) { + final PreferenceScreen preferenceScreen = getPreferenceScreen(); + getActivity().invalidateOptionsMenu(); + int messageId = 0; + + switch (bluetoothState) { + case BluetoothAdapter.STATE_ON: + preferenceScreen.removeAll(); + // Repopulate (which isn't too bad since it's cached in the settings bluetooth manager) + addDevices(); + mLocalAdapter.startScanning(false); + return; + + case BluetoothAdapter.STATE_TURNING_OFF: + int preferenceCount = preferenceScreen.getPreferenceCount(); + for (int i = 0; i < preferenceCount; i++) { + preferenceScreen.getPreference(i).setEnabled(false); + } + return; + + case BluetoothAdapter.STATE_OFF: + messageId = R.string.bluetooth_empty_list_bluetooth_off; + break; + + case BluetoothAdapter.STATE_TURNING_ON: + messageId = R.string.bluetooth_turning_on; + break; } - return super.onPreferenceTreeClick(preferenceScreen, preference); + removeAllDevices(); + // TODO: from xml, add top padding. Same as in wifi + Preference emptyListPreference = new Preference(getActivity()); + emptyListPreference.setTitle(messageId); + preferenceScreen.addPreference(emptyListPreference); } - public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, - int bondState) { + public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { if (bondState == BluetoothDevice.BOND_BONDED) { // add to "Paired devices" list after remote-initiated pairing if (mDevicePreferenceMap.get(cachedDevice) == null) { diff --git a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java index a978e23..409edb9 100644 --- a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java +++ b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java @@ -21,6 +21,7 @@ import android.bluetooth.BluetoothDevice; import android.os.Bundle; import android.preference.Preference; import android.preference.PreferenceCategory; +import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; import android.util.Log; @@ -36,7 +37,6 @@ import java.util.WeakHashMap; * * @see BluetoothSettings * @see DevicePickerFragment - * @see BluetoothFindNearby */ public abstract class DeviceListPreferenceFragment extends SettingsPreferenceFragment implements BluetoothCallback { @@ -53,7 +53,7 @@ public abstract class DeviceListPreferenceFragment extends LocalBluetoothAdapter mLocalAdapter; LocalBluetoothManager mLocalManager; - private PreferenceCategory mDeviceList; + private PreferenceGroup mDeviceListGroup; final WeakHashMap<CachedBluetoothDevice, BluetoothDevicePreference> mDevicePreferenceMap = new WeakHashMap<CachedBluetoothDevice, BluetoothDevicePreference>(); @@ -83,8 +83,13 @@ public abstract class DeviceListPreferenceFragment extends addPreferencesForActivity(); - mDeviceList = (PreferenceCategory) findPreference(KEY_BT_DEVICE_LIST); - if (mDeviceList == null) { + mDeviceListGroup = (PreferenceCategory) findPreference(KEY_BT_DEVICE_LIST); + if (mDeviceListGroup == null) { + // If null, device preferences are added directly to the root of the preference screen + mDeviceListGroup = getPreferenceScreen(); + mDeviceListGroup.setOrderingAsAdded(false); + } + if (mDeviceListGroup == null) { Log.e(TAG, "Could not find device list preference object!"); } } @@ -105,13 +110,15 @@ public abstract class DeviceListPreferenceFragment extends @Override public void onPause() { super.onPause(); - - mLocalAdapter.stopScanning(); + removeAllDevices(); mLocalManager.setForegroundActivity(null); mLocalManager.getEventManager().unregisterCallback(this); + } + void removeAllDevices() { + mLocalAdapter.stopScanning(); mDevicePreferenceMap.clear(); - mDeviceList.removeAll(); + mDeviceListGroup.removeAll(); } void addDevices() { @@ -132,7 +139,7 @@ public abstract class DeviceListPreferenceFragment extends } if (preference instanceof BluetoothDevicePreference) { - BluetoothDevicePreference btPreference = (BluetoothDevicePreference)preference; + BluetoothDevicePreference btPreference = (BluetoothDevicePreference) preference; CachedBluetoothDevice device = btPreference.getCachedDevice(); mSelectedDevice = device.getDevice(); onDevicePreferenceClick(btPreference); @@ -152,6 +159,9 @@ public abstract class DeviceListPreferenceFragment extends return; } + // No update while list shows state message + if (mLocalAdapter.getBluetoothState() != BluetoothAdapter.STATE_ON) return; + if (mFilter.matches(cachedDevice.getDevice())) { createDevicePreference(cachedDevice); } @@ -162,7 +172,7 @@ public abstract class DeviceListPreferenceFragment extends getActivity(), cachedDevice); initDevicePreference(preference); - mDeviceList.addPreference(preference); + mDeviceListGroup.addPreference(preference); mDevicePreferenceMap.put(cachedDevice, preference); } @@ -170,13 +180,14 @@ public abstract class DeviceListPreferenceFragment extends * Overridden in {@link BluetoothSettings} to add a listener. * @param preference the newly added preference */ - void initDevicePreference(BluetoothDevicePreference preference) { } + void initDevicePreference(BluetoothDevicePreference preference) { + // Does nothing by default + } public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) { - BluetoothDevicePreference preference = mDevicePreferenceMap.remove( - cachedDevice); + BluetoothDevicePreference preference = mDevicePreferenceMap.remove(cachedDevice); if (preference != null) { - mDeviceList.removePreference(preference); + mDeviceListGroup.removePreference(preference); } } @@ -185,9 +196,10 @@ public abstract class DeviceListPreferenceFragment extends } private void updateProgressUi(boolean start) { - if (mDeviceList instanceof ProgressCategory) { - ((ProgressCategory) mDeviceList).setProgress(start); + if (mDeviceListGroup instanceof ProgressCategory) { + ((ProgressCategory) mDeviceListGroup).setProgress(start); } + // else TODO Add a spinner at the end of the list to show in progress state } public void onBluetoothStateChanged(int bluetoothState) { diff --git a/src/com/android/settings/bluetooth/DevicePickerFragment.java b/src/com/android/settings/bluetooth/DevicePickerFragment.java index 126df02..3aeb7e2 100644 --- a/src/com/android/settings/bluetooth/DevicePickerFragment.java +++ b/src/com/android/settings/bluetooth/DevicePickerFragment.java @@ -89,7 +89,7 @@ public final class DevicePickerFragment extends DeviceListPreferenceFragment { super.onBluetoothStateChanged(bluetoothState); if (bluetoothState == BluetoothAdapter.STATE_ON) { - mLocalAdapter.startScanning(false); + mLocalAdapter.startScanning(false); } } diff --git a/src/com/android/settings/bluetooth/DeviceProfilesSettings.java b/src/com/android/settings/bluetooth/DeviceProfilesSettings.java index 9db4baf..ecb7112 100644 --- a/src/com/android/settings/bluetooth/DeviceProfilesSettings.java +++ b/src/com/android/settings/bluetooth/DeviceProfilesSettings.java @@ -50,7 +50,7 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment private static final String KEY_RENAME_DEVICE = "rename_device"; private static final String KEY_PROFILE_CONTAINER = "profile_container"; private static final String KEY_UNPAIR = "unpair"; - private static final String KEY_ALLOW_INCOMING = "allow_incoming"; + //private static final String KEY_ALLOW_INCOMING = "allow_incoming"; public static final String EXTRA_DEVICE = "device"; @@ -355,6 +355,7 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment mCachedDevice.unpair(); } + /* private void setIncomingFileTransfersAllowed(boolean allow) { // TODO: make an IPC call into BluetoothOpp to update Log.d(TAG, "Set allow incoming = " + allow); @@ -364,6 +365,7 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment // TODO: get this value from BluetoothOpp ??? return true; } + */ private boolean getAutoConnect(LocalBluetoothProfile prof) { return prof.isPreferred(mCachedDevice.getDevice()); diff --git a/src/com/android/settings/deviceinfo/PercentageBarChart.java b/src/com/android/settings/deviceinfo/PercentageBarChart.java index 0c71c12..95973c4 100644 --- a/src/com/android/settings/deviceinfo/PercentageBarChart.java +++ b/src/com/android/settings/deviceinfo/PercentageBarChart.java @@ -71,20 +71,21 @@ public class PercentageBarChart extends View { final int width = right - left; - int lastX = left; + float lastX = left; if (mEntries != null) { for (final Entry e : mEntries) { - final int entryWidth; - if (e.percentage == 0f) { - entryWidth = 0; + final float entryWidth; + if (e.percentage == 0.0f) { + entryWidth = 0.0f; } else { - entryWidth = Math.max(mMinTickWidth, (int) (width * e.percentage)); + entryWidth = Math.max(mMinTickWidth, width * e.percentage); } - final int nextX = lastX + entryWidth; - if (nextX >= right) { - break; + final float nextX = lastX + entryWidth; + if (nextX > right) { + canvas.drawRect(lastX, top, right, bottom, e.paint); + return; } canvas.drawRect(lastX, top, nextX, bottom, e.paint); @@ -92,7 +93,7 @@ public class PercentageBarChart extends View { } } - canvas.drawRect(lastX, top, lastX + width, bottom, mEmptyPaint); + canvas.drawRect(lastX, top, right, bottom, mEmptyPaint); } /** diff --git a/src/com/android/settings/deviceinfo/UsageBarPreference.java b/src/com/android/settings/deviceinfo/UsageBarPreference.java index e9909f1..5aeaef5 100644 --- a/src/com/android/settings/deviceinfo/UsageBarPreference.java +++ b/src/com/android/settings/deviceinfo/UsageBarPreference.java @@ -36,17 +36,17 @@ public class UsageBarPreference extends Preference { public UsageBarPreference(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - setWidgetLayoutResource(R.layout.preference_memoryusage); + setLayoutResource(R.layout.preference_memoryusage); } public UsageBarPreference(Context context) { super(context); - setWidgetLayoutResource(R.layout.preference_memoryusage); + setLayoutResource(R.layout.preference_memoryusage); } public UsageBarPreference(Context context, AttributeSet attrs) { super(context, attrs); - setWidgetLayoutResource(R.layout.preference_memoryusage); + setLayoutResource(R.layout.preference_memoryusage); } public void addEntry(float percentage, int color) { diff --git a/src/com/android/settings/fuelgauge/BatteryHistoryChart.java b/src/com/android/settings/fuelgauge/BatteryHistoryChart.java index 97ebf43..13a962d 100644 --- a/src/com/android/settings/fuelgauge/BatteryHistoryChart.java +++ b/src/com/android/settings/fuelgauge/BatteryHistoryChart.java @@ -368,9 +368,9 @@ public class BatteryHistoryChart extends View { } if (rec.batteryLevel != lastLevel || pos == 1) { lastLevel = rec.batteryLevel; - lastInteresting = pos; - mHistEnd = rec.time; } + lastInteresting = pos; + mHistEnd = rec.time; aggrStates |= rec.states; } } @@ -438,7 +438,13 @@ public class BatteryHistoryChart extends View { 2, getResources().getDisplayMetrics()); if (h > (textHeight*6)) { mLargeMode = true; - mLineWidth = textHeight/2; + if (h > (textHeight*15)) { + // Plenty of room for the chart. + mLineWidth = textHeight/2; + } else { + // Compress lines to make more room for chart. + mLineWidth = textHeight/3; + } mLevelTop = textHeight + mLineWidth; mScreenOnPaint.setARGB(255, 32, 64, 255); mGpsOnPaint.setARGB(255, 32, 64, 255); @@ -472,7 +478,8 @@ public class BatteryHistoryChart extends View { mWifiRunningOffset = mWakeLockOffset + barOffset; mGpsOnOffset = mWifiRunningOffset + (mHaveWifi ? barOffset : 0); mPhoneSignalOffset = mGpsOnOffset + (mHaveGps ? barOffset : 0); - mLevelOffset = mPhoneSignalOffset + (mHavePhoneSignal ? barOffset : 0) + mLineWidth; + mLevelOffset = mPhoneSignalOffset + (mHavePhoneSignal ? barOffset : 0) + + ((mLineWidth*3)/2); if (mHavePhoneSignal) { mPhoneSignalChart.init(w); } @@ -670,8 +677,8 @@ public class BatteryHistoryChart extends View { if (!mBatCriticalPath.isEmpty()) { canvas.drawPath(mBatCriticalPath, mBatteryCriticalPaint); } - int top = height - (mHavePhoneSignal ? mPhoneSignalOffset - (mLineWidth/2) : 0); if (mHavePhoneSignal) { + int top = height-mPhoneSignalOffset - (mLineWidth/2); mPhoneSignalChart.draw(canvas, top, mLineWidth); } if (!mScreenOnPath.isEmpty()) { diff --git a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java index a4808b0..c72f0ba 100644 --- a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java +++ b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java @@ -18,9 +18,11 @@ package com.android.settings.inputmethod; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.UserDictionarySettings; import com.android.settings.Utils; import com.android.settings.VoiceInputOutputSettings; +import android.app.Activity; import android.content.Context; import android.content.res.Configuration; import android.os.Bundle; @@ -30,12 +32,17 @@ import android.preference.PreferenceScreen; import android.provider.Settings; import android.view.inputmethod.InputMethodManager; +import java.util.Set; + public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment implements Preference.OnPreferenceChangeListener{ private static final String KEY_PHONE_LANGUAGE = "phone_language"; private static final String KEY_CURRENT_INPUT_METHOD = "current_input_method"; private static final String KEY_INPUT_METHOD_SELECTOR = "input_method_selector"; + private static final String KEY_LANGUAGE_SETTINGS_CATEGORY = "language_settings_category"; + private static final String KEY_USER_DICTIONARY_SETTINGS = "key_user_dictionary_settings"; + private int mDefaultInputMethodSelectorVisibility = 0; private ListPreference mShowInputMethodSelectorPref; @@ -77,6 +84,28 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment } } + private void updateUserDictionaryPreference(Preference userDictionaryPreference) { + final Activity activity = getActivity(); + final Set<String> localeList = UserDictionaryList.getUserDictionaryLocalesList(activity); + if (localeList.size() <= 1) { + userDictionaryPreference.setTitle(R.string.user_dict_single_settings_title); + userDictionaryPreference.setFragment(UserDictionarySettings.class.getName()); + // If the size of localeList is 0, we don't set the locale parameter in the + // extras. This will be interpreted by the UserDictionarySettings class as + // meaning "the current locale". + // Note that with the current code for UserDictionaryList#getUserDictionaryLocalesList() + // the locale list always has at least one element, since it always includes the current + // locale explicitly. @see UserDictionaryList.getUserDictionaryLocalesList(). + if (localeList.size() == 1) { + final String locale = (String)localeList.toArray()[0]; + userDictionaryPreference.getExtras().putString("locale", locale); + } + } else { + userDictionaryPreference.setTitle(R.string.user_dict_multiple_settings_title); + userDictionaryPreference.setFragment(UserDictionaryList.class.getName()); + } + } + @Override public void onResume() { super.onResume(); @@ -89,6 +118,7 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment } } + updateUserDictionaryPreference(findPreference(KEY_USER_DICTIONARY_SETTINGS)); mShowInputMethodSelectorPref.setOnPreferenceChangeListener(this); } diff --git a/src/com/android/settings/inputmethod/InputMethodAndSubtypeEnabler.java b/src/com/android/settings/inputmethod/InputMethodAndSubtypeEnabler.java index 6e1d4d1..43d54a2 100644 --- a/src/com/android/settings/inputmethod/InputMethodAndSubtypeEnabler.java +++ b/src/com/android/settings/inputmethod/InputMethodAndSubtypeEnabler.java @@ -188,7 +188,8 @@ public class InputMethodAndSubtypeEnabler extends SettingsPreferenceFragment { private PreferenceScreen createPreferenceHierarchy() { // Root - PreferenceScreen root = getPreferenceManager().createPreferenceScreen(getActivity()); + final PreferenceScreen root = getPreferenceManager().createPreferenceScreen(getActivity()); + final Context context = getActivity(); int N = (mInputMethodProperties == null ? 0 : mInputMethodProperties.size()); @@ -202,7 +203,7 @@ public class InputMethodAndSubtypeEnabler extends SettingsPreferenceFragment { if (!TextUtils.isEmpty(mInputMethodId) && !mInputMethodId.equals(imiId)) { continue; } - PreferenceCategory keyboardSettingsCategory = new PreferenceCategory(getActivity()); + PreferenceCategory keyboardSettingsCategory = new PreferenceCategory(context); root.addPreference(keyboardSettingsCategory); PackageManager pm = getPackageManager(); CharSequence label = imi.loadLabel(pm); @@ -210,31 +211,22 @@ public class InputMethodAndSubtypeEnabler extends SettingsPreferenceFragment { keyboardSettingsCategory.setTitle(label); keyboardSettingsCategory.setKey(imiId); // TODO: Use toggle Preference if images are ready. - CheckBoxPreference autoCB = new CheckBoxPreference(getActivity()); + CheckBoxPreference autoCB = new CheckBoxPreference(context); autoCB.setTitle(R.string.use_system_language_to_select_input_method_subtypes); mSubtypeAutoSelectionCBMap.put(imiId, autoCB); keyboardSettingsCategory.addPreference(autoCB); - PreferenceCategory activeInputMethodsCategory = new PreferenceCategory(getActivity()); + PreferenceCategory activeInputMethodsCategory = new PreferenceCategory(context); activeInputMethodsCategory.setTitle(R.string.active_input_method_subtypes); root.addPreference(activeInputMethodsCategory); ArrayList<Preference> subtypePreferences = new ArrayList<Preference>(); if (subtypeCount > 0) { for (int j = 0; j < subtypeCount; ++j) { - InputMethodSubtype subtype = imi.getSubtypeAt(j); - CharSequence subtypeLabel; - int nameResId = subtype.getNameResId(); - if (nameResId != 0) { - subtypeLabel = pm.getText(imi.getPackageName(), nameResId, - imi.getServiceInfo().applicationInfo); - } else { - String mode = subtype.getMode(); - CharSequence language = subtype.getLocale(); - subtypeLabel = (mode == null ? "" : mode) + "," - + (language == null ? "" : language); - } - CheckBoxPreference chkbxPref = new CheckBoxPreference(getActivity()); + final InputMethodSubtype subtype = imi.getSubtypeAt(j); + final CharSequence subtypeLabel = subtype.getDisplayName(context, + imi.getPackageName(), imi.getServiceInfo().applicationInfo); + final CheckBoxPreference chkbxPref = new CheckBoxPreference(context); chkbxPref.setKey(imiId + subtype.hashCode()); chkbxPref.setTitle(subtypeLabel); activeInputMethodsCategory.addPreference(chkbxPref); diff --git a/src/com/android/settings/inputmethod/InputMethodConfig.java b/src/com/android/settings/inputmethod/InputMethodConfig.java index 2cfe35d..393292e 100644 --- a/src/com/android/settings/inputmethod/InputMethodConfig.java +++ b/src/com/android/settings/inputmethod/InputMethodConfig.java @@ -266,7 +266,6 @@ public class InputMethodConfig extends SettingsPreferenceFragment { private void updateActiveInputMethodsSummary() { final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - final PackageManager pm = getPackageManager(); for (InputMethodInfo imi: mActiveInputMethodsPrefMap.keySet()) { Preference pref = mActiveInputMethodsPrefMap.get(imi); List<InputMethodSubtype> subtypes = imm.getEnabledInputMethodSubtypeList(imi, true); @@ -276,8 +275,9 @@ public class InputMethodConfig extends SettingsPreferenceFragment { if (subtypeAdded) { summary.append(", "); } - summary.append(pm.getText(imi.getPackageName(), subtype.getNameResId(), - imi.getServiceInfo().applicationInfo)); + final CharSequence subtypeLabel = subtype.getDisplayName(getActivity(), + imi.getPackageName(), imi.getServiceInfo().applicationInfo); + summary.append(subtypeLabel); subtypeAdded = true; } pref.setSummary(summary.toString()); diff --git a/src/com/android/settings/inputmethod/InputMethodDialogActivity.java b/src/com/android/settings/inputmethod/InputMethodDialogActivity.java new file mode 100644 index 0000000..4efbf05 --- /dev/null +++ b/src/com/android/settings/inputmethod/InputMethodDialogActivity.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.inputmethod; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.view.inputmethod.InputMethodManager; + +public class InputMethodDialogActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)). + showInputMethodPicker(); + finish(); + } +} diff --git a/src/com/android/settings/inputmethod/UserDictionaryList.java b/src/com/android/settings/inputmethod/UserDictionaryList.java new file mode 100644 index 0000000..5db2841 --- /dev/null +++ b/src/com/android/settings/inputmethod/UserDictionaryList.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.inputmethod; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.UserDictionarySettings; +import com.android.settings.Utils; + +import android.app.Activity; +import android.content.Intent; +import android.database.Cursor; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceGroup; +import android.provider.UserDictionary; + +import java.util.Locale; +import java.util.Set; +import java.util.TreeSet; + +public class UserDictionaryList extends SettingsPreferenceFragment { + + private static final String USER_DICTIONARY_SETTINGS_INTENT_ACTION = + "android.settings.USER_DICTIONARY_SETTINGS"; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getActivity())); + } + + static Set<String> getUserDictionaryLocalesList(Activity activity) { + final Cursor cursor = activity.managedQuery(UserDictionary.Words.CONTENT_URI, + new String[] { UserDictionary.Words.LOCALE }, + null, null, null); + final Set<String> localeList = new TreeSet<String>(); + if (cursor.moveToFirst()) { + final int columnIndex = cursor.getColumnIndex(UserDictionary.Words.LOCALE); + do { + String locale = cursor.getString(columnIndex); + localeList.add(null != locale ? locale : ""); + } while (cursor.moveToNext()); + } + localeList.add(Locale.getDefault().toString()); + return localeList; + } + + /** + * Creates the entries that allow the user to go into the user dictionary for each locale. + * @param userDictGroup The group to put the settings in. + */ + protected void createUserDictSettings(PreferenceGroup userDictGroup) { + final Activity activity = getActivity(); + userDictGroup.removeAll(); + final Set<String> localeList = UserDictionaryList.getUserDictionaryLocalesList(activity); + + if (localeList.isEmpty()) { + userDictGroup.addPreference(createUserDictionaryPreference(null, activity)); + } else { + for (String locale : localeList) { + userDictGroup.addPreference(createUserDictionaryPreference(locale, activity)); + } + } + } + + /** + * Create a single User Dictionary Preference object, with its parameters set. + * @param locale The locale for which this user dictionary is for. + * @return The corresponding preference. + */ + protected Preference createUserDictionaryPreference(String locale, Activity activity) { + final Preference newPref = new Preference(getActivity()); + final Intent intent = new Intent(USER_DICTIONARY_SETTINGS_INTENT_ACTION); + if (null == locale) { + newPref.setTitle(Locale.getDefault().getDisplayName()); + } else { + if ("".equals(locale)) + newPref.setTitle(getString(R.string.user_dict_settings_all_languages)); + else + newPref.setTitle(Utils.createLocaleFromString(locale).getDisplayName()); + intent.putExtra("locale", locale); + newPref.getExtras().putString("locale", locale); + } + newPref.setIntent(intent); + newPref.setFragment(UserDictionarySettings.class.getName()); + return newPref; + } + + @Override + public void onResume() { + super.onResume(); + createUserDictSettings(getPreferenceScreen()); + } +} diff --git a/src/com/android/settings/net/NetworkPolicyEditor.java b/src/com/android/settings/net/NetworkPolicyEditor.java new file mode 100644 index 0000000..c50a490 --- /dev/null +++ b/src/com/android/settings/net/NetworkPolicyEditor.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.net; + +import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER; +import static android.net.NetworkTemplate.MATCH_MOBILE_4G; +import static android.net.NetworkTemplate.MATCH_MOBILE_ALL; +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.net.INetworkPolicyManager; +import android.net.NetworkPolicy; +import android.net.NetworkTemplate; +import android.os.AsyncTask; +import android.os.RemoteException; + +import com.android.internal.util.Objects; +import com.google.android.collect.Lists; + +import java.util.ArrayList; + +/** + * Utility class to modify list of {@link NetworkPolicy}. Specifically knows + * about which policies can coexist. + */ +public class NetworkPolicyEditor { + // TODO: be more robust when missing policies from service + + private INetworkPolicyManager mPolicyService; + private ArrayList<NetworkPolicy> mPolicies = Lists.newArrayList(); + + public NetworkPolicyEditor(INetworkPolicyManager policyService) { + mPolicyService = checkNotNull(policyService); + } + + public void read() { + try { + final NetworkPolicy[] policies = mPolicyService.getNetworkPolicies(); + mPolicies.clear(); + for (NetworkPolicy policy : policies) { + mPolicies.add(policy); + } + } catch (RemoteException e) { + throw new RuntimeException("problem reading policies", e); + } + } + + public void writeAsync() { + // TODO: consider making more robust by passing through service + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + write(); + return null; + } + }.execute(); + } + + public void write() { + try { + final NetworkPolicy[] policies = mPolicies.toArray(new NetworkPolicy[mPolicies.size()]); + mPolicyService.setNetworkPolicies(policies); + } catch (RemoteException e) { + throw new RuntimeException("problem reading policies", e); + } + } + + public NetworkPolicy getPolicy(NetworkTemplate template) { + for (NetworkPolicy policy : mPolicies) { + if (policy.template.equals(template)) { + return policy; + } + } + return null; + } + + public void setPolicyCycleDay(NetworkTemplate template, int cycleDay) { + getPolicy(template).cycleDay = cycleDay; + writeAsync(); + } + + public void setPolicyWarningBytes(NetworkTemplate template, long warningBytes) { + getPolicy(template).warningBytes = warningBytes; + writeAsync(); + } + + public void setPolicyLimitBytes(NetworkTemplate template, long limitBytes) { + getPolicy(template).limitBytes = limitBytes; + writeAsync(); + } + + public boolean isMobilePolicySplit(String subscriberId) { + boolean has3g = false; + boolean has4g = false; + for (NetworkPolicy policy : mPolicies) { + final NetworkTemplate template = policy.template; + if (Objects.equal(subscriberId, template.getSubscriberId())) { + switch (template.getMatchRule()) { + case MATCH_MOBILE_3G_LOWER: + has3g = true; + break; + case MATCH_MOBILE_4G: + has4g = true; + break; + } + } + } + return has3g && has4g; + } + + public void setMobilePolicySplit(String subscriberId, boolean split) { + final boolean beforeSplit = isMobilePolicySplit(subscriberId); + + final NetworkTemplate template3g = new NetworkTemplate(MATCH_MOBILE_3G_LOWER, subscriberId); + final NetworkTemplate template4g = new NetworkTemplate(MATCH_MOBILE_4G, subscriberId); + final NetworkTemplate templateAll = new NetworkTemplate(MATCH_MOBILE_ALL, subscriberId); + + if (split == beforeSplit) { + // already in requested state; skip + return; + + } else if (beforeSplit && !split) { + // combine, picking most restrictive policy + final NetworkPolicy policy3g = getPolicy(template3g); + final NetworkPolicy policy4g = getPolicy(template4g); + + final NetworkPolicy restrictive = policy3g.compareTo(policy4g) < 0 ? policy3g + : policy4g; + mPolicies.remove(policy3g); + mPolicies.remove(policy4g); + mPolicies.add( + new NetworkPolicy(templateAll, restrictive.cycleDay, restrictive.warningBytes, + restrictive.limitBytes)); + writeAsync(); + + } else if (!beforeSplit && split) { + // duplicate existing policy into two rules + final NetworkPolicy policyAll = getPolicy(templateAll); + mPolicies.remove(policyAll); + mPolicies.add(new NetworkPolicy( + template3g, policyAll.cycleDay, policyAll.warningBytes, policyAll.limitBytes)); + mPolicies.add(new NetworkPolicy( + template4g, policyAll.cycleDay, policyAll.warningBytes, policyAll.limitBytes)); + writeAsync(); + + } + } + +} diff --git a/src/com/android/settings/vpn/VpnSettings.java b/src/com/android/settings/vpn/VpnSettings.java index 5d75b6a..9b96761 100644 --- a/src/com/android/settings/vpn/VpnSettings.java +++ b/src/com/android/settings/vpn/VpnSettings.java @@ -55,6 +55,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.nio.charset.Charsets; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -698,7 +699,7 @@ public class VpnSettings extends SettingsPreferenceFragment } private boolean isKeyStoreUnlocked() { - return mKeyStore.test() == KeyStore.NO_ERROR; + return mKeyStore.state() == KeyStore.State.UNLOCKED; } // Returns true if the profile needs to access keystore @@ -1034,7 +1035,7 @@ public class VpnSettings extends SettingsPreferenceFragment String presharedKey = pskProfile.getPresharedKey(); String key = KEY_PREFIX_IPSEC_PSK + p.getId(); if (!TextUtils.isEmpty(presharedKey) && - !mKeyStore.put(key, presharedKey)) { + !mKeyStore.put(key, presharedKey.getBytes(Charsets.UTF_8))) { Log.e(TAG, "keystore write failed: key=" + key); } pskProfile.setPresharedKey(key); @@ -1046,7 +1047,7 @@ public class VpnSettings extends SettingsPreferenceFragment if (l2tpProfile.isSecretEnabled()) { String secret = l2tpProfile.getSecretString(); if (!TextUtils.isEmpty(secret) && - !mKeyStore.put(key, secret)) { + !mKeyStore.put(key, secret.getBytes(Charsets.UTF_8))) { Log.e(TAG, "keystore write failed: key=" + key); } l2tpProfile.setSecretString(key); diff --git a/src/com/android/settings/widget/ChartAxis.java b/src/com/android/settings/widget/ChartAxis.java new file mode 100644 index 0000000..2b21d28 --- /dev/null +++ b/src/com/android/settings/widget/ChartAxis.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.widget; + +/** + * Axis along a {@link ChartView} that knows how to convert between raw point + * and screen coordinate systems. + */ +public interface ChartAxis { + + public void setBounds(long min, long max); + public void setSize(float size); + + public float convertToPoint(long value); + public long convertToValue(float point); + + public CharSequence getLabel(long value); + + public float[] getTickPoints(); + +} diff --git a/src/com/android/settings/widget/ChartGridView.java b/src/com/android/settings/widget/ChartGridView.java new file mode 100644 index 0000000..be71890 --- /dev/null +++ b/src/com/android/settings/widget/ChartGridView.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.widget; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Paint.Style; +import android.view.View; + +import com.google.common.base.Preconditions; + +/** + * Background of {@link ChartView} that renders grid lines as requested by + * {@link ChartAxis#getTickPoints()}. + */ +public class ChartGridView extends View { + + private final ChartAxis mHoriz; + private final ChartAxis mVert; + + private final Paint mPaintHoriz; + private final Paint mPaintVert; + + public ChartGridView(Context context, ChartAxis horiz, ChartAxis vert) { + super(context); + + mHoriz = Preconditions.checkNotNull(horiz, "missing horiz"); + mVert = Preconditions.checkNotNull(vert, "missing vert"); + + setWillNotDraw(false); + + // TODO: convert these colors to resources + mPaintHoriz = new Paint(); + mPaintHoriz.setColor(Color.parseColor("#667bb5")); + mPaintHoriz.setStrokeWidth(2.0f); + mPaintHoriz.setStyle(Style.STROKE); + mPaintHoriz.setAntiAlias(true); + + mPaintVert = new Paint(); + mPaintVert.setColor(Color.parseColor("#28262c")); + mPaintVert.setStrokeWidth(1.0f); + mPaintVert.setStyle(Style.STROKE); + mPaintVert.setAntiAlias(true); + } + + @Override + protected void onDraw(Canvas canvas) { + final int width = getWidth(); + final int height = getHeight(); + + final float[] vertTicks = mVert.getTickPoints(); + for (float y : vertTicks) { + canvas.drawLine(0, y, width, y, mPaintVert); + } + + final float[] horizTicks = mHoriz.getTickPoints(); + for (float x : horizTicks) { + canvas.drawLine(x, 0, x, height, mPaintHoriz); + } + + canvas.drawRect(0, 0, width, height, mPaintHoriz); + } +} diff --git a/src/com/android/settings/widget/ChartNetworkSeriesView.java b/src/com/android/settings/widget/ChartNetworkSeriesView.java new file mode 100644 index 0000000..780ca46 --- /dev/null +++ b/src/com/android/settings/widget/ChartNetworkSeriesView.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.widget; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Paint.Style; +import android.graphics.Path; +import android.graphics.RectF; +import android.net.NetworkStatsHistory; +import android.util.Log; +import android.view.View; + +import com.google.common.base.Preconditions; + +/** + * {@link NetworkStatsHistory} series to render inside a {@link ChartView}, + * using {@link ChartAxis} to map into screen coordinates. + */ +public class ChartNetworkSeriesView extends View { + private static final String TAG = "ChartNetworkSeriesView"; + private static final boolean LOGD = true; + + private final ChartAxis mHoriz; + private final ChartAxis mVert; + + private Paint mPaintStroke; + private Paint mPaintFill; + private Paint mPaintFillDisabled; + + private NetworkStatsHistory mStats; + + private Path mPathStroke; + private Path mPathFill; + + private ChartSweepView mSweep1; + private ChartSweepView mSweep2; + + public ChartNetworkSeriesView(Context context, ChartAxis horiz, ChartAxis vert) { + super(context); + + mHoriz = Preconditions.checkNotNull(horiz, "missing horiz"); + mVert = Preconditions.checkNotNull(vert, "missing vert"); + + setChartColor(Color.parseColor("#24aae1"), Color.parseColor("#c050ade5"), + Color.parseColor("#88566abc")); + + mPathStroke = new Path(); + mPathFill = new Path(); + } + + public void setChartColor(int stroke, int fill, int disabled) { + mPaintStroke = new Paint(); + mPaintStroke.setStrokeWidth(6.0f); + mPaintStroke.setColor(stroke); + mPaintStroke.setStyle(Style.STROKE); + mPaintStroke.setAntiAlias(true); + + mPaintFill = new Paint(); + mPaintFill.setColor(fill); + mPaintFill.setStyle(Style.FILL); + mPaintFill.setAntiAlias(true); + + mPaintFillDisabled = new Paint(); + mPaintFillDisabled.setColor(disabled); + mPaintFillDisabled.setStyle(Style.FILL); + mPaintFillDisabled.setAntiAlias(true); + } + + public void bindNetworkStats(NetworkStatsHistory stats) { + mStats = stats; + + mPathStroke.reset(); + mPathFill.reset(); + } + + public void bindSweepRange(ChartSweepView sweep1, ChartSweepView sweep2) { + // TODO: generalize to support vertical sweeps + // TODO: enforce that both sweeps are along same dimension + + mSweep1 = Preconditions.checkNotNull(sweep1, "missing sweep1"); + mSweep2 = Preconditions.checkNotNull(sweep2, "missing sweep2"); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + generatePath(); + } + + /** + * Erase any existing {@link Path} and generate series outline based on + * currently bound {@link NetworkStatsHistory} data. + */ + public void generatePath() { + if (LOGD) Log.d(TAG, "generatePath()"); + + mPathStroke.reset(); + mPathFill.reset(); + + // bail when not enough stats to render + if (mStats == null || mStats.bucketCount < 2) return; + + final int width = getWidth(); + final int height = getHeight(); + + boolean started = false; + float firstX = 0; + float lastX = 0; + float lastY = 0; + + // TODO: count fractional data from first bucket crossing start; + // currently it only accepts first full bucket. + + long totalData = 0; + + for (int i = 0; i < mStats.bucketCount; i++) { + final float x = mHoriz.convertToPoint(mStats.bucketStart[i]); + final float y = mVert.convertToPoint(totalData); + + // skip until we find first stats on screen + if (i > 0 && !started && x > 0) { + mPathStroke.moveTo(lastX, lastY); + mPathFill.moveTo(lastX, lastY); + started = true; + firstX = x; + } + + if (started) { + mPathStroke.lineTo(x, y); + mPathFill.lineTo(x, y); + totalData += mStats.rx[i] + mStats.tx[i]; + } + + // skip if beyond view + if (x > width) break; + + lastX = x; + lastY = y; + } + + if (LOGD) { + final RectF bounds = new RectF(); + mPathFill.computeBounds(bounds, true); + Log.d(TAG, "onLayout() rendered with bounds=" + bounds.toString() + " and totalData=" + + totalData); + } + + // drop to bottom of graph from current location + mPathFill.lineTo(lastX, height); + mPathFill.lineTo(firstX, height); + } + + @Override + protected void onDraw(Canvas canvas) { + + // clip to sweep area + final float sweep1 = mSweep1.getPoint(); + final float sweep2 = mSweep2.getPoint(); + final float sweepLeft = Math.min(sweep1, sweep2); + final float sweepRight = Math.max(sweep1, sweep2); + + int save; + + save = canvas.save(); + canvas.clipRect(0, 0, sweepLeft, getHeight()); + canvas.drawPath(mPathFill, mPaintFillDisabled); + canvas.restoreToCount(save); + + save = canvas.save(); + canvas.clipRect(sweepRight, 0, getWidth(), getHeight()); + canvas.drawPath(mPathFill, mPaintFillDisabled); + canvas.restoreToCount(save); + + save = canvas.save(); + canvas.clipRect(sweepLeft, 0, sweepRight, getHeight()); + canvas.drawPath(mPathFill, mPaintFill); + canvas.drawPath(mPathStroke, mPaintStroke); + canvas.restoreToCount(save); + + } +} diff --git a/src/com/android/settings/widget/ChartSweepView.java b/src/com/android/settings/widget/ChartSweepView.java new file mode 100644 index 0000000..788caad --- /dev/null +++ b/src/com/android/settings/widget/ChartSweepView.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.widget; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.DashPathEffect; +import android.graphics.Paint; +import android.graphics.Paint.Style; +import android.view.MotionEvent; +import android.view.View; + +import com.google.common.base.Preconditions; + +/** + * Sweep across a {@link ChartView} at a specific {@link ChartAxis} value, which + * a user can drag. + */ +public class ChartSweepView extends View { + + private final Paint mPaintSweep; + private final Paint mPaintSweepDisabled; + private final Paint mPaintShadow; + + private final ChartAxis mAxis; + private long mValue; + + public interface OnSweepListener { + public void onSweep(ChartSweepView sweep, boolean sweepDone); + } + + private OnSweepListener mListener; + + private boolean mHorizontal; + private MotionEvent mTracking; + + public ChartSweepView(Context context, ChartAxis axis, long value, int color) { + super(context); + + mAxis = Preconditions.checkNotNull(axis, "missing axis"); + mValue = value; + + mPaintSweep = new Paint(); + mPaintSweep.setColor(color); + mPaintSweep.setStrokeWidth(3.0f); + mPaintSweep.setStyle(Style.FILL_AND_STROKE); + mPaintSweep.setAntiAlias(true); + + mPaintSweepDisabled = new Paint(); + mPaintSweepDisabled.setColor(color); + mPaintSweepDisabled.setStrokeWidth(1.5f); + mPaintSweepDisabled.setStyle(Style.FILL_AND_STROKE); + mPaintSweepDisabled.setPathEffect(new DashPathEffect(new float[] { 5, 5 }, 0)); + mPaintSweepDisabled.setAntiAlias(true); + + mPaintShadow = new Paint(); + mPaintShadow.setColor(Color.BLACK); + mPaintShadow.setStrokeWidth(6.0f); + mPaintShadow.setStyle(Style.FILL_AND_STROKE); + mPaintShadow.setAntiAlias(true); + + } + + public void addOnSweepListener(OnSweepListener listener) { + mListener = listener; + } + + private void dispatchOnSweep(boolean sweepDone) { + if (mListener != null) { + mListener.onSweep(this, sweepDone); + } + } + + public ChartAxis getAxis() { + return mAxis; + } + + public void setValue(long value) { + mValue = value; + } + + public long getValue() { + return mValue; + } + + public float getPoint() { + return mAxis.convertToPoint(mValue); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!isEnabled()) return false; + + final View parent = (View) getParent(); + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: { + mTracking = event.copy(); + return true; + } + case MotionEvent.ACTION_MOVE: { + getParent().requestDisallowInterceptTouchEvent(true); + + if (mHorizontal) { + setTranslationY(event.getRawY() - mTracking.getRawY()); + final float point = (getTop() + getTranslationY() + (getHeight() / 2)) + - parent.getPaddingTop(); + mValue = mAxis.convertToValue(point); + dispatchOnSweep(false); + } else { + setTranslationX(event.getRawX() - mTracking.getRawX()); + final float point = (getLeft() + getTranslationX() + (getWidth() / 2)) + - parent.getPaddingLeft(); + mValue = mAxis.convertToValue(point); + dispatchOnSweep(false); + } + return true; + } + case MotionEvent.ACTION_UP: { + mTracking = null; + setTranslationX(0); + setTranslationY(0); + requestLayout(); + dispatchOnSweep(true); + return true; + } + default: { + return false; + } + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // need at least 50px in each direction for grippies + // TODO: provide this value through params + setMeasuredDimension(50, 50); + } + + @Override + protected void onDraw(Canvas canvas) { + + // draw line across larger dimension + final int width = getWidth(); + final int height = getHeight(); + + mHorizontal = width > height; + + final Paint linePaint = isEnabled() ? mPaintSweep : mPaintSweepDisabled; + + if (mHorizontal) { + final int centerY = height / 2; + final int endX = width - height; + + canvas.drawLine(0, centerY, endX, centerY, mPaintShadow); + canvas.drawLine(0, centerY, endX, centerY, linePaint); + canvas.drawCircle(endX, centerY, 4.0f, mPaintShadow); + canvas.drawCircle(endX, centerY, 4.0f, mPaintSweep); + } else { + final int centerX = width / 2; + final int endY = height - width; + + canvas.drawLine(centerX, 0, centerX, endY, mPaintShadow); + canvas.drawLine(centerX, 0, centerX, endY, linePaint); + canvas.drawCircle(centerX, endY, 4.0f, mPaintShadow); + canvas.drawCircle(centerX, endY, 4.0f, mPaintSweep); + } + } + +} diff --git a/src/com/android/settings/widget/ChartView.java b/src/com/android/settings/widget/ChartView.java new file mode 100644 index 0000000..3e5fc50 --- /dev/null +++ b/src/com/android/settings/widget/ChartView.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.widget; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static com.google.common.base.Preconditions.checkNotNull; + +import android.content.Context; +import android.graphics.Rect; +import android.util.Log; +import android.view.Gravity; +import android.view.View; +import android.widget.FrameLayout; + +/** + * Container for two-dimensional chart, drawn with a combination of + * {@link ChartGridView}, {@link ChartNetworkSeriesView} and {@link ChartSweepView} + * children. The entire chart uses {@link ChartAxis} to map between raw values + * and screen coordinates. + */ +public class ChartView extends FrameLayout { + private static final String TAG = "ChartView"; + + // TODO: extend something that supports two-dimensional scrolling + + final ChartAxis mHoriz; + final ChartAxis mVert; + + private Rect mContent = new Rect(); + + public ChartView(Context context, ChartAxis horiz, ChartAxis vert) { + super(context); + + mHoriz = checkNotNull(horiz, "missing horiz"); + mVert = checkNotNull(vert, "missing vert"); + + setClipToPadding(false); + setClipChildren(false); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + mContent.set(getPaddingLeft(), getPaddingTop(), r - l - getPaddingRight(), + b - t - getPaddingBottom()); + final int width = mContent.width(); + final int height = mContent.height(); + + // no scrolling yet, so tell dimensions to fill exactly + mHoriz.setSize(width); + mVert.setSize(height); + + final Rect parentRect = new Rect(); + final Rect childRect = new Rect(); + + for (int i = 0; i < getChildCount(); i++) { + final View child = getChildAt(i); + final LayoutParams params = (LayoutParams) child.getLayoutParams(); + + parentRect.set(mContent); + + if (child instanceof ChartNetworkSeriesView || child instanceof ChartGridView) { + // series are always laid out to fill entire graph area + // TODO: handle scrolling for series larger than content area + Gravity.apply(params.gravity, width, height, parentRect, childRect); + child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom); + + } else if (child instanceof ChartSweepView) { + // sweep is always placed along specific dimension + final ChartSweepView sweep = (ChartSweepView) child; + final ChartAxis axis = sweep.getAxis(); + final float point = sweep.getPoint(); + + if (axis == mHoriz) { + parentRect.left = parentRect.right = (int) point + getPaddingLeft(); + parentRect.bottom += child.getMeasuredWidth(); + Gravity.apply(params.gravity, child.getMeasuredWidth(), parentRect.height(), + parentRect, childRect); + + } else if (axis == mVert) { + parentRect.top = parentRect.bottom = (int) point + getPaddingTop(); + parentRect.right += child.getMeasuredHeight(); + Gravity.apply(params.gravity, parentRect.width(), child.getMeasuredHeight(), + parentRect, childRect); + + } else { + throw new IllegalStateException("unexpected axis"); + } + } + + child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom); + } + } + + public static LayoutParams buildChartParams() { + final LayoutParams params = new LayoutParams(MATCH_PARENT, MATCH_PARENT); + params.gravity = Gravity.LEFT | Gravity.BOTTOM; + return params; + } + + public static LayoutParams buildSweepParams() { + final LayoutParams params = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT); + params.gravity = Gravity.CENTER; + return params; + } + +} diff --git a/src/com/android/settings/widget/DataUsageChartView.java b/src/com/android/settings/widget/DataUsageChartView.java new file mode 100644 index 0000000..6c62fa8 --- /dev/null +++ b/src/com/android/settings/widget/DataUsageChartView.java @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.widget; + +import android.content.Context; +import android.graphics.Color; +import android.net.NetworkPolicy; +import android.net.NetworkStatsHistory; +import android.text.format.DateUtils; +import android.view.View; + +import com.android.settings.widget.ChartSweepView.OnSweepListener; + +/** + * Specific {@link ChartView} that displays {@link ChartNetworkSeriesView} along + * with {@link ChartSweepView} for inspection ranges and warning/limits. + */ +public class DataUsageChartView extends ChartView { + + private static final long KB_IN_BYTES = 1024; + private static final long MB_IN_BYTES = KB_IN_BYTES * 1024; + private static final long GB_IN_BYTES = MB_IN_BYTES * 1024; + + private ChartNetworkSeriesView mSeries; + + // TODO: limit sweeps at graph boundaries + private ChartSweepView mSweepTime1; + private ChartSweepView mSweepTime2; + private ChartSweepView mSweepDataWarn; + private ChartSweepView mSweepDataLimit; + + public interface DataUsageChartListener { + public void onInspectRangeChanged(); + public void onWarningChanged(); + public void onLimitChanged(); + } + + private DataUsageChartListener mListener; + + private static ChartAxis buildTimeAxis() { + return new TimeAxis(); + } + + private static ChartAxis buildDataAxis() { + return new InvertedChartAxis(new DataAxis()); + } + + public DataUsageChartView(Context context) { + super(context, buildTimeAxis(), buildDataAxis()); + setPadding(20, 20, 20, 20); + + addView(new ChartGridView(context, mHoriz, mVert), buildChartParams()); + + mSeries = new ChartNetworkSeriesView(context, mHoriz, mVert); + addView(mSeries, buildChartParams()); + + mSweepTime1 = new ChartSweepView(context, mHoriz, 0L, Color.parseColor("#ffffff")); + mSweepTime2 = new ChartSweepView(context, mHoriz, 0L, Color.parseColor("#ffffff")); + mSweepDataWarn = new ChartSweepView(context, mVert, 0L, Color.parseColor("#f7931d")); + mSweepDataLimit = new ChartSweepView(context, mVert, 0L, Color.parseColor("#be1d2c")); + + addView(mSweepTime1, buildSweepParams()); + addView(mSweepTime2, buildSweepParams()); + addView(mSweepDataWarn, buildSweepParams()); + addView(mSweepDataLimit, buildSweepParams()); + + mSeries.bindSweepRange(mSweepTime1, mSweepTime2); + + mSweepDataWarn.addOnSweepListener(mWarningListener); + mSweepDataLimit.addOnSweepListener(mLimitListener); + + mSweepTime1.addOnSweepListener(mSweepListener); + mSweepTime2.addOnSweepListener(mSweepListener); + + mSweepDataWarn.setVisibility(View.INVISIBLE); + mSweepDataLimit.setVisibility(View.INVISIBLE); + + } + + public void setChartColor(int stroke, int fill, int disabled) { + mSeries.setChartColor(stroke, fill, disabled); + } + + public void setListener(DataUsageChartListener listener) { + mListener = listener; + } + + public void bindNetworkStats(NetworkStatsHistory stats) { + mSeries.bindNetworkStats(stats); + } + + public void bindNetworkPolicy(NetworkPolicy policy) { + if (policy == null) { + mSweepDataLimit.setVisibility(View.INVISIBLE); + mSweepDataWarn.setVisibility(View.INVISIBLE); + return; + } + + if (policy.limitBytes != NetworkPolicy.LIMIT_DISABLED) { + mSweepDataLimit.setVisibility(View.VISIBLE); + mSweepDataLimit.setValue(policy.limitBytes); + mSweepDataLimit.setEnabled(true); + } else { + // TODO: set limit default based on axis maximum + mSweepDataLimit.setVisibility(View.VISIBLE); + mSweepDataLimit.setValue(5 * GB_IN_BYTES); + mSweepDataLimit.setEnabled(false); + } + + if (policy.warningBytes != NetworkPolicy.WARNING_DISABLED) { + mSweepDataWarn.setVisibility(View.VISIBLE); + mSweepDataWarn.setValue(policy.warningBytes); + } else { + mSweepDataWarn.setVisibility(View.INVISIBLE); + } + + requestLayout(); + } + + private OnSweepListener mSweepListener = new OnSweepListener() { + public void onSweep(ChartSweepView sweep, boolean sweepDone) { + // always update graph clip region + mSeries.invalidate(); + + // update detail list only when done sweeping + if (sweepDone && mListener != null) { + mListener.onInspectRangeChanged(); + } + } + }; + + private OnSweepListener mWarningListener = new OnSweepListener() { + public void onSweep(ChartSweepView sweep, boolean sweepDone) { + if (sweepDone && mListener != null) { + mListener.onWarningChanged(); + } + } + }; + + private OnSweepListener mLimitListener = new OnSweepListener() { + public void onSweep(ChartSweepView sweep, boolean sweepDone) { + if (sweepDone && mListener != null) { + mListener.onLimitChanged(); + } + } + }; + + /** + * Return current inspection range (start and end time) based on internal + * {@link ChartSweepView} positions. + */ + public long[] getInspectRange() { + final long sweep1 = mSweepTime1.getValue(); + final long sweep2 = mSweepTime2.getValue(); + final long start = Math.min(sweep1, sweep2); + final long end = Math.max(sweep1, sweep2); + return new long[] { start, end }; + } + + public long getWarningBytes() { + return mSweepDataWarn.getValue(); + } + + public long getLimitBytes() { + return mSweepDataLimit.getValue(); + } + + /** + * Set the exact time range that should be displayed, updating how + * {@link ChartNetworkSeriesView} paints. Moves inspection ranges to be the + * last "week" of available data, without triggering listener events. + */ + public void setVisibleRange(long start, long end, long dataBoundary) { + mHoriz.setBounds(start, end); + + // default sweeps to last week of data + final long halfRange = (end + start) / 2; + final long sweepMax = Math.min(end, dataBoundary); + final long sweepMin = Math.max(start, (sweepMax - DateUtils.WEEK_IN_MILLIS)); + + mSweepTime1.setValue(sweepMin); + mSweepTime2.setValue(sweepMax); + + requestLayout(); + mSeries.generatePath(); + mSeries.invalidate(); + } + + public static class TimeAxis implements ChartAxis { + private static final long TICK_INTERVAL = DateUtils.DAY_IN_MILLIS * 7; + + private long mMin; + private long mMax; + private float mSize; + + public TimeAxis() { + final long currentTime = System.currentTimeMillis(); + setBounds(currentTime - DateUtils.DAY_IN_MILLIS * 30, currentTime); + } + + /** {@inheritDoc} */ + public void setBounds(long min, long max) { + mMin = min; + mMax = max; + } + + /** {@inheritDoc} */ + public void setSize(float size) { + this.mSize = size; + } + + /** {@inheritDoc} */ + public float convertToPoint(long value) { + return (mSize * (value - mMin)) / (mMax - mMin); + } + + /** {@inheritDoc} */ + public long convertToValue(float point) { + return (long) (mMin + ((point * (mMax - mMin)) / mSize)); + } + + /** {@inheritDoc} */ + public CharSequence getLabel(long value) { + // TODO: convert to string + return Long.toString(value); + } + + /** {@inheritDoc} */ + public float[] getTickPoints() { + // tick mark for every week + final int tickCount = (int) ((mMax - mMin) / TICK_INTERVAL); + final float[] tickPoints = new float[tickCount]; + for (int i = 0; i < tickCount; i++) { + tickPoints[i] = convertToPoint(mMax - (TICK_INTERVAL * i)); + } + return tickPoints; + } + } + + public static class DataAxis implements ChartAxis { + private long mMin; + private long mMax; + private long mMinLog; + private long mMaxLog; + private float mSize; + + public DataAxis() { + // TODO: adapt ranges to show when history >5GB, and handle 4G + // interfaces with higher limits. + setBounds(1 * MB_IN_BYTES, 5 * GB_IN_BYTES); + } + + /** {@inheritDoc} */ + public void setBounds(long min, long max) { + mMin = min; + mMax = max; + mMinLog = (long) Math.log(mMin); + mMaxLog = (long) Math.log(mMax); + } + + /** {@inheritDoc} */ + public void setSize(float size) { + this.mSize = size; + } + + /** {@inheritDoc} */ + public float convertToPoint(long value) { + return (mSize * (value - mMin)) / (mMax - mMin); + + // TODO: finish tweaking log scale +// if (value > mMin) { +// return (float) ((mSize * (Math.log(value) - mMinLog)) / (mMaxLog - mMinLog)); +// } else { +// return 0; +// } + } + + /** {@inheritDoc} */ + public long convertToValue(float point) { + return (long) (mMin + ((point * (mMax - mMin)) / mSize)); + + // TODO: finish tweaking log scale +// return (long) Math.pow(Math.E, (mMinLog + ((point * (mMaxLog - mMinLog)) / mSize))); + } + + /** {@inheritDoc} */ + public CharSequence getLabel(long value) { + // TODO: convert to string + return Long.toString(value); + } + + /** {@inheritDoc} */ + public float[] getTickPoints() { + final float[] tickPoints = new float[16]; + + long value = mMax; + float mult = 0.8f; + for (int i = 0; i < tickPoints.length; i++) { + tickPoints[i] = convertToPoint(value); + value = (long) (value * mult); + mult *= 0.9; + } + return tickPoints; + } + } + +} diff --git a/src/com/android/settings/widget/InvertedChartAxis.java b/src/com/android/settings/widget/InvertedChartAxis.java new file mode 100644 index 0000000..e7e7893 --- /dev/null +++ b/src/com/android/settings/widget/InvertedChartAxis.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.widget; + +/** + * Utility to invert another {@link ChartAxis}. + */ +public class InvertedChartAxis implements ChartAxis { + private final ChartAxis mWrapped; + private float mSize; + + public InvertedChartAxis(ChartAxis wrapped) { + mWrapped = wrapped; + } + + /** {@inheritDoc} */ + public void setBounds(long min, long max) { + mWrapped.setBounds(min, max); + } + + /** {@inheritDoc} */ + public void setSize(float size) { + mSize = size; + mWrapped.setSize(size); + } + + /** {@inheritDoc} */ + public float convertToPoint(long value) { + return mSize - mWrapped.convertToPoint(value); + } + + /** {@inheritDoc} */ + public long convertToValue(float point) { + return mWrapped.convertToValue(mSize - point); + } + + /** {@inheritDoc} */ + public CharSequence getLabel(long value) { + return mWrapped.getLabel(value); + } + + /** {@inheritDoc} */ + public float[] getTickPoints() { + final float[] points = mWrapped.getTickPoints(); + for (int i = 0; i < points.length; i++) { + points[i] = mSize - points[i]; + } + return points; + } +} diff --git a/src/com/android/settings/wifi/AdvancedSettings.java b/src/com/android/settings/wifi/AdvancedSettings.java index faea9f2..4855c99 100644 --- a/src/com/android/settings/wifi/AdvancedSettings.java +++ b/src/com/android/settings/wifi/AdvancedSettings.java @@ -16,21 +16,23 @@ package com.android.settings.wifi; -import com.android.settings.R; -import com.android.settings.SettingsPreferenceFragment; -import com.android.settings.Utils; - -import android.app.Activity; import android.content.Context; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Bundle; -import android.os.SystemProperties; +import android.preference.CheckBoxPreference; import android.preference.ListPreference; import android.preference.Preference; +import android.preference.PreferenceScreen; +import android.provider.Settings; +import android.provider.Settings.Secure; import android.text.TextUtils; -import android.widget.Toast; import android.util.Log; +import android.widget.Toast; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.Utils; public class AdvancedSettings extends SettingsPreferenceFragment implements Preference.OnPreferenceChangeListener { @@ -39,6 +41,8 @@ public class AdvancedSettings extends SettingsPreferenceFragment private static final String KEY_MAC_ADDRESS = "mac_address"; private static final String KEY_CURRENT_IP_ADDRESS = "current_ip_address"; private static final String KEY_FREQUENCY_BAND = "frequency_band"; + private static final String KEY_NOTIFY_OPEN_NETWORKS = "notify_open_networks"; + private static final String KEY_SLEEP_POLICY = "sleep_policy"; private WifiManager mWifiManager; @@ -62,27 +66,61 @@ public class AdvancedSettings extends SettingsPreferenceFragment } private void initPreferences() { + CheckBoxPreference notifyOpenNetworks = + (CheckBoxPreference) findPreference(KEY_NOTIFY_OPEN_NETWORKS); + notifyOpenNetworks.setChecked(Secure.getInt(getContentResolver(), + Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0) == 1); + notifyOpenNetworks.setEnabled(mWifiManager.isWifiEnabled()); - ListPreference pref = (ListPreference) findPreference(KEY_FREQUENCY_BAND); + ListPreference frequencyPref = (ListPreference) findPreference(KEY_FREQUENCY_BAND); if (mWifiManager.isDualBandSupported()) { - pref.setOnPreferenceChangeListener(this); + frequencyPref.setOnPreferenceChangeListener(this); int value = mWifiManager.getFrequencyBand(); if (value != -1) { - pref.setValue(String.valueOf(value)); + frequencyPref.setValue(String.valueOf(value)); } else { Log.e(TAG, "Failed to fetch frequency band"); } } else { - getPreferenceScreen().removePreference(pref); + if (frequencyPref != null) { + // null if it has already been removed before resume + getPreferenceScreen().removePreference(frequencyPref); + } + } + + ListPreference sleepPolicyPref = (ListPreference) findPreference(KEY_SLEEP_POLICY); + if (sleepPolicyPref != null) { + if (Utils.isWifiOnly()) { + sleepPolicyPref.setEntries(R.array.wifi_sleep_policy_entries_wifi_only); + sleepPolicyPref.setSummary(R.string.wifi_setting_sleep_policy_summary_wifi_only); + } + sleepPolicyPref.setOnPreferenceChangeListener(this); + int value = Settings.System.getInt(getContentResolver(), + Settings.System.WIFI_SLEEP_POLICY, + Settings.System.WIFI_SLEEP_POLICY_NEVER); + sleepPolicyPref.setValue(String.valueOf(value)); } } + @Override + public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) { + String key = preference.getKey(); + + if (KEY_NOTIFY_OPEN_NETWORKS.equals(key)) { + Secure.putInt(getContentResolver(), + Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, + ((CheckBoxPreference) preference).isChecked() ? 1 : 0); + } else { + return super.onPreferenceTreeClick(screen, preference); + } + return true; + } + public boolean onPreferenceChange(Preference preference, Object newValue) { String key = preference.getKey(); - if (key == null) return true; - if (key.equals(KEY_FREQUENCY_BAND)) { + if (KEY_FREQUENCY_BAND.equals(key)) { try { mWifiManager.setFrequencyBand(Integer.parseInt(((String) newValue)), true); } catch (NumberFormatException e) { @@ -92,6 +130,17 @@ public class AdvancedSettings extends SettingsPreferenceFragment } } + if (KEY_SLEEP_POLICY.equals(key)) { + try { + Settings.System.putInt(getContentResolver(), + Settings.System.WIFI_SLEEP_POLICY, Integer.parseInt(((String) newValue))); + } catch (NumberFormatException e) { + Toast.makeText(getActivity(), R.string.wifi_setting_sleep_policy_error, + Toast.LENGTH_SHORT).show(); + return false; + } + } + return true; } diff --git a/src/com/android/settings/wifi/AdvancedWifiSettings.java b/src/com/android/settings/wifi/AdvancedWifiSettings.java new file mode 100644 index 0000000..1da8b68 --- /dev/null +++ b/src/com/android/settings/wifi/AdvancedWifiSettings.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.wifi; + +import android.content.Context; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.preference.ListPreference; +import android.preference.Preference; +import android.text.TextUtils; +import android.util.Log; +import android.widget.Toast; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.Utils; + +public class AdvancedWifiSettings extends SettingsPreferenceFragment + implements Preference.OnPreferenceChangeListener { + + private static final String TAG = "AdvancedWifiSettings"; + private static final String KEY_MAC_ADDRESS = "mac_address"; + private static final String KEY_CURRENT_IP_ADDRESS = "current_ip_address"; + private static final String KEY_FREQUENCY_BAND = "frequency_band"; + + private WifiManager mWifiManager; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.wifi_advanced_settings); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); + } + + @Override + public void onResume() { + super.onResume(); + initPreferences(); + refreshWifiInfo(); + } + + private void initPreferences() { + + ListPreference pref = (ListPreference) findPreference(KEY_FREQUENCY_BAND); + + if (mWifiManager.isDualBandSupported()) { + pref.setOnPreferenceChangeListener(this); + int value = mWifiManager.getFrequencyBand(); + if (value != -1) { + pref.setValue(String.valueOf(value)); + } else { + Log.e(TAG, "Failed to fetch frequency band"); + } + } else { + getPreferenceScreen().removePreference(pref); + } + } + + public boolean onPreferenceChange(Preference preference, Object newValue) { + String key = preference.getKey(); + if (key == null) return true; + + if (key.equals(KEY_FREQUENCY_BAND)) { + try { + mWifiManager.setFrequencyBand(Integer.parseInt(((String) newValue)), true); + } catch (NumberFormatException e) { + Toast.makeText(getActivity(), R.string.wifi_setting_frequency_band_error, + Toast.LENGTH_SHORT).show(); + return false; + } + } + + return true; + } + + private void refreshWifiInfo() { + WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); + + Preference wifiMacAddressPref = findPreference(KEY_MAC_ADDRESS); + String macAddress = wifiInfo == null ? null : wifiInfo.getMacAddress(); + wifiMacAddressPref.setSummary(!TextUtils.isEmpty(macAddress) ? macAddress + : getActivity().getString(R.string.status_unavailable)); + + Preference wifiIpAddressPref = findPreference(KEY_CURRENT_IP_ADDRESS); + String ipAddress = Utils.getWifiIpAddresses(getActivity()); + wifiIpAddressPref.setSummary(ipAddress == null ? + getActivity().getString(R.string.status_unavailable) : ipAddress); + } + +} diff --git a/src/com/android/settings/wifi/WifiConfigController.java b/src/com/android/settings/wifi/WifiConfigController.java index 91f4110..a8ce94f 100644 --- a/src/com/android/settings/wifi/WifiConfigController.java +++ b/src/com/android/settings/wifi/WifiConfigController.java @@ -16,33 +16,29 @@ package com.android.settings.wifi; +import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID; + import android.content.Context; -import android.content.DialogInterface; import android.content.res.Resources; -import android.net.DhcpInfo; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.NetworkInfo.DetailedState; import android.net.NetworkUtils; -import android.net.Proxy; import android.net.ProxyProperties; import android.net.RouteInfo; import android.net.wifi.WifiConfiguration; -import android.net.wifi.WifiConfiguration.IpAssignment; import android.net.wifi.WifiConfiguration.AuthAlgorithm; +import android.net.wifi.WifiConfiguration.IpAssignment; import android.net.wifi.WifiConfiguration.KeyMgmt; -import android.net.wifi.WpsConfiguration; -import android.net.wifi.WpsConfiguration.Setup; - -import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID; import android.net.wifi.WifiConfiguration.ProxySettings; import android.net.wifi.WifiInfo; +import android.net.wifi.WpsConfiguration; +import android.net.wifi.WpsConfiguration.Setup; import android.security.Credentials; import android.security.KeyStore; import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; -import android.text.format.Formatter; import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -57,12 +53,10 @@ import com.android.settings.ProxySelector; import com.android.settings.R; import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.UnknownHostException; import java.util.Iterator; /** - * The class for allowing UIs like {@link WifiDialog} and {@link WifiConfigPreference} to + * The class for allowing UIs like {@link WifiDialog} and {@link WifiConfigUiBase} to * share the logic for controlling buttons, text fields, etc. */ public class WifiConfigController implements TextWatcher, @@ -429,7 +423,9 @@ public class WifiConfigController implements TextWatcher, int networkPrefixLength = -1; try { networkPrefixLength = Integer.parseInt(mNetworkPrefixLengthView.getText().toString()); - } catch (NumberFormatException e) { } + } catch (NumberFormatException e) { + // Use -1 + } if (networkPrefixLength < 0 || networkPrefixLength > 32) { return R.string.wifi_ip_settings_invalid_network_prefix_length; } @@ -698,6 +694,7 @@ public class WifiConfigController implements TextWatcher, private void setSelection(Spinner spinner, String value) { if (value != null) { + @SuppressWarnings("unchecked") ArrayAdapter<String> adapter = (ArrayAdapter<String>) spinner.getAdapter(); for (int i = adapter.getCount() - 1; i >= 0; --i) { if (value.equals(adapter.getItem(i))) { @@ -719,10 +716,12 @@ public class WifiConfigController implements TextWatcher, @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // work done in afterTextChanged } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { + // work done in afterTextChanged } @Override @@ -750,5 +749,6 @@ public class WifiConfigController implements TextWatcher, @Override public void onNothingSelected(AdapterView<?> parent) { + // } } diff --git a/src/com/android/settings/wifi/WifiEnabler.java b/src/com/android/settings/wifi/WifiEnabler.java index 7f1221e..223022d 100644 --- a/src/com/android/settings/wifi/WifiEnabler.java +++ b/src/com/android/settings/wifi/WifiEnabler.java @@ -16,9 +16,6 @@ package com.android.settings.wifi; -import com.android.settings.R; -import com.android.settings.WirelessSettings; - import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -27,19 +24,19 @@ import android.net.NetworkInfo; import android.net.wifi.SupplicantState; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.preference.Preference; -import android.preference.CheckBoxPreference; import android.provider.Settings; -import android.text.TextUtils; +import android.widget.CompoundButton; +import android.widget.Switch; import android.widget.Toast; +import com.android.settings.R; +import com.android.settings.WirelessSettings; + import java.util.concurrent.atomic.AtomicBoolean; -public class WifiEnabler implements Preference.OnPreferenceChangeListener { +public class WifiEnabler implements CompoundButton.OnCheckedChangeListener { private final Context mContext; - private final CheckBoxPreference mCheckBox; - private final CharSequence mOriginalSummary; - + private Switch mSwitch; private AtomicBoolean mConnected = new AtomicBoolean(false); private final WifiManager mWifiManager; @@ -65,11 +62,9 @@ public class WifiEnabler implements Preference.OnPreferenceChangeListener { } }; - public WifiEnabler(Context context, CheckBoxPreference checkBox) { + public WifiEnabler(Context context, Switch switch_) { mContext = context; - mCheckBox = checkBox; - mOriginalSummary = checkBox.getSummary(); - checkBox.setPersistent(false); + mSwitch = switch_; mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); mIntentFilter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION); @@ -81,78 +76,86 @@ public class WifiEnabler implements Preference.OnPreferenceChangeListener { public void resume() { // Wi-Fi state is sticky, so just let the receiver update UI mContext.registerReceiver(mReceiver, mIntentFilter); - mCheckBox.setOnPreferenceChangeListener(this); + mSwitch.setOnCheckedChangeListener(this); } public void pause() { mContext.unregisterReceiver(mReceiver); - mCheckBox.setOnPreferenceChangeListener(null); + mSwitch.setOnCheckedChangeListener(null); } - public boolean onPreferenceChange(Preference preference, Object value) { - boolean enable = (Boolean) value; + public void setSwitch(Switch switch_) { + if (mSwitch == switch_) return; + mSwitch.setOnCheckedChangeListener(null); + mSwitch = switch_; + mSwitch.setOnCheckedChangeListener(this); + + final int wifiState = mWifiManager.getWifiState(); + boolean isEnabled = wifiState == WifiManager.WIFI_STATE_ENABLED; + boolean isDisabled = wifiState == WifiManager.WIFI_STATE_DISABLED; + mSwitch.setChecked(isEnabled); + mSwitch.setEnabled(isEnabled || isDisabled); + } + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { // Show toast message if Wi-Fi is not allowed in airplane mode - if (enable && !WirelessSettings - .isRadioAllowed(mContext, Settings.System.RADIO_WIFI)) { - Toast.makeText(mContext, R.string.wifi_in_airplane_mode, - Toast.LENGTH_SHORT).show(); - return false; + if (isChecked && !WirelessSettings.isRadioAllowed(mContext, Settings.System.RADIO_WIFI)) { + Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show(); + // Reset switch to off. No infinite check/listenenr loop. + buttonView.setChecked(false); } - /** - * Disable tethering if enabling Wifi - */ + // Disable tethering if enabling Wifi int wifiApState = mWifiManager.getWifiApState(); - if (enable && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) || + if (isChecked && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) || (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) { mWifiManager.setWifiApEnabled(null, false); } - if (mWifiManager.setWifiEnabled(enable)) { - mCheckBox.setEnabled(false); + + if (mWifiManager.setWifiEnabled(isChecked)) { + // Intent has been taken into account, disable until new state is active + mSwitch.setEnabled(false); } else { - mCheckBox.setSummary(R.string.wifi_error); + // Error + Toast.makeText(mContext, R.string.wifi_error, Toast.LENGTH_SHORT).show(); } - - // Don't update UI to opposite state until we're sure - return false; } private void handleWifiStateChanged(int state) { switch (state) { case WifiManager.WIFI_STATE_ENABLING: - mCheckBox.setSummary(R.string.wifi_starting); - mCheckBox.setEnabled(false); + mSwitch.setEnabled(false); break; case WifiManager.WIFI_STATE_ENABLED: - mCheckBox.setChecked(true); - mCheckBox.setSummary(null); - mCheckBox.setEnabled(true); + mSwitch.setChecked(true); + mSwitch.setEnabled(true); break; case WifiManager.WIFI_STATE_DISABLING: - mCheckBox.setSummary(R.string.wifi_stopping); - mCheckBox.setEnabled(false); + mSwitch.setEnabled(false); break; case WifiManager.WIFI_STATE_DISABLED: - mCheckBox.setChecked(false); - mCheckBox.setSummary(mOriginalSummary); - mCheckBox.setEnabled(true); + mSwitch.setChecked(false); + mSwitch.setEnabled(true); break; default: - mCheckBox.setChecked(false); - mCheckBox.setSummary(R.string.wifi_error); - mCheckBox.setEnabled(true); + mSwitch.setChecked(false); + mSwitch.setEnabled(true); } } - private void handleStateChanged(NetworkInfo.DetailedState state) { + private void handleStateChanged(@SuppressWarnings("unused") NetworkInfo.DetailedState state) { + // After the refactoring from a CheckBoxPreference to a Switch, this method is useless since + // there is nowhere to display a summary. + // This code is kept in case a future change re-introduces an associated text. + /* // WifiInfo is valid if and only if Wi-Fi is enabled. - // Here we use the state of the check box as an optimization. - if (state != null && mCheckBox.isChecked()) { + // Here we use the state of the switch as an optimization. + if (state != null && mSwitch.isChecked()) { WifiInfo info = mWifiManager.getConnectionInfo(); if (info != null) { - mCheckBox.setSummary(Summary.get(mContext, info.getSSID(), state)); + //setSummary(Summary.get(mContext, info.getSSID(), state)); } } + */ } } diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java index ab5e686..a829602 100644 --- a/src/com/android/settings/wifi/WifiSettings.java +++ b/src/com/android/settings/wifi/WifiSettings.java @@ -18,6 +18,7 @@ package com.android.settings.wifi; import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID; +import android.app.ActionBar; import android.app.Activity; import android.app.AlertDialog; import android.content.BroadcastReceiver; @@ -31,40 +32,35 @@ import android.net.NetworkInfo.DetailedState; import android.net.wifi.ScanResult; import android.net.wifi.SupplicantState; import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiConfiguration.KeyMgmt; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.net.wifi.WpsResult; -import android.net.wifi.WifiConfiguration.KeyMgmt; -import android.net.wifi.WpsConfiguration; import android.os.Bundle; import android.os.Handler; import android.os.Message; -import android.preference.CheckBoxPreference; import android.preference.Preference; -import android.preference.ListPreference; import android.preference.PreferenceActivity; import android.preference.PreferenceScreen; -import android.provider.Settings.Secure; -import android.provider.Settings; import android.security.Credentials; import android.security.KeyStore; import android.util.Log; import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +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.view.ContextMenu.ContextMenuInfo; -import android.widget.Toast; import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.Switch; +import android.widget.Toast; import com.android.internal.util.AsyncChannel; -import com.android.settings.ProgressCategoryBase; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; -import com.android.settings.Utils; import java.util.ArrayList; import java.util.Collection; @@ -83,14 +79,14 @@ import java.util.concurrent.atomic.AtomicBoolean; * other decorations specific to that screen. */ public class WifiSettings extends SettingsPreferenceFragment - implements DialogInterface.OnClickListener, Preference.OnPreferenceChangeListener { + implements DialogInterface.OnClickListener { private static final String TAG = "WifiSettings"; private static final int MENU_ID_SCAN = Menu.FIRST; - private static final int MENU_ID_ADVANCED = Menu.FIRST + 1; - private static final int MENU_ID_CONNECT = Menu.FIRST + 2; - private static final int MENU_ID_FORGET = Menu.FIRST + 3; - private static final int MENU_ID_MODIFY = Menu.FIRST + 4; - private static final String KEY_SLEEP_POLICY = "sleep_policy"; + private static final int MENU_ID_ADD_NETWORK = Menu.FIRST + 1; + private static final int MENU_ID_ADVANCED = Menu.FIRST + 2; + private static final int MENU_ID_CONNECT = Menu.FIRST + 3; + private static final int MENU_ID_FORGET = Menu.FIRST + 4; + private static final int MENU_ID_MODIFY = Menu.FIRST + 5; private final IntentFilter mFilter; private final BroadcastReceiver mReceiver; @@ -98,12 +94,8 @@ public class WifiSettings extends SettingsPreferenceFragment private WifiManager mWifiManager; private WifiEnabler mWifiEnabler; - private CheckBoxPreference mNotifyOpenNetworks; - private ProgressCategoryBase mAccessPoints; - private Preference mAddNetwork; // An access point being editted is stored here. private AccessPoint mSelectedAccessPoint; - private boolean mEdit; private DetailedState mLastState; private WifiInfo mLastInfo; @@ -180,60 +172,48 @@ public class WifiSettings extends SettingsPreferenceFragment // state, start it off in the right state mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false); - // Avoid re-adding on returning from an overlapping activity/fragment. - if (getPreferenceScreen() == null || getPreferenceScreen().getPreferenceCount() < 2) { - if (mEnableNextOnConnection) { - if (hasNextButton()) { - final ConnectivityManager connectivity = (ConnectivityManager) - getActivity().getSystemService(Context.CONNECTIVITY_SERVICE); - if (connectivity != null) { - NetworkInfo info = connectivity.getNetworkInfo( - ConnectivityManager.TYPE_WIFI); - changeNextButtonState(info.isConnected()); - } + if (mEnableNextOnConnection) { + if (hasNextButton()) { + final ConnectivityManager connectivity = (ConnectivityManager) + getActivity().getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivity != null) { + NetworkInfo info = connectivity.getNetworkInfo( + ConnectivityManager.TYPE_WIFI); + changeNextButtonState(info.isConnected()); } } + } - if (mInXlSetupWizard) { - addPreferencesFromResource(R.xml.wifi_access_points_for_wifi_setup_xl); - } else if (intent.getBooleanExtra("only_access_points", false)) { - addPreferencesFromResource(R.xml.wifi_access_points); - } else { - addPreferencesFromResource(R.xml.wifi_settings); - mWifiEnabler = new WifiEnabler(activity, - (CheckBoxPreference) findPreference("enable_wifi")); - mNotifyOpenNetworks = - (CheckBoxPreference) findPreference("notify_open_networks"); - mNotifyOpenNetworks.setChecked(Secure.getInt(getContentResolver(), - Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0) == 1); - } - // This may be either ProgressCategory or AccessPointCategoryForXL. - final ProgressCategoryBase preference = - (ProgressCategoryBase) findPreference("access_points"); - mAccessPoints = preference; - mAccessPoints.setOrderingAsAdded(false); - mAddNetwork = findPreference("add_network"); - - ListPreference pref = (ListPreference) findPreference(KEY_SLEEP_POLICY); - if (pref != null) { - if (Utils.isWifiOnly()) { - pref.setEntries(R.array.wifi_sleep_policy_entries_wifi_only); - pref.setSummary(R.string.wifi_setting_sleep_policy_summary_wifi_only); + if (mInXlSetupWizard) { + addPreferencesFromResource(R.xml.wifi_access_points_for_wifi_setup_xl); + } else { + addPreferencesFromResource(R.xml.wifi_settings); + + Switch actionBarSwitch = 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); + actionBarSwitch.setPadding(0, 0, padding, 0); + activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, + ActionBar.DISPLAY_SHOW_CUSTOM); + activity.getActionBar().setCustomView(actionBarSwitch, new ActionBar.LayoutParams( + ActionBar.LayoutParams.WRAP_CONTENT, + ActionBar.LayoutParams.WRAP_CONTENT, + Gravity.CENTER_VERTICAL | Gravity.RIGHT)); } - pref.setOnPreferenceChangeListener(this); - int value = Settings.System.getInt(getContentResolver(), - Settings.System.WIFI_SLEEP_POLICY, - Settings.System.WIFI_SLEEP_POLICY_NEVER); - pref.setValue(String.valueOf(value)); } - registerForContextMenu(getListView()); - setHasOptionsMenu(true); + mWifiEnabler = new WifiEnabler(activity, actionBarSwitch); } + registerForContextMenu(getListView()); + setHasOptionsMenu(true); + // After confirming PreferenceScreen is available, we call super. super.onActivityCreated(savedInstanceState); - } @Override @@ -245,10 +225,11 @@ public class WifiSettings extends SettingsPreferenceFragment getActivity().registerReceiver(mReceiver, mFilter); if (mKeyStoreNetworkId != INVALID_NETWORK_ID && - KeyStore.getInstance().test() == KeyStore.NO_ERROR) { + KeyStore.getInstance().state() == KeyStore.State.UNLOCKED) { mWifiManager.connectNetwork(mKeyStoreNetworkId); } mKeyStoreNetworkId = INVALID_NETWORK_ID; + updateAccessPoints(); } @@ -270,8 +251,11 @@ public class WifiSettings extends SettingsPreferenceFragment public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { // We don't want menus in Setup Wizard XL. if (!mInXlSetupWizard) { + final boolean wifiIsEnabled = mWifiManager.isWifiEnabled(); menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.wifi_menu_scan) - .setIcon(R.drawable.ic_menu_scan_network); + .setIcon(R.drawable.ic_menu_scan_network).setEnabled(wifiIsEnabled); + menu.add(Menu.NONE, MENU_ID_ADD_NETWORK, 0, R.string.wifi_add_network) + .setIcon(android.R.drawable.ic_menu_add).setEnabled(wifiIsEnabled); menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced) .setIcon(android.R.drawable.ic_menu_manage); } @@ -286,6 +270,11 @@ public class WifiSettings extends SettingsPreferenceFragment mScanner.forceScan(); } return true; + case MENU_ID_ADD_NETWORK: + if (mWifiManager.isWifiEnabled()) { + onAddNetworkPressed(); + } + return true; case MENU_ID_ADVANCED: if (getActivity() instanceof PreferenceActivity) { ((PreferenceActivity) getActivity()).startPreferencePanel( @@ -363,43 +352,17 @@ public class WifiSettings extends SettingsPreferenceFragment if (preference instanceof AccessPoint) { mSelectedAccessPoint = (AccessPoint) preference; showConfigUi(mSelectedAccessPoint, false); - } else if (preference == mAddNetwork) { - onAddNetworkPressed(); - } else if (preference == mNotifyOpenNetworks) { - Secure.putInt(getContentResolver(), - Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, - mNotifyOpenNetworks.isChecked() ? 1 : 0); } else { return super.onPreferenceTreeClick(screen, preference); } return true; } - public boolean onPreferenceChange(Preference preference, Object newValue) { - String key = preference.getKey(); - if (key == null) return true; - - if (key.equals(KEY_SLEEP_POLICY)) { - try { - Settings.System.putInt(getContentResolver(), - Settings.System.WIFI_SLEEP_POLICY, Integer.parseInt(((String) newValue))); - } catch (NumberFormatException e) { - Toast.makeText(getActivity(), R.string.wifi_setting_sleep_policy_error, - Toast.LENGTH_SHORT).show(); - return false; - } - } - - return true; - } - - /** * Shows an appropriate Wifi configuration component. * Called when a user clicks "Add network" preference or one of available networks is selected. */ private void showConfigUi(AccessPoint accessPoint, boolean edit) { - mEdit = edit; if (mInXlSetupWizard) { ((WifiSettingsForSetupWizardXL)getActivity()).showConfigUi(accessPoint, edit); } else { @@ -417,7 +380,7 @@ public class WifiSettings extends SettingsPreferenceFragment private boolean requireKeyStore(WifiConfiguration config) { if (WifiConfigController.requireKeyStore(config) && - KeyStore.getInstance().test() != KeyStore.NO_ERROR) { + KeyStore.getInstance().state() != KeyStore.State.UNLOCKED) { mKeyStoreNetworkId = config.networkId; Credentials.getInstance().unlock(getActivity()); return true; @@ -430,20 +393,40 @@ public class WifiSettings extends SettingsPreferenceFragment * the strength of network and the security for it. */ private void updateAccessPoints() { - mAccessPoints.removeAll(); + final PreferenceScreen preferenceScreen = getPreferenceScreen(); + preferenceScreen.removeAll(); + final int wifiState = mWifiManager.getWifiState(); + + switch (wifiState) { + case WifiManager.WIFI_STATE_ENABLED: + // AccessPoints are automatically sorted with TreeSet. + final Collection<AccessPoint> accessPoints = constructAccessPoints(); + if (mInXlSetupWizard) { + ((WifiSettingsForSetupWizardXL)getActivity()).onAccessPointsUpdated( + getPreferenceScreen(), accessPoints); + } else { + for (AccessPoint accessPoint : accessPoints) { + getPreferenceScreen().addPreference(accessPoint); + } + } + break; - // AccessPoints are automatically sorted with TreeSet. - final Collection<AccessPoint> accessPoints = constructAccessPoints(); - if (mInXlSetupWizard) { - ((WifiSettingsForSetupWizardXL)getActivity()).onAccessPointsUpdated( - mAccessPoints, accessPoints); - } else { - for (AccessPoint accessPoint : accessPoints) { - mAccessPoints.addPreference(accessPoint); - } + case WifiManager.WIFI_STATE_DISABLING: + addMessagePreference(R.string.wifi_stopping); + break; + + case WifiManager.WIFI_STATE_DISABLED: + addMessagePreference(R.string.wifi_empty_list_wifi_off); + break; } } + private void addMessagePreference(int messageId) { + Preference emptyListPreference = new Preference(getActivity()); + emptyListPreference.setTitle(messageId); + getPreferenceScreen().addPreference(emptyListPreference); + } + private Collection<AccessPoint> constructAccessPoints() { Collection<AccessPoint> accessPoints = new ArrayList<AccessPoint>(); @@ -542,9 +525,9 @@ public class WifiSettings extends SettingsPreferenceFragment mLastState = state; } - for (int i = mAccessPoints.getPreferenceCount() - 1; i >= 0; --i) { + for (int i = getPreferenceScreen().getPreferenceCount() - 1; i >= 0; --i) { // Maybe there's a WifiConfigPreference - Preference preference = mAccessPoints.getPreference(i); + Preference preference = getPreferenceScreen().getPreference(i); if (preference instanceof AccessPoint) { final AccessPoint accessPoint = (AccessPoint) preference; accessPoint.update(mLastInfo, mLastState); @@ -557,12 +540,25 @@ public class WifiSettings extends SettingsPreferenceFragment } private void updateWifiState(int state) { - if (state == WifiManager.WIFI_STATE_ENABLED) { - mScanner.resume(); - } else { - mScanner.pause(); - mAccessPoints.removeAll(); + getActivity().invalidateOptionsMenu(); + + switch (state) { + case WifiManager.WIFI_STATE_ENABLED: + mScanner.resume(); + return; // not break, to avoid pause + + case WifiManager.WIFI_STATE_ENABLING: + getPreferenceScreen().removeAll(); + addMessagePreference(R.string.wifi_starting); + break; + + case WifiManager.WIFI_STATE_DISABLED: + getPreferenceScreen().removeAll(); + addMessagePreference(R.string.wifi_empty_list_wifi_off); + break; } + + mScanner.pause(); } private class Scanner extends Handler { @@ -580,7 +576,6 @@ public class WifiSettings extends SettingsPreferenceFragment void pause() { mRetry = 0; - mAccessPoints.setProgress(false); removeMessages(0); } @@ -594,7 +589,6 @@ public class WifiSettings extends SettingsPreferenceFragment Toast.LENGTH_LONG).show(); return; } - mAccessPoints.setProgress(mRetry != 0); // Combo scans can take 5-6s to complete. Increase interval to 10s. sendEmptyMessageDelayed(0, 10000); } @@ -636,6 +630,7 @@ public class WifiSettings extends SettingsPreferenceFragment } break; } + break; //TODO: more connectivity feedback default: //Ignore @@ -740,7 +735,7 @@ public class WifiSettings extends SettingsPreferenceFragment mScanner.resume(); } - mAccessPoints.removeAll(); + getPreferenceScreen().removeAll(); } /** @@ -753,8 +748,9 @@ public class WifiSettings extends SettingsPreferenceFragment } /* package */ int getAccessPointsCount() { - if (mAccessPoints != null) { - return mAccessPoints.getPreferenceCount(); + final boolean wifiIsEnabled = mWifiManager.isWifiEnabled(); + if (wifiIsEnabled) { + return getPreferenceScreen().getPreferenceCount(); } else { return 0; } diff --git a/src/com/android/settings/wifi/WifiSettingsForSetupWizardXL.java b/src/com/android/settings/wifi/WifiSettingsForSetupWizardXL.java index a73f96c..a3f1764 100644 --- a/src/com/android/settings/wifi/WifiSettingsForSetupWizardXL.java +++ b/src/com/android/settings/wifi/WifiSettingsForSetupWizardXL.java @@ -16,8 +16,6 @@ package com.android.settings.wifi; -import com.android.settings.R; - import android.app.Activity; import android.content.Context; import android.content.Intent; @@ -28,11 +26,9 @@ import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.Handler; import android.os.Message; -import android.preference.PreferenceCategory; +import android.preference.PreferenceScreen; import android.text.TextUtils; import android.util.Log; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; @@ -43,6 +39,7 @@ import android.widget.ProgressBar; import android.widget.TextView; import com.android.internal.util.AsyncChannel; +import com.android.settings.R; import java.util.Collection; import java.util.EnumMap; @@ -75,12 +72,6 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis sNetworkStateMap.put(DetailedState.FAILED, DetailedState.FAILED); } - /** - * Used with {@link Button#setTag(Object)} to remember "Connect" button is pressed in - * with "add network" flow. - */ - private static final int CONNECT_BUTTON_TAG_ADD_NETWORK = 1; - private WifiSettings mWifiSettings; private WifiManager mWifiManager; @@ -166,7 +157,7 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis // At first, Wifi module doesn't return SCANNING state (it's too early), so we manually // show it. - showScanningProgressBar(); + showScanningState(); } private void initViews() { @@ -291,17 +282,16 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis switch (state) { case SCANNING: { - // Let users know the device is working correctly though currently there's - // no visible network on the list. - if (mWifiSettings.getAccessPointsCount() == 0) { - showScanningState(); - } else { - // Users already see available networks. - showDisconnectedProgressBar(); - if (mScreenState == SCREEN_STATE_DISCONNECTED) { + if (mScreenState == SCREEN_STATE_DISCONNECTED) { + if (mWifiSettings.getAccessPointsCount() == 0) { + showScanningState(); + } else { + showDisconnectedProgressBar(); mWifiSettingsFragmentLayout.setVisibility(View.VISIBLE); mBottomPadding.setVisibility(View.GONE); } + } else { + showDisconnectedProgressBar(); } break; } @@ -316,7 +306,8 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis break; } default: // DISCONNECTED, FAILED - if (mScreenState != SCREEN_STATE_CONNECTED) { + if (mScreenState != SCREEN_STATE_CONNECTED && + mWifiSettings.getAccessPointsCount() > 0) { showDisconnectedState(Summary.get(this, state)); } break; @@ -326,7 +317,8 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis private void showDisconnectedState(String stateString) { showDisconnectedProgressBar(); - if (mScreenState == SCREEN_STATE_DISCONNECTED) { + if (mScreenState == SCREEN_STATE_DISCONNECTED && + mWifiSettings.getAccessPointsCount() > 0) { mWifiSettingsFragmentLayout.setVisibility(View.VISIBLE); mBottomPadding.setVisibility(View.GONE); } @@ -474,13 +466,11 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis parent.removeAllViews(); mWifiConfig = new WifiConfigUiForSetupWizardXL(this, parent, selectedAccessPoint, edit); - // Tag will be updated in this method when needed. - mConnectButton.setTag(null); if (selectedAccessPoint == null) { // "Add network" flow showAddNetworkTitle(); mConnectButton.setVisibility(View.VISIBLE); - mConnectButton.setTag(CONNECT_BUTTON_TAG_ADD_NETWORK); + showDisconnectedProgressBar(); showEditingButtonState(); } else if (selectedAccessPoint.security == AccessPoint.SECURITY_NONE) { mNetworkName = selectedAccessPoint.getTitle().toString(); @@ -490,6 +480,7 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis } else { mNetworkName = selectedAccessPoint.getTitle().toString(); showEditingTitle(); + showDisconnectedProgressBar(); showEditingButtonState(); if (selectedAccessPoint.security == AccessPoint.SECURITY_EAP) { onEapNetworkSelected(); @@ -645,8 +636,9 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis mAddNetworkButton.setEnabled(true); mRefreshButton.setEnabled(true); mSkipOrNextButton.setEnabled(true); - mWifiSettingsFragmentLayout.setVisibility(View.VISIBLE); showDisconnectedProgressBar(); + mWifiSettingsFragmentLayout.setVisibility(View.VISIBLE); + mBottomPadding.setVisibility(View.GONE); } setPaddingVisibility(View.VISIBLE); @@ -671,9 +663,10 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis /** * Called when the list of AccessPoints are modified and this Activity needs to refresh * the list. + * @param preferenceScreen */ /* package */ void onAccessPointsUpdated( - PreferenceCategory holder, Collection<AccessPoint> accessPoints) { + PreferenceScreen preferenceScreen, Collection<AccessPoint> accessPoints) { // If we already show some of access points but the bar still shows "scanning" state, it // should be stopped. if (mProgressBar.isIndeterminate() && accessPoints.size() > 0) { @@ -688,20 +681,12 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis for (AccessPoint accessPoint : accessPoints) { accessPoint.setLayoutResource(R.layout.custom_preference); - holder.addPreference(accessPoint); + preferenceScreen.addPreference(accessPoint); } } private void refreshAccessPoints(boolean disconnectNetwork) { - final Object tag = mConnectButton.getTag(); - if (tag != null && (tag instanceof Integer) && - ((Integer)tag == CONNECT_BUTTON_TAG_ADD_NETWORK)) { - // In "Add network" flow, we won't get DetaledState available for changing ProgressBar - // state. Instead we manually show previous status here. - showDisconnectedState(Summary.get(this, mPreviousNetworkState)); - } else { - showScanningState(); - } + showScanningState(); if (disconnectNetwork) { mWifiManager.disconnect(); @@ -801,11 +786,6 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis mWifiManager.connectNetwork(config); } - @Override - public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, view, menuInfo); - } - /** * Replace the current background with a new background whose id is resId if needed. */ |